github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/coveragedb/time_period.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package coveragedb 5 6 import ( 7 "errors" 8 "fmt" 9 "slices" 10 "sort" 11 12 "cloud.google.com/go/civil" 13 ) 14 15 type TimePeriod struct { 16 DateTo civil.Date 17 Days int 18 Type string // DayPeriod, MonthPeriod, QuarterPeriod. 19 } 20 21 // DatesFromTo returns the closed range [fromDate, toDate]. 22 func (tp *TimePeriod) DatesFromTo() (civil.Date, civil.Date) { 23 return tp.DateTo.AddDays(-tp.Days + 1), tp.DateTo 24 } 25 26 func MakeTimePeriod(targetDate civil.Date, periodType string) (TimePeriod, error) { 27 pOps, err := PeriodOps(periodType) 28 if err != nil { 29 return TimePeriod{}, err 30 } 31 tp := TimePeriod{DateTo: targetDate, Days: pOps.pointedPeriodDays(targetDate), Type: periodType} 32 if !pOps.IsValidPeriod(tp) { 33 return TimePeriod{}, fmt.Errorf("date %s doesn't point the period(%s) end", targetDate.String(), periodType) 34 } 35 return tp, nil 36 } 37 38 const ( 39 DayPeriod = "day" 40 MonthPeriod = "month" 41 QuarterPeriod = "quarter" 42 ) 43 44 var AllPeriods = []string{DayPeriod, MonthPeriod, QuarterPeriod} 45 46 var errUnknownTimePeriodType = errors.New("unknown time period type") 47 48 func MinMaxDays(periodType string) (int, int, error) { 49 switch periodType { 50 case DayPeriod: 51 return 1, 1, nil 52 case MonthPeriod: 53 return 28, 31, nil 54 case QuarterPeriod: 55 return 31 + 28 + 31, 31 + 30 + 31, nil 56 default: 57 return 0, 0, errUnknownTimePeriodType 58 } 59 } 60 61 func PeriodOps(periodType string) (periodOps, error) { 62 switch periodType { 63 case DayPeriod: 64 return &DayPeriodOps{}, nil 65 case MonthPeriod: 66 return &MonthPeriodOps{}, nil 67 case QuarterPeriod: 68 return &QuarterPeriodOps{}, nil 69 default: 70 return nil, errUnknownTimePeriodType 71 } 72 } 73 74 type periodOps interface { 75 IsValidPeriod(p TimePeriod) bool 76 lastPeriodDate(d civil.Date) civil.Date 77 pointedPeriodDays(d civil.Date) int 78 } 79 80 func GenNPeriodsTill(n int, d civil.Date, periodType string) ([]TimePeriod, error) { 81 pOps, err := PeriodOps(periodType) 82 if err != nil { 83 return nil, err 84 } 85 var res []TimePeriod 86 for i := 0; i < n; i++ { 87 d = pOps.lastPeriodDate(d) 88 res = append(res, TimePeriod{DateTo: d, Days: pOps.pointedPeriodDays(d), Type: periodType}) 89 d = d.AddDays(-pOps.pointedPeriodDays(d)) 90 } 91 slices.Reverse(res) 92 return res, nil 93 } 94 95 type DayPeriodOps struct{} 96 97 func (dpo *DayPeriodOps) lastPeriodDate(d civil.Date) civil.Date { 98 return d 99 } 100 101 func (dpo *DayPeriodOps) IsValidPeriod(p TimePeriod) bool { 102 return p.Days == 1 103 } 104 105 func (dpo *DayPeriodOps) pointedPeriodDays(d civil.Date) int { 106 return 1 107 } 108 109 type MonthPeriodOps struct{} 110 111 func (m *MonthPeriodOps) lastPeriodDate(d civil.Date) civil.Date { 112 d.Day = 1 113 d = d.AddDays(32) 114 d.Day = 1 115 return d.AddDays(-1) 116 } 117 118 func (m *MonthPeriodOps) IsValidPeriod(p TimePeriod) bool { 119 lmd := m.lastPeriodDate(p.DateTo) 120 return lmd == p.DateTo && p.Days == lmd.Day 121 } 122 123 func (m *MonthPeriodOps) pointedPeriodDays(d civil.Date) int { 124 return m.lastPeriodDate(d).Day 125 } 126 127 type QuarterPeriodOps struct{} 128 129 func (q *QuarterPeriodOps) IsValidPeriod(p TimePeriod) bool { 130 lmd := q.lastPeriodDate(p.DateTo) 131 return lmd == p.DateTo && p.Days == q.pointedPeriodDays(lmd) 132 } 133 134 func (q *QuarterPeriodOps) lastPeriodDate(d civil.Date) civil.Date { 135 d.Month = ((d.Month-1)/3)*3 + 3 136 d.Day = 1 137 return (&MonthPeriodOps{}).lastPeriodDate(d) 138 } 139 140 func (q *QuarterPeriodOps) pointedPeriodDays(d civil.Date) int { 141 d = q.lastPeriodDate(d) 142 d.Day = 1 143 res := 0 144 for i := 0; i < 3; i++ { 145 res += (&MonthPeriodOps{}).pointedPeriodDays(d) 146 d.Month-- 147 } 148 return res 149 } 150 151 func PeriodsToMerge(srcDates, mergedPeriods []TimePeriod, srcRows, mergedRows []int64, ops periodOps) []TimePeriod { 152 periodRows := map[civil.Date]int64{} 153 for i, srcDate := range srcDates { 154 periodID := ops.lastPeriodDate(srcDate.DateTo) 155 periodRows[periodID] += srcRows[i] 156 } 157 for i, period := range mergedPeriods { 158 if !ops.IsValidPeriod(period) { 159 continue 160 } 161 mergerPeriodID := period.DateTo 162 if rowsAvailable, ok := periodRows[mergerPeriodID]; ok && rowsAvailable == mergedRows[i] { 163 delete(periodRows, mergerPeriodID) 164 } 165 } 166 periods := []TimePeriod{} 167 for periodEndDate := range periodRows { 168 periods = append(periods, 169 TimePeriod{DateTo: periodEndDate, Days: ops.pointedPeriodDays(periodEndDate)}) 170 } 171 sort.Slice(periods, func(i, j int) bool { 172 return periods[i].DateTo.After(periods[j].DateTo) 173 }) 174 return periods 175 } 176 177 func AtMostNLatestPeriods(periods []TimePeriod, n int) []TimePeriod { 178 sort.Slice(periods, func(i, j int) bool { 179 return periods[i].DateTo.After(periods[j].DateTo) 180 }) 181 if len(periods) <= n { 182 return periods 183 } 184 return periods[:n] 185 }