package internal import ( "time" "github.com/pkg/errors" ) // TimeOfDay represents the time of day. type TimeOfDay struct { hour, minute, second int d time.Duration } const shortForm = "15:04:05" // NewTimeOfDay returns a newly initialized TimeOfDay. func NewTimeOfDay(hour, minute, second int) TimeOfDay { d := time.Duration(second)*time.Second + time.Duration(minute)*time.Minute + time.Duration(hour)*time.Hour return TimeOfDay{hour: hour, minute: minute, second: second, d: d} } // ParseTimeOfDay parses a TimeOfDay from a string in the format HH:MM:SS. func ParseTimeOfDay(str string) (TimeOfDay, error) { t, err := time.Parse(shortForm, str) if err != nil { return TimeOfDay{}, errors.Wrap(err, "time must be in the format HH:MM:SS") } return NewTimeOfDay(t.Clock()), nil } // TimeRange represents a time band in a given time zone. type TimeRange struct { startTime, endTime TimeOfDay weekdays []time.Weekday startDay, endDay *time.Weekday loc *time.Location } // NewUTCTimeRange returns a time range in UTC. func NewUTCTimeRange(start, end TimeOfDay, weekdays []time.Weekday) (*TimeRange, error) { return NewTimeRangeInLocation(start, end, weekdays, time.UTC) } // NewTimeRangeInLocation returns a time range in a given location. func NewTimeRangeInLocation(start, end TimeOfDay, weekdays []time.Weekday, loc *time.Location) (*TimeRange, error) { if loc == nil { return nil, errors.New("time: missing Location in call to NewTimeRangeInLocation") } return &TimeRange{ startTime: start, endTime: end, weekdays: weekdays, loc: loc, }, nil } // NewUTCWeekRange returns a weekly TimeRange. func NewUTCWeekRange(startTime, endTime TimeOfDay, startDay, endDay time.Weekday) (*TimeRange, error) { return NewWeekRangeInLocation(startTime, endTime, startDay, endDay, time.UTC) } // NewWeekRangeInLocation returns a time range in a given location. func NewWeekRangeInLocation(startTime, endTime TimeOfDay, startDay, endDay time.Weekday, loc *time.Location) (*TimeRange, error) { r, err := NewTimeRangeInLocation(startTime, endTime, []time.Weekday{}, loc) if err != nil { return nil, err } r.startDay = &startDay r.endDay = &endDay return r, nil } func (r *TimeRange) isInWeekdays(day time.Weekday) bool { if len(r.weekdays) > 0 { found := false for _, weekday := range r.weekdays { if day == weekday { found = true break } } if !found { return false } } return true } func (r *TimeRange) addWeekdayOffset(day time.Weekday, offset int) time.Weekday { return (day + time.Weekday(offset)) % 7 } func (r *TimeRange) isInTimeRange(t time.Time) bool { t = t.In(r.loc) ts := NewTimeOfDay(t.Clock()).d if r.startTime.d < r.endTime.d { if r.isInWeekdays(t.Weekday()) { return r.startTime.d <= ts && ts <= r.endTime.d } return false } if ts <= r.endTime.d { return r.isInWeekdays(r.addWeekdayOffset(t.Weekday(), -1)) } if ts >= r.startTime.d { return r.isInWeekdays(t.Weekday()) } return false } func (r *TimeRange) isInWeekRange(t time.Time) bool { t = t.In(r.loc) day := t.Weekday() if *r.startDay == *r.endDay { if day == *r.startDay { return r.isInTimeRange(t) } if r.startTime.d < r.endTime.d { return false } return true } switch { case *r.startDay < *r.endDay: if day < *r.startDay || *r.endDay < day { return false } default: if *r.endDay < day && day < *r.startDay { return false } } timeOfDay := NewTimeOfDay(t.Clock()) if day == *r.startDay { return timeOfDay.d >= r.startTime.d } if day == *r.endDay { return timeOfDay.d <= r.endTime.d } return true } // IsInRange returns true if time t is within in the time range. func (r *TimeRange) IsInRange(t time.Time) bool { if r == nil { return true } if r.startDay != nil { return r.isInWeekRange(t) } return r.isInTimeRange(t) } // IsInSameRange determines if two points in time are in the same time range. func (r *TimeRange) IsInSameRange(t1, t2 time.Time) bool { if r == nil { return true } if !(r.IsInRange(t1) && r.IsInRange(t2)) { return false } if t2.Before(t1) { t1, t2 = t2, t1 } t1 = t1.In(r.loc) t1Time := NewTimeOfDay(t1.Clock()) dayOffset := 0 if r.endDay == nil { if r.startTime.d >= r.endTime.d && t1Time.d >= r.startTime.d { dayOffset = 1 } } else { switch { case *r.endDay < t1.Weekday(): dayOffset = 7 + int(*(r.endDay)-t1.Weekday()) case t1.Weekday() == *r.endDay: if r.endTime.d <= t1Time.d { dayOffset = 7 } default: dayOffset = int(*(r.endDay) - t1.Weekday()) } } sessionEnd := time.Date(t1.Year(), t1.Month(), t1.Day(), r.endTime.hour, r.endTime.minute, r.endTime.second, 0, r.loc) sessionEnd = sessionEnd.AddDate(0, 0, dayOffset) return t2.Before(sessionEnd) }