From c44b39822d0f2f3cd8f8a8f7e497191264953b30 Mon Sep 17 00:00:00 2001 From: Ryan Nixon <902500+taiidani@users.noreply.github.com> Date: Sat, 23 Sep 2023 10:25:54 -0700 Subject: [PATCH] Remove Timezones (#164) * Remove timezone tracking * Remove constant * Reduce seed day by one * More specific locator in spec * Add remaining time estimate * Human readable remainingtime * Disambiguate tests --- app/actions.go | 13 -------- app/assets/index.js | 4 --- app/guess.go | 5 +--- app/hint.go | 2 +- app/seed.go | 4 +-- app/stats.go | 4 +-- app/templates/guesser.gohtml | 9 ++++-- internal/sessions/session.go | 49 +++++++++++++++++++++++-------- internal/sessions/session_test.go | 49 +++++++++++++++++++++++++++++++ web/tests/guess.spec.ts | 5 ++-- 10 files changed, 100 insertions(+), 44 deletions(-) diff --git a/app/actions.go b/app/actions.go index e656eef..bdf692b 100644 --- a/app/actions.go +++ b/app/actions.go @@ -7,9 +7,7 @@ import ( "guess_my_word/internal/sessions" "html/template" "io/fs" - "log/slog" "net/http" - "strconv" "time" "github.com/gin-gonic/gin" @@ -92,7 +90,6 @@ func AddHandlers(r *gin.Engine) error { } type bodyData struct { - TZ int // The timezone offset for the user, in milliseconds Session *sessions.Session // The session for the user } @@ -103,15 +100,5 @@ func parseBodyData(c *gin.Context) (bodyData, error) { ret.Session = sessions.New(c) fnPopulateTestSessionData(ret.Session) - tz, err := strconv.ParseInt(c.Request.URL.Query().Get("tz"), 10, 64) - if err != nil { - tz, err = strconv.ParseInt(c.Request.PostFormValue("tz"), 10, 64) - if err != nil { - slog.Error("Could not parse timezone", "error", err) - tz = 0 - } - } - ret.TZ = int(tz) - return ret, nil } diff --git a/app/assets/index.js b/app/assets/index.js index 5bc6ef8..78e4914 100644 --- a/app/assets/index.js +++ b/app/assets/index.js @@ -1,7 +1,3 @@ -document.body.setAttribute("hx-vals", JSON.stringify({ - tz: new Date().getTimezoneOffset(), -})) - document.addEventListener('htmx:afterSwap', function (evt) { let fInput = document.getElementById("guess-input"); if (fInput !== null) { diff --git a/app/guess.go b/app/guess.go index 9532b63..285078c 100644 --- a/app/guess.go +++ b/app/guess.go @@ -22,9 +22,6 @@ const ( // ErrInvalidStartTime is emitted when the start time is malformed or invalid ErrInvalidStartTime = "Invalid start time provided with request" - // ErrInvalidTimezone is emitted when the timezone is malformed or invalid - ErrInvalidTimezone = "Invalid timezone provided with request" - // ErrEmptyGuess is emitted when the guess provided was empty ErrEmptyGuess = "Guess must not be empty" ) @@ -78,7 +75,7 @@ func guessHandlerReply(ctx context.Context, data bodyData, guess string) error { defer guessMutex.Unlock() // Generate the word for the day - tm := data.Session.DateUser(data.TZ) + tm := data.Session.DateUser() word, err := wordStore.GetForDay(ctx, tm, data.Session.Mode) if err != nil { return err diff --git a/app/hint.go b/app/hint.go index 99083a2..eaa3362 100644 --- a/app/hint.go +++ b/app/hint.go @@ -27,7 +27,7 @@ func HintHandler(c *gin.Context) { // Generate the word for the day h := request.Session.Current() - word, err := wordStore.GetForDay(c, h.DateUser(request.TZ), request.Session.Mode) + word, err := wordStore.GetForDay(c, h.DateUser(), request.Session.Mode) if err != nil { c.HTML(http.StatusBadRequest, "error.gohtml", err.Error()) return diff --git a/app/seed.go b/app/seed.go index ee243eb..e2009a8 100644 --- a/app/seed.go +++ b/app/seed.go @@ -28,7 +28,7 @@ func SeedHandler(c *gin.Context) { // The answer is "website" // Yesterday's answer is "worst" "default": { - Start: time.Date(2022, 11, 8, 0, 0, 0, 0, time.UTC), + Start: time.Date(2022, 11, 7, 0, 0, 0, 0, time.UTC), Before: []string{}, After: []string{}, }, @@ -36,7 +36,7 @@ func SeedHandler(c *gin.Context) { // The answer is "gemshorn" // Yesterday's answer is "gabbroid" "hard": { - Start: time.Date(2022, 11, 8, 0, 0, 0, 0, time.UTC), + Start: time.Date(2022, 11, 7, 0, 0, 0, 0, time.UTC), Before: []string{}, After: []string{}, }, diff --git a/app/stats.go b/app/stats.go index 8c441ad..39bb71e 100644 --- a/app/stats.go +++ b/app/stats.go @@ -23,7 +23,7 @@ func YesterdayHandler(c *gin.Context) { } // Subtract one day for yesterday - dateUser := request.Session.DateUser(request.TZ).Add(time.Hour * -24) + dateUser := request.Session.DateUser().Add(time.Hour * -24) // Is it too early to reveal the word? y, m, d := time.Now().Date() @@ -57,7 +57,7 @@ func TodayHandler(c *gin.Context) { } // Generate the word for the day - word, err := wordStore.GetForDay(c, request.Session.DateUser(request.TZ), request.Session.Mode) + word, err := wordStore.GetForDay(c, request.Session.DateUser(), request.Session.Mode) if err != nil { slog.Warn("Unable to get day", "error", err) c.HTML(http.StatusBadRequest, "error.gohtml", err) diff --git a/app/templates/guesser.gohtml b/app/templates/guesser.gohtml index 58cabd5..ef4e6e5 100644 --- a/app/templates/guesser.gohtml +++ b/app/templates/guesser.gohtml @@ -54,10 +54,13 @@ {{ end }} {{ else }} -

+

🎉 You guessed "{{ .Answer }}" correctly with - {{ .GuessCount }} tries. Come back tomorrow for another! -

+ {{ .GuessCount }} tries. + {{ if .Stale }} A new word is available! + {{ else }} Come back in {{ .RemainingTime }} for another! + {{ end}} +
{{ end }} diff --git a/internal/sessions/session.go b/internal/sessions/session.go index 07b4b77..11abcc1 100644 --- a/internal/sessions/session.go +++ b/internal/sessions/session.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log/slog" + "math" "time" "github.com/gin-contrib/sessions" @@ -77,9 +78,9 @@ func (s *Session) Current() *SessionMode { return s.History[s.Mode] } -func (s *Session) DateUser(tz int) time.Time { +func (s *Session) DateUser() time.Time { m := s.Current() - return convertUTCToUser(m.Start, tz) + return m.Start } func (s *Session) Save() error { @@ -126,19 +127,43 @@ func (m *SessionMode) CommonGuessPrefix() string { return before[0:minWord] } -func (m *SessionMode) DateUser(tz int) time.Time { - return convertUTCToUser(m.Start, tz) +func (m *SessionMode) DateUser() time.Time { + return m.Start } -func (m *SessionMode) Stale() bool { +var remainingSeedTime time.Time + +func (m *SessionMode) RemainingTime() string { now := time.Now() - return m.Start.Month() != now.Month() || m.Start.Day() != now.Day() + if !remainingSeedTime.IsZero() { + now = remainingSeedTime + } + + tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) + + // The time between tomorrow (midnight) and right now + interval := tomorrow.Sub(now) + + // Number of hours, floating point precision + hours := math.Floor(interval.Hours()) + + // Subtract the truncated hours=>minutes from the total minutes + minutes := math.Floor(interval.Minutes() - (hours * 60)) + + // Truncate the hours & minutes, and print + hoursStr := "hours" + if hours >= 1 && hours < 2 { + hoursStr = "hour" + } + + minutesStr := "minutes" + if minutes >= 1 && minutes < 2 { + minutesStr = "minute" + } + return fmt.Sprintf("%0.f %s, %0.f %s", hours, hoursStr, minutes, minutesStr) } -// convertUTCToLocal will take a given time in UTC and convert it to a given user's timezone -// TZ for PDT (-7:00) is a positive 420, so SUBTRACT that from the unix timestamp -func convertUTCToUser(t time.Time, tz int) time.Time { - ret := t.In(time.FixedZone("User", tz*-1)) - ret = ret.Add(time.Minute * -1 * time.Duration(tz)) - return ret +func (m *SessionMode) Stale() bool { + now := time.Now() + return m.Start.Month() != now.Month() || m.Start.Day() != now.Day() } diff --git a/internal/sessions/session_test.go b/internal/sessions/session_test.go index 9cb24ce..d9eb430 100644 --- a/internal/sessions/session_test.go +++ b/internal/sessions/session_test.go @@ -95,3 +95,52 @@ func TestSessionMode_Stale(t *testing.T) { }) } } + +func TestSessionMode_RemainingTime(t *testing.T) { + tests := []struct { + name string + remainingSeedTime time.Time + want string + }{ + { + name: "24 hours", + remainingSeedTime: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + want: "24 hours, 0 minutes", + }, + { + name: "6 hours, 30 minutes", + remainingSeedTime: time.Date(2020, time.January, 1, 17, 30, 0, 0, time.UTC), + want: "6 hours, 30 minutes", + }, + { + name: "7 hours, 59 minutes", + remainingSeedTime: time.Date(2020, time.January, 1, 16, 1, 0, 0, time.UTC), + want: "7 hours, 59 minutes", + }, + { + name: "1 hour, 1 minute", + remainingSeedTime: time.Date(2020, time.January, 1, 22, 59, 0, 0, time.UTC), + want: "1 hour, 1 minute", + }, + { + name: "0 hours, 1 minute", + remainingSeedTime: time.Date(2020, time.January, 1, 23, 59, 0, 0, time.UTC), + want: "0 hours, 1 minute", + }, + { + name: "Next month", + remainingSeedTime: time.Date(2020, time.January, 31, 0, 0, 0, 0, time.UTC), + want: "24 hours, 0 minutes", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &SessionMode{} + remainingSeedTime = tt.remainingSeedTime + + if got := m.RemainingTime(); got != tt.want { + t.Errorf("SessionMode.RemainingTime() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/web/tests/guess.spec.ts b/web/tests/guess.spec.ts index 06afeb8..55816e7 100644 --- a/web/tests/guess.spec.ts +++ b/web/tests/guess.spec.ts @@ -7,7 +7,6 @@ const hardYesterday = "gabbroid" test.use({ locale: 'en-US', - timezoneId: 'America/Los_Angeles', }) test('guesses the default word', async ({ page }) => { @@ -53,7 +52,7 @@ test('guesses the default word', async ({ page }) => { // Correct guess await lWordEntry.type(defaultToday) await lWordEntry.press('Enter') - await expect(page.locator('#app')).toContainText('You guessed "' + defaultToday + '" correctly') + await expect(page.locator('#guesser').first()).toContainText('You guessed "' + defaultToday + '" correctly') await expect(lGuessBefore).toHaveText('apple ham') await expect(lGuessAfter).toHaveText('yam zoo') }) @@ -101,7 +100,7 @@ test('guesses the hard word', async ({ page }) => { // Correct guess await lWordEntry.type(hardToday) await lWordEntry.press('Enter') - await expect(page.locator('#app')).toContainText('You guessed "' + hardToday + '" correctly') + await expect(page.locator('#guesser').first()).toContainText('You guessed "' + hardToday + '" correctly') await expect(lGuessBefore).toHaveText('apple cherry') await expect(lGuessAfter).toHaveText('tree trunk') })