
The unit test was using a mocked "now" in a hardcoded timezone (UTC+2), while the code under test was actually using the local timezone of the computer. Also the schedule computations are now explicitly only in local time.
112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package sleep_scheduler
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"projects.blender.org/studio/flamenco/internal/manager/persistence"
|
|
"projects.blender.org/studio/flamenco/pkg/api"
|
|
"projects.blender.org/studio/flamenco/pkg/time_of_day"
|
|
)
|
|
|
|
// scheduledWorkerStatus returns the expected worker status at the given date/time.
|
|
func scheduledWorkerStatus(now time.Time, sched *persistence.SleepSchedule) api.WorkerStatus {
|
|
if sched == nil {
|
|
// If there is no schedule at all, the worker should be awake.
|
|
return api.WorkerStatusAwake
|
|
}
|
|
|
|
// This function should always work with localized time.
|
|
now = now.In(time.Local)
|
|
|
|
tod := time_of_day.MakeTimeOfDay(now)
|
|
|
|
if !sched.IsActive {
|
|
return api.WorkerStatusAwake
|
|
}
|
|
|
|
if sched.DaysOfWeek != "" {
|
|
weekdayName := strings.ToLower(now.Weekday().String()[:2])
|
|
if !strings.Contains(sched.DaysOfWeek, weekdayName) {
|
|
// There are days configured, and today is not a sleeping day.
|
|
return api.WorkerStatusAwake
|
|
}
|
|
}
|
|
|
|
beforeStart := sched.StartTime.HasValue() && tod.IsBefore(sched.StartTime)
|
|
afterEnd := sched.EndTime.HasValue() && !tod.IsBefore(sched.EndTime)
|
|
|
|
if beforeStart || afterEnd {
|
|
// Outside sleeping time.
|
|
return api.WorkerStatusAwake
|
|
}
|
|
|
|
return api.WorkerStatusAsleep
|
|
}
|
|
|
|
func cleanupDaysOfWeek(daysOfWeek string) string {
|
|
trimmed := strings.TrimSpace(daysOfWeek)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
|
|
daynames := strings.Fields(trimmed)
|
|
for idx, name := range daynames {
|
|
daynames[idx] = strings.ToLower(strings.TrimSpace(name))[:2]
|
|
}
|
|
return strings.Join(daynames, " ")
|
|
}
|
|
|
|
// Return a timestamp when the next scheck for this schedule is due.
|
|
func calculateNextCheck(now time.Time, schedule persistence.SleepSchedule) time.Time {
|
|
// This function should always work with localized time.
|
|
now = now.In(time.Local)
|
|
|
|
// calcNext returns the given time of day on "today" if that hasn't passed
|
|
// yet, otherwise on "tomorrow".
|
|
calcNext := func(tod time_of_day.TimeOfDay) time.Time {
|
|
nextCheck := tod.OnDate(now)
|
|
if nextCheck.Before(now) {
|
|
nextCheck = nextCheck.AddDate(0, 0, 1)
|
|
}
|
|
return nextCheck
|
|
}
|
|
|
|
nextChecks := []time.Time{
|
|
// Always check at the end of the day.
|
|
endOfDay(now),
|
|
}
|
|
|
|
// No start time means "start of the day", which is already covered by
|
|
// yesterday's "end of the day" check.
|
|
if schedule.StartTime.HasValue() {
|
|
nextChecks = append(nextChecks, calcNext(schedule.StartTime))
|
|
}
|
|
// No end time means "end of the day", which is already covered by today's
|
|
// "end of the day" check.
|
|
if schedule.EndTime.HasValue() {
|
|
nextChecks = append(nextChecks, calcNext(schedule.EndTime))
|
|
}
|
|
|
|
next := earliestTime(nextChecks)
|
|
return next
|
|
}
|
|
|
|
func earliestTime(timestamps []time.Time) time.Time {
|
|
earliest := timestamps[0]
|
|
for _, timestamp := range timestamps[1:] {
|
|
if timestamp.Before(earliest) {
|
|
earliest = timestamp
|
|
}
|
|
}
|
|
return earliest
|
|
}
|
|
|
|
// endOfDay returns the next midnight at UTC.
|
|
func endOfDay(now time.Time) time.Time {
|
|
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
|
return startOfDay.AddDate(0, 0, 1)
|
|
}
|