package mqe import ( "fmt" "time" "gordenko.dev/dima/qb/timeutil" ) type PeriodCalculatorOptions struct { GroupBy GroupBy LastDayOfMonth int FirstHourOfDay int Since time.Time Until time.Time } func NewPeriodCalculator(opt PeriodCalculatorOptions) PeriodCalculator { switch opt.GroupBy { case ByHour, ByDay, ByMonth: // pass default: panic(fmt.Sprintf("bug: unknown groupBy %q", opt.GroupBy)) } s := PeriodCalculator{ groupBy: opt.GroupBy, lastDayOfMonth: opt.LastDayOfMonth, firstHourOfDay: opt.FirstHourOfDay, } s.sincePeriod = s.TimeToPeriod(opt.Since) s.untilPeriod = s.TimeToPeriod(opt.Until) return s } type PeriodCalculator struct { groupBy GroupBy lastDayOfMonth int firstHourOfDay int sincePeriod time.Time untilPeriod time.Time } func (s PeriodCalculator) IsExtendedSincePeriod(period time.Time) bool { return period.Before(s.sincePeriod) } func (s PeriodCalculator) IsExtendedUntilPeriod(period time.Time) bool { return period.After(s.untilPeriod) } func (s PeriodCalculator) NextPeriod(period time.Time) time.Time { switch s.groupBy { case ByHour: return period.Add(time.Hour) case ByDay: return period.AddDate(0, 0, 1) case ByMonth: return period.AddDate(0, 1, 0) default: panic(fmt.Sprintf("bug: unknown groupBy %q", s.groupBy)) } } func (s PeriodCalculator) IsPeriodCorrectEnds(period time.Time, lastMeasureTime int64) bool { // по умолчанию tm := timeutil.LastSecondInPeriod(period, string(s.groupBy)) switch s.groupBy { case ByDay: if s.firstHourOfDay > 0 { // Было 23:59, стало - 01:59 tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour) } case ByMonth: if s.lastDayOfMonth == 0 { if s.firstHourOfDay > 0 { // Было 23:59, стало - 01:59 tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour) } } else { if s.firstHourOfDay > 0 { // last day 28, first hour 2 превращается в day 29 01:59:59 tm = time.Date( tm.Year(), tm.Month(), s.lastDayOfMonth+1, s.firstHourOfDay-1, 59, 59, 0, tm.Location(), ) } else { // last day 23:59:59 tm = time.Date( tm.Year(), tm.Month(), s.lastDayOfMonth, 23, 59, 59, 0, tm.Location(), ) } } } timestamp := tm.Add(-179 * time.Second).Unix() if lastMeasureTime >= timestamp && lastMeasureTime <= tm.Unix() { return true } //fmt.Printf("Ends: lastMeasureTime=%s, timetsamp=%s\n", time.Unix(lastMeasureTime, 0), time.Unix(timestamp, 0)) return false } func (s PeriodCalculator) TimeToPeriod(tm time.Time) (period time.Time) { switch s.groupBy { case ByDay: if s.firstHourOfDay > 0 { if tm.Hour() < s.firstHourOfDay { tm = tm.AddDate(0, 0, -1) } } case ByMonth: if s.lastDayOfMonth > 0 { if s.lastDayOfMonth > 28 { // На всякий случай добавляем защиту от дурака, ибо модификация даты // в запросе (+1 месяц) для 29, 30 и 31 чисел сделает запрос неверным. panic(fmt.Sprintf("LastDayOfMonth: %d, max allowed value is 28", s.lastDayOfMonth)) } if s.firstHourOfDay > 0 { if tm.Hour() >= s.firstHourOfDay { if tm.Day() > s.lastDayOfMonth { // add 1 month tm = tm.AddDate(0, 1, 0) } } else { // firstHourOfDay показывает что текущий день еще не наступил, // поэтому day - 1 if (tm.Day() - 1) <= s.lastDayOfMonth { tm = tm.AddDate(0, 0, -1) } else { // - 1 day, + 1 month tm = tm.AddDate(0, 0, -1).AddDate(0, 1, 0) } } } else { if tm.Day() > s.lastDayOfMonth { tm = tm.AddDate(0, 1, 0) } } } else { if s.firstHourOfDay > 0 { if tm.Hour() < s.firstHourOfDay { tm = tm.AddDate(0, 0, -1) } } } } return timeutil.FirstSecondInPeriod(tm, string(s.groupBy)) }