Skip to content

Commit bdd5ed9

Browse files
committed
Display datetime in local timezone
1 parent 14c5b54 commit bdd5ed9

File tree

9 files changed

+350
-246
lines changed

9 files changed

+350
-246
lines changed

app/intl/intl.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ var SupportedLangs = []language.Tag{
2020
language.French,
2121
}
2222

23+
var DefaultTimezone *time.Location = time.UTC
24+
2325
const LocalizeErrorMessage string = "<failed to localize>"
2426

2527
type Intl struct {
26-
CurrentLang language.Tag
27-
localizer *i18n.Localizer
28-
logger *slog.Logger
28+
CurrentLang language.Tag
29+
CurrentTimezone *time.Location
30+
localizer *i18n.Localizer
31+
logger *slog.Logger
2932
}
3033

3134
func NewBundle(localesFS embed.FS) (*i18n.Bundle, error) {
@@ -34,10 +37,6 @@ func NewBundle(localesFS embed.FS) (*i18n.Bundle, error) {
3437

3538
for _, tag := range SupportedLangs {
3639
path := fmt.Sprintf("locales/active.%s.toml", tag.String())
37-
// debug: check if path exists in localFS
38-
// if _, err := localeFS.Open(path); err != nil {
39-
// return nil, fmt.Errorf("loading i18n file %q: %w", path, err)
40-
// }
4140
if _, err := b.LoadMessageFileFS(localesFS, path); err != nil {
4241
return nil, fmt.Errorf("loading i18n file %q: %w", path, err)
4342
}
@@ -46,13 +45,14 @@ func NewBundle(localesFS embed.FS) (*i18n.Bundle, error) {
4645
return b, nil
4746
}
4847

49-
func New(logger *slog.Logger, i18nBundle *i18n.Bundle, lang language.Tag) *Intl {
48+
func New(logger *slog.Logger, i18nBundle *i18n.Bundle, lang language.Tag, tz *time.Location) *Intl {
5049
localizer := i18n.NewLocalizer(i18nBundle, lang.String())
5150

5251
return &Intl{
53-
CurrentLang: lang,
54-
localizer: localizer,
55-
logger: logger,
52+
CurrentLang: lang,
53+
CurrentTimezone: tz,
54+
localizer: localizer,
55+
logger: logger,
5656
}
5757
}
5858

@@ -91,6 +91,8 @@ func mondayLocale(tag language.Tag) monday.Locale {
9191
}
9292

9393
func (i *Intl) FormatDate(t time.Time) string {
94+
t = i.TimeInUserTz(t)
95+
9496
locale := mondayLocale(i.CurrentLang)
9597
format, ok := monday.MediumFormatsByLocale[locale]
9698
if !ok {
@@ -100,10 +102,20 @@ func (i *Intl) FormatDate(t time.Time) string {
100102
}
101103

102104
func (i *Intl) FormatTime(t time.Time) string {
105+
t = i.TimeInUserTz(t)
106+
103107
switch i.CurrentLang {
104108
case language.French:
105109
return t.Format("15:04 MST")
106110
default:
107111
return t.Format("3:04 PM MST")
108112
}
109113
}
114+
115+
func (i *Intl) TimeInUserTz(t time.Time) time.Time {
116+
if i.CurrentTimezone == DefaultTimezone {
117+
return t
118+
}
119+
120+
return t.In(i.CurrentTimezone)
121+
}

app/rctx/intl.context.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"log/slog"
66
"net/http"
77
"slices"
8+
"time"
89

910
"github.com/nicksnyder/go-i18n/v2/i18n"
1011
"github.com/nicolashery/simply-shared-notes/app/intl"
@@ -14,17 +15,19 @@ import (
1415
func IntlCtxMiddleware(logger *slog.Logger, i18nBundle *i18n.Bundle) func(http.Handler) http.Handler {
1516
return func(next http.Handler) http.Handler {
1617
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17-
lang := selectLanguage(r.Header.Get("Accept-Language"))
18+
lang := selectLanguage(r)
19+
tz := selectTimezone(r)
1820

19-
intl := intl.New(logger, i18nBundle, lang)
21+
intl := intl.New(logger, i18nBundle, lang, tz)
2022

2123
ctx := context.WithValue(r.Context(), intlContextKey, intl)
2224
next.ServeHTTP(w, r.WithContext(ctx))
2325
})
2426
}
2527
}
2628

27-
func selectLanguage(acceptLang string) language.Tag {
29+
func selectLanguage(r *http.Request) language.Tag {
30+
acceptLang := r.Header.Get("Accept-Language")
2831
if acceptLang == "" {
2932
return intl.DefaultLang
3033
}
@@ -44,6 +47,20 @@ func selectLanguage(acceptLang string) language.Tag {
4447
return intl.DefaultLang
4548
}
4649

50+
func selectTimezone(r *http.Request) *time.Location {
51+
tz := intl.DefaultTimezone
52+
53+
c, err := r.Cookie("tz")
54+
if err == nil && c.Value != "" {
55+
l, err := time.LoadLocation(c.Value)
56+
if err == nil {
57+
tz = l
58+
}
59+
}
60+
61+
return tz
62+
}
63+
4764
func GetIntl(ctx context.Context) *intl.Intl {
4865
intl, ok := ctx.Value(intlContextKey).(*intl.Intl)
4966
if !ok {

app/views/components/created_updated.templ

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ templ CreatedAtBy(
3131
<span class="opacity-50">{ "Created by " }</span>
3232
<span class="opacity-75">{ helpers.DisplayMemberName(createdBy, membersByID) }</span>
3333
<span class="opacity-50">{ " on " } </span>
34-
<span class="opacity-75">{ intl.FormatDate(createdAt) }</span>
35-
<span class="opacity-50">{ " at " } </span>
36-
<span class="opacity-75">{ intl.FormatTime(createdAt) }</span>
34+
<time datetime={ createdAt.Format(time.RFC3339) }>
35+
<span class="opacity-75">{ intl.FormatDate(createdAt) }</span>
36+
<span class="opacity-50">{ " at " } </span>
37+
<span class="opacity-75">{ intl.FormatTime(createdAt) }</span>
38+
</time>
3739
</div>
3840
}
3941

@@ -47,8 +49,10 @@ templ UpdatedAtBy(
4749
<span class="opacity-50">{ " Updated by " }</span>
4850
<span class="opacity-75">{ helpers.DisplayMemberName(updatedBy, membersByID) }</span>
4951
<span class="opacity-50">{ " on " }</span>
50-
<span class="opacity-75">{ intl.FormatDate(updatedAt) }</span>
51-
<span class="opacity-50">{ " at " }</span>
52-
<span class="opacity-75">{ intl.FormatTime(updatedAt) }</span>
52+
<time datetime={ updatedAt.Format(time.RFC3339) }>
53+
<span class="opacity-75">{ intl.FormatDate(updatedAt) }</span>
54+
<span class="opacity-50">{ " at " }</span>
55+
<span class="opacity-75">{ intl.FormatTime(updatedAt) }</span>
56+
</time>
5357
</div>
5458
}

app/views/components/created_updated_templ.go

Lines changed: 68 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/views/pages/activity_list.page.templ

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/nicolashery/simply-shared-notes/app/rctx"
77
"github.com/nicolashery/simply-shared-notes/app/views/helpers"
88
"github.com/nicolashery/simply-shared-notes/app/views/layouts"
9+
"time"
910
)
1011

1112
templ ActivityList(entries []db.Activity, membersByID map[int64]db.Member, notesByID map[int64]db.Note) {
@@ -21,9 +22,11 @@ templ ActivityList(entries []db.Activity, membersByID map[int64]db.Member, notes
2122
class="text-xs opacity-50 link link-hover"
2223
href={ templ.URL(fmt.Sprintf("/s/%s/activity/%s", access.Token, activity.PublicID)) }
2324
>
24-
{ intl.FormatDate(activity.CreatedAt) }
25-
{ " at " }
26-
{ intl.FormatTime(activity.CreatedAt) }
25+
<time datetime={ activity.CreatedAt.Format(time.RFC3339) }>
26+
{ intl.FormatDate(activity.CreatedAt) }
27+
{ " at " }
28+
{ intl.FormatTime(activity.CreatedAt) }
29+
</time>
2730
</a>
2831
<div class="text-xs">
2932
<span class="opacity-75">{ helpers.DisplayMemberName(activity.MemberID, membersByID) }</span>

0 commit comments

Comments
 (0)