flamenco/internal/manager/sleep_scheduler/calculations.go
Sybren A. Stüvel eb9f46dc9b Manager: fix sleep scheduler time zone handling
The sleep scheduler now always works in the local time zone.

This fixes the sleep scheduler part of #104219.
2023-08-23 13:54:02 +00:00

105 lines
2.8 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"
)
// 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
}
tod := persistence.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 {
// calcNext returns the given time of day on "today" if that hasn't passed
// yet, otherwise on "tomorrow".
calcNext := func(tod persistence.TimeOfDay) time.Time {
nextCheck := tod.OnDate(now).In(time.Local)
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)
}