From 83467e4c6061a1a6a5996d17f4d74239ecfd376e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 18 Jul 2022 19:30:17 +0200 Subject: [PATCH] Sleep schedule: store 'next check' timestamp in UTC SQLite doesn't parse the timezone info, so timestamps should always be in UTC. --- .../manager/persistence/worker_sleep_schedule.go | 16 ++++++++++++++-- .../persistence/worker_sleep_schedule_test.go | 2 +- internal/manager/sleep_scheduler/calculations.go | 7 ++++++- .../sleep_scheduler/sleep_scheduler_test.go | 3 +-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/internal/manager/persistence/worker_sleep_schedule.go b/internal/manager/persistence/worker_sleep_schedule.go index db497a79..a68a0707 100644 --- a/internal/manager/persistence/worker_sleep_schedule.go +++ b/internal/manager/persistence/worker_sleep_schedule.go @@ -63,6 +63,11 @@ func (db *DB) SetWorkerSleepSchedule(ctx context.Context, workerUUID string, sch schedule.WorkerID = worker.ID schedule.Worker = worker + // Only store timestamps in UTC. + if schedule.NextCheck.Location() != time.UTC { + schedule.NextCheck = schedule.NextCheck.UTC() + } + tx := db.gormDB.WithContext(ctx). Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "worker_id"}}, @@ -73,6 +78,11 @@ func (db *DB) SetWorkerSleepSchedule(ctx context.Context, workerUUID string, sch } func (db *DB) SetWorkerSleepScheduleNextCheck(ctx context.Context, schedule *SleepSchedule) error { + // Only store timestamps in UTC. + if schedule.NextCheck.Location() != time.UTC { + schedule.NextCheck = schedule.NextCheck.UTC() + } + tx := db.gormDB.WithContext(ctx). Select("next_check"). Updates(schedule) @@ -92,10 +102,12 @@ func (db *DB) FetchSleepScheduleWorker(ctx context.Context, schedule *SleepSched // FetchSleepSchedulesToCheck returns the sleep schedules that are due for a check. func (db *DB) FetchSleepSchedulesToCheck(ctx context.Context) ([]*SleepSchedule, error) { - log.Trace().Msg("fetching sleep schedules that need checking") - now := db.gormDB.NowFunc() + log.Debug(). + Str("timeout", now.String()). + Msg("fetching sleep schedules that need checking") + schedules := []*SleepSchedule{} tx := db.gormDB.WithContext(ctx). Model(&SleepSchedule{}). diff --git a/internal/manager/persistence/worker_sleep_schedule_test.go b/internal/manager/persistence/worker_sleep_schedule_test.go index c6e85c02..d4689e3d 100644 --- a/internal/manager/persistence/worker_sleep_schedule_test.go +++ b/internal/manager/persistence/worker_sleep_schedule_test.go @@ -243,7 +243,7 @@ func TestFetchSleepSchedulesToCheck(t *testing.T) { ctx, finish, db := persistenceTestFixtures(t, 1*time.Second) defer finish() - mockedNow := mustParseTime("2022-06-07T11:14:47+02:00") + mockedNow := mustParseTime("2022-06-07T11:14:47+02:00").UTC() mockedPast := mockedNow.Add(-10 * time.Second) mockedFuture := mockedNow.Add(10 * time.Second) diff --git a/internal/manager/sleep_scheduler/calculations.go b/internal/manager/sleep_scheduler/calculations.go index 896970ef..bea60c81 100644 --- a/internal/manager/sleep_scheduler/calculations.go +++ b/internal/manager/sleep_scheduler/calculations.go @@ -69,7 +69,7 @@ func calculateNextCheck(now time.Time, schedule *persistence.SleepSchedule) time nextChecks := []time.Time{ // Always check at the end of the day. - calcNext(persistence.TimeOfDay{Hour: 24, Minute: 0}), + endOfDay(now), } // No start time means "start of the day", which is already covered by @@ -96,3 +96,8 @@ func earliestTime(timestamps []time.Time) time.Time { } return earliest } + +// endOfDay returns the next midnight at UTC. +func endOfDay(now time.Time) time.Time { + return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, 1) +} diff --git a/internal/manager/sleep_scheduler/sleep_scheduler_test.go b/internal/manager/sleep_scheduler/sleep_scheduler_test.go index 62a63aca..7f2d9503 100644 --- a/internal/manager/sleep_scheduler/sleep_scheduler_test.go +++ b/internal/manager/sleep_scheduler/sleep_scheduler_test.go @@ -232,12 +232,11 @@ type TestMocks struct { func (m *TestMocks) todayAt(hour, minute int) time.Time { now := m.clock.Now() return time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()) - } // endOfDay returns midnight of the day after whatever the mocked clock's "now" is set to. func (m *TestMocks) endOfDay() time.Time { - now := m.clock.Now() + now := m.clock.Now().UTC() return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, 1) }