Files
qb/mqe/period_calculator.go

173 lines
3.9 KiB
Go
Raw Permalink Normal View History

2026-06-19 07:25:46 +03:00
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))
}