package search import ( "fmt" "time" ) // DateRange represents a time range [Start, End) in a specific year. type DateRange struct { Start time.Time End time.Time Year int // the year this range belongs to (for scoring) } // BuildCELFilter creates a CEL filter string for a date range using created_ts. func BuildCELFilter(dr DateRange) string { return fmt.Sprintf("created_ts >= %d && created_ts < %d", dr.Start.Unix(), dr.End.Unix()) } func isLeapYear(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } func daysInMonth(year int, month time.Month) int { return time.Date(year, month+1, 0, 0, 0, 0, 0, time.UTC).Day() } // tier1Ranges returns date ranges for the exact same day in previous years. // Special handling for Feb 29 / Feb 28. func tier1Ranges(today time.Time, maxYears int, loc *time.Location) []DateRange { month := today.Month() day := today.Day() currentYear := today.Year() var ranges []DateRange for y := currentYear - maxYears; y < currentYear; y++ { if month == time.February && day == 29 { // Today is Feb 29 — only include leap years if !isLeapYear(y) { continue } } // Check that this day exists in that year if day > daysInMonth(y, month) { continue } start := time.Date(y, month, day, 0, 0, 0, 0, loc) end := start.AddDate(0, 0, 1) ranges = append(ranges, DateRange{Start: start, End: end, Year: y}) } // Special: if today is Feb 28 in a non-leap year, also check Feb 29 in past leap years if month == time.February && day == 28 && !isLeapYear(currentYear) { for y := currentYear - maxYears; y < currentYear; y++ { if isLeapYear(y) { start := time.Date(y, time.February, 29, 0, 0, 0, 0, loc) end := start.AddDate(0, 0, 1) ranges = append(ranges, DateRange{Start: start, End: end, Year: y}) } } } return ranges } // tier2Ranges returns date ranges for the same day-of-month in previous months (last 24 months). // Excludes the current month (covered by Tier 1). func tier2Ranges(today time.Time, loc *time.Location) []DateRange { day := today.Day() currentYear := today.Year() currentMonth := today.Month() var ranges []DateRange for i := 1; i <= 24; i++ { t := time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, loc).AddDate(0, -i, 0) y := t.Year() m := t.Month() // Skip if this day doesn't exist in that month if day > daysInMonth(y, m) { continue } start := time.Date(y, m, day, 0, 0, 0, 0, loc) end := start.AddDate(0, 0, 1) ranges = append(ranges, DateRange{Start: start, End: end, Year: y}) } return ranges } // tier3Ranges returns ±3 day ranges around the exact date in previous years, // excluding the exact day itself (which is Tier 1). func tier3Ranges(today time.Time, maxYears int, loc *time.Location) []DateRange { month := today.Month() day := today.Day() currentYear := today.Year() var ranges []DateRange for y := currentYear - maxYears; y < currentYear; y++ { // The center date in that year if day > daysInMonth(y, month) { continue } center := time.Date(y, month, day, 0, 0, 0, 0, loc) weekStart := center.AddDate(0, 0, -3) weekEnd := center.AddDate(0, 0, 4) // +3 days inclusive = +4 exclusive // We need to exclude the center day: split into [weekStart, center) and [center+1day, weekEnd) centerEnd := center.AddDate(0, 0, 1) ranges = append(ranges, DateRange{Start: weekStart, End: center, Year: y}, DateRange{Start: centerEnd, End: weekEnd, Year: y}, ) } return ranges } // tier4Ranges returns the same month in previous years, // excluding the ±3 day window around the exact date (covered by Tier 1+3). func tier4Ranges(today time.Time, maxYears int, loc *time.Location) []DateRange { month := today.Month() day := today.Day() currentYear := today.Year() var ranges []DateRange for y := currentYear - maxYears; y < currentYear; y++ { monthStart := time.Date(y, month, 1, 0, 0, 0, 0, loc) monthEnd := time.Date(y, month+1, 1, 0, 0, 0, 0, loc) d := min(day, daysInMonth(y, month)) center := time.Date(y, month, d, 0, 0, 0, 0, loc) excludeStart := center.AddDate(0, 0, -3) excludeEnd := center.AddDate(0, 0, 4) // Before the exclude window if excludeStart.After(monthStart) { ranges = append(ranges, DateRange{Start: monthStart, End: excludeStart, Year: y}) } // After the exclude window if excludeEnd.Before(monthEnd) { ranges = append(ranges, DateRange{Start: excludeEnd, End: monthEnd, Year: y}) } } return ranges } // tier5Ranges returns the same quarter in previous years, excluding the current month. func tier5Ranges(today time.Time, maxYears int, loc *time.Location) []DateRange { month := today.Month() currentYear := today.Year() qStart := (month-1)/3*3 + 1 // first month of quarter (1, 4, 7, 10) var quarterMonths [3]time.Month for i := range 3 { quarterMonths[i] = time.Month(int(qStart) + i) } var ranges []DateRange for y := currentYear - maxYears; y < currentYear; y++ { for _, m := range quarterMonths { if m == month { continue // exclude current month (covered by Tier 4) } start := time.Date(y, m, 1, 0, 0, 0, 0, loc) end := time.Date(y, m+1, 1, 0, 0, 0, 0, loc) ranges = append(ranges, DateRange{Start: start, End: end, Year: y}) } } return ranges } // tier6Ranges returns the same half-year in previous years, excluding the current quarter. func tier6Ranges(today time.Time, maxYears int, loc *time.Location) []DateRange { month := today.Month() currentYear := today.Year() // Half-year: H1 = Jan-Jun, H2 = Jul-Dec var halfStart time.Month if month <= 6 { halfStart = 1 } else { halfStart = 7 } // Current quarter start qStart := (month-1)/3*3 + 1 var ranges []DateRange for y := currentYear - maxYears; y < currentYear; y++ { for m := halfStart; m < halfStart+6; m++ { // Skip months in the current quarter if m >= qStart && m < qStart+3 { continue } start := time.Date(y, m, 1, 0, 0, 0, 0, loc) end := time.Date(y, m+1, 1, 0, 0, 0, 0, loc) ranges = append(ranges, DateRange{Start: start, End: end, Year: y}) } } return ranges } // tier7Ranges returns 2 to 6 months ago (recent past). func tier7Ranges(today time.Time, loc *time.Location) []DateRange { end := time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, loc).AddDate(0, -2, 0) start := time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, loc).AddDate(0, -6, 0) return []DateRange{ {Start: start, End: end, Year: 0}, // Year=0 means "recent" } }