diff --git a/absences_test.go b/absences_test.go index 930d8a3..f6cc2c9 100644 --- a/absences_test.go +++ b/absences_test.go @@ -18,7 +18,7 @@ func TestAddAbsence(t *testing.T) { store, err := NewStore(db, zerolog.Nop()) require.NoError(t, err) - now := NextWednesday(nil) + now := NextOfWeekday(nil, ScheduledGameDay) assert.NoError(t, store.AddAbsence(context.TODO(), now, "foobarbaz")) absentees, err := store.GetAbsentees(context.TODO(), now) assert.NoError(t, err) @@ -40,7 +40,7 @@ func TestGetSchedule(t *testing.T) { store, err := NewStore(db, zerolog.Nop()) require.NoError(t, err) - now := NextWednesday(nil) + now := NextOfWeekday(nil, ScheduledGameDay) _ = store.AddAbsence(context.TODO(), now.Add(7*24*time.Hour), "foobar") diff --git a/cmd/themis-server/main.go b/cmd/themis-server/main.go index 679ed39..ad7aa07 100644 --- a/cmd/themis-server/main.go +++ b/cmd/themis-server/main.go @@ -581,7 +581,7 @@ func main() { }, "schedule": func(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) error { // get schedule from now to 4 wednesdays into the future - sched, err := store.GetSchedule(ctx, themis.NextWednesday(nil), themis.NextWednesday(nil).Add(4*7*24*time.Hour)) + sched, err := store.GetSchedule(ctx, themis.NextOfWeekday(nil, themis.ScheduledGameDay), themis.NextOfWeekday(nil, themis.ScheduledGameDay).Add(4*7*24*time.Hour)) if err != nil { if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, @@ -643,7 +643,7 @@ func main() { "absent": func(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) error { var rawDate string if len(i.ApplicationCommandData().Options) == 0 { - rawDate = themis.NextWednesday(nil).Format(time.DateOnly) + rawDate = themis.NextOfWeekday(nil, themis.ScheduledGameDay).Format(time.DateOnly) } else { rawDate = i.ApplicationCommandData().Options[0].StringValue() } @@ -739,7 +739,7 @@ func main() { defer span.End() log.Info().Msg("sending weekly reminder") - absentees, err := store.GetAbsentees(ctx, themis.NextWednesday(nil)) + absentees, err := store.GetAbsentees(ctx, themis.NextOfWeekday(nil, themis.ScheduledGameDay)) if err != nil { log.Error().Err(err).Msg("failed to get absentees for next session") return @@ -884,7 +884,7 @@ func registerHandlers(sess *discordgo.Session, handlers map[string]Handler) { userId := i.Member.User.ID log.Info().Ctx(ctx).Str("message_component", "schedule-response").Str("userid", userId).Msg("handling message component interaction") - if err := store.AddAbsence(ctx, themis.NextWednesday(nil), userId); err != nil { + if err := store.AddAbsence(ctx, themis.NextOfWeekday(nil, themis.ScheduledGameDay), userId); err != nil { if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ diff --git a/notify.go b/notify.go index 3494dea..accd5ee 100644 --- a/notify.go +++ b/notify.go @@ -3,17 +3,17 @@ package themis import ( "context" "fmt" + "sync" "time" "github.com/rs/zerolog/log" "go.opentelemetry.io/otel/trace" ) -var loc *time.Location - -func init() { - loc, _ = time.LoadLocation("America/New_York") -} +var onceLoc = sync.OnceValue(func() *time.Location { + loc, _ := time.LoadLocation("America/New_York") + return loc +}) type Notifier struct { c chan context.Context @@ -26,13 +26,13 @@ func NewNotifier(c chan context.Context) *Notifier { } func (n *Notifier) Start(ctx context.Context) { - m := NextWednesday(nil) + m := NextOfWeekday(nil, ScheduledGameDay) sat := m.AddDate(0, 0, -4) if sat.Before(time.Now()) { sat = sat.AddDate(0, 0, 7) } - t, err := time.ParseInLocation(time.DateTime, fmt.Sprintf("%s 17:00:00", sat.Format(time.DateOnly)), loc) + t, err := time.ParseInLocation(time.DateTime, fmt.Sprintf("%s 17:00:00", sat.Format(time.DateOnly)), onceLoc()) if err != nil { panic("failed to parse next wednesday notif time. this is likely a bug.") } diff --git a/time.go b/time.go index 30d4547..2e41398 100644 --- a/time.go +++ b/time.go @@ -2,6 +2,8 @@ package themis import "time" +const ScheduledGameDay = time.Wednesday + var defaultClock Clock = DefaultClock{} type Clock interface { @@ -14,10 +16,10 @@ func (DefaultClock) Now() time.Time { return time.Now() } -func NextWednesday(clock Clock) time.Time { +func NextOfWeekday(clock Clock, weekday time.Weekday) time.Time { if clock == nil { clock = defaultClock } now := clock.Now() - return now.AddDate(0, 0, int((10-now.Weekday())%7)) + return now.AddDate(0, 0, int(((7+weekday)-now.Weekday())%7)) } diff --git a/time_test.go b/time_test.go index 9d13147..e18f040 100644 --- a/time_test.go +++ b/time_test.go @@ -52,7 +52,50 @@ func TestNextWednesday(t *testing.T) { require.NoError(t, err) testClock := TestClock{now: seedt} - if got := NextWednesday(testClock); !reflect.DeepEqual(got, wantt) { + if got := NextOfWeekday(testClock, time.Wednesday); !reflect.DeepEqual(got, wantt) { + t.Errorf("NextWednesday() = %v, want %v", got, wantt) + } + }) + } +} + +func TestNextMonday(t *testing.T) { + tests := []struct { + name string + seed string + want string + }{ + { + name: "on monday", + seed: "2023-11-13T15:04:05Z", + want: "2023-11-13T15:04:05Z", + }, + { + name: "on sunday", + seed: "2023-11-12T15:04:05Z", + want: "2023-11-13T15:04:05Z", + }, + { + name: "on tuesday", + seed: "2023-11-14T15:04:05Z", + want: "2023-11-20T15:04:05Z", + }, + { + name: "on saturday", + seed: "2023-11-18T15:04:05Z", + want: "2023-11-20T15:04:05Z", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + seedt, err := time.Parse(time.RFC3339, tt.seed) + require.NoError(t, err) + + wantt, err := time.Parse(time.RFC3339, tt.want) + require.NoError(t, err) + + testClock := TestClock{now: seedt} + if got := NextOfWeekday(testClock, time.Monday); !reflect.DeepEqual(got, wantt) { t.Errorf("NextWednesday() = %v, want %v", got, wantt) } })