Skip to content

Commit

Permalink
Remove Timezones (#164)
Browse files Browse the repository at this point in the history
* Remove timezone tracking

* Remove constant

* Reduce seed day by one

* More specific locator in spec

* Add remaining time estimate

* Human readable remainingtime

* Disambiguate tests
  • Loading branch information
taiidani authored Sep 23, 2023
1 parent 97f8e63 commit c44b398
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 44 deletions.
13 changes: 0 additions & 13 deletions app/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
4 changes: 0 additions & 4 deletions app/assets/index.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
5 changes: 1 addition & 4 deletions app/guess.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ 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{},
},

// 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{},
},
Expand Down
4 changes: 2 additions & 2 deletions app/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions app/templates/guesser.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@
{{ end }}

{{ else }}
<p>
<form method="POST" action="/reset" style="display: inline">
🎉 You guessed "{{ .Answer }}" correctly with
{{ .GuessCount }} tries. Come back tomorrow for another!
</p>
{{ .GuessCount }} tries.
{{ if .Stale }} A new word is available! <button type="submit" class="btn btn-success">Start guessing</button>
{{ else }} Come back in {{ .RemainingTime }} for another!
{{ end}}
</form>
{{ end }}
</section>
</div>
Expand Down
49 changes: 37 additions & 12 deletions internal/sessions/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log/slog"
"math"
"time"

"github.com/gin-contrib/sessions"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
}
49 changes: 49 additions & 0 deletions internal/sessions/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
5 changes: 2 additions & 3 deletions web/tests/guess.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const hardYesterday = "gabbroid"

test.use({
locale: 'en-US',
timezoneId: 'America/Los_Angeles',
})

test('guesses the default word', async ({ page }) => {
Expand Down Expand Up @@ -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')
})
Expand Down Expand Up @@ -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')
})

0 comments on commit c44b398

Please sign in to comment.