Yōten: A social tracker for your language learning journey built on the atproto.

fix: store UTC dates and attempt to use users timezone when possible

brookjeynes.dev 49c8f3cb 44840518

verified
+44 -30
+1 -1
internal/consumer/ingester.go
··· 448 448 reactionType := record.ReactionType 449 449 createdAt, err := time.Parse(time.RFC3339, record.CreatedAt) 450 450 if err != nil { 451 - createdAt = time.Now() 451 + createdAt = time.Now().UTC() 452 452 } 453 453 454 454 reaction, err := db.ReactionFromString(reactionType)
+1 -1
internal/server/handlers/activity.go
··· 84 84 } 85 85 newActivity.Did = user.Did 86 86 newActivity.Rkey = atproto.TID() 87 - newActivity.CreatedAt = time.Now() 87 + newActivity.CreatedAt = time.Now().UTC() 88 88 89 89 if err := db.ValidateActivity(newActivity); err != nil { 90 90 log.Println("invalid activity def:", err)
+1 -1
internal/server/handlers/comment.go
··· 82 82 ParentCommentUri: (*syntax.ATURI)(parentCommentUri), 83 83 StudySessionUri: syntax.ATURI(studySessionUri), 84 84 Body: commentBody, 85 - CreatedAt: time.Now(), 85 + CreatedAt: time.Now().UTC(), 86 86 } 87 87 88 88 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+1 -1
internal/server/handlers/follow.go
··· 55 55 56 56 switch r.Method { 57 57 case http.MethodPost: 58 - createdAt := time.Now().Format(time.RFC3339) 58 + createdAt := time.Now().UTC().Format(time.RFC3339) 59 59 rkey := atproto.TID() 60 60 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 61 61 Collection: yoten.GraphFollowNSID,
+1 -1
internal/server/handlers/reaction.go
··· 99 99 return 100 100 } 101 101 102 - createdAt := time.Now().Format(time.RFC3339) 102 + createdAt := time.Now().UTC().Format(time.RFC3339) 103 103 rkey := atproto.TID() 104 104 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 105 105 Collection: yoten.FeedReactionNSID,
+1 -1
internal/server/handlers/resource.go
··· 92 92 } 93 93 newResource.Did = user.Did 94 94 newResource.Rkey = atproto.TID() 95 - newResource.CreatedAt = time.Now() 95 + newResource.CreatedAt = time.Now().UTC() 96 96 97 97 if err := db.ValidateResource(newResource); err != nil { 98 98 log.Println("invalid resource definition:", err)
+12 -2
internal/server/handlers/study-session.go
··· 446 446 } 447 447 newStudySession.Did = user.Did 448 448 newStudySession.Rkey = atproto.TID() 449 - newStudySession.CreatedAt = time.Now() 449 + newStudySession.CreatedAt = time.Now().UTC() 450 + 451 + timezone := r.FormValue("timezone") 452 + if timezone == "" { 453 + timezone = "UTC" 454 + } 455 + 456 + loc, err := time.LoadLocation(timezone) 457 + if err != nil { 458 + loc = time.UTC 459 + } 450 460 451 461 if err := db.ValidateStudySession(newStudySession); err != nil { 452 462 log.Println("invalid study session:", err) ··· 532 542 Set("activity_id", newStudySession.Activity.ID). 533 543 Set("is_custom_activity", len(newStudySession.Activity.Did) > 0). 534 544 Set("description_provided", len(newStudySession.Description) > 0). 535 - Set("date_is_today", newStudySession.Date.Truncate(24*time.Hour).Equal(time.Now().Truncate(24*time.Hour))), 545 + Set("date_is_today", newStudySession.Date.Truncate(24*time.Hour).Equal(time.Now().UTC().In(loc).Truncate(24*time.Hour))), 536 546 }) 537 547 if err != nil { 538 548 log.Println("failed to enqueue posthog event:", err)
+9 -10
internal/server/views/edit-study-session.templ
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "time" 6 5 7 6 "yoten.app/internal/db" 8 7 "yoten.app/internal/server/views/layouts" ··· 278 277 </div> 279 278 </div> 280 279 </div> 281 - {{ 282 - var ( 283 - today = time.Now().Format("2006-01-02") 284 - oneYearAgo = time.Now().AddDate(-1, 0, 0).Format("2006-01-02") 285 - ) 286 - }} 287 - <div class="flex flex-col gap-2"> 280 + <div 281 + x-data="{ 282 + today: (d => d.toISOString().slice(0,10))(new Date()), 283 + oneYearAgo: (d => d.toISOString().slice(0,10))(new Date(new Date().setFullYear(new Date().getFullYear() - 1))) 284 + }" 285 + class="flex flex-col gap-2" 286 + > 288 287 <label for="date" class="font-medium text-sm">Date</label> 289 288 <input 290 289 type="date" ··· 292 291 id="date" 293 292 value={ params.StudySession.Date.Format("2006-01-02") } 294 293 class="input w-full" 295 - max={ today } 296 - min={ oneYearAgo } 294 + :min="oneYearAgo" 295 + :max="today" 297 296 /> 298 297 </div> 299 298 </div>
+9 -11
internal/server/views/new-study-session.templ
··· 1 1 package views 2 2 3 3 import ( 4 - "time" 5 - 6 4 "yoten.app/internal/server/views/layouts" 7 5 "yoten.app/internal/server/views/partials" 8 6 ) 9 7 10 8 templ NewStudySessionPage(params NewStudySessionPageParams) { 11 9 {{ 12 - var ( 13 - tomorrow = time.Now().AddDate(0, 0, 1).Format("2006-01-02") 14 - today = time.Now().Format("2006-01-02") 15 - oneYearAgo = time.Now().AddDate(-1, 0, 0).Format("2006-01-02") 16 - initialLangCode = "" 17 - ) 10 + var initialLangCode = "" 18 11 19 12 if len(params.Profile.Languages) == 1 { 20 13 initialLangCode = string(params.Profile.Languages[0].Code) ··· 29 22 class="card group" 30 23 hx-post="/session/new" 31 24 hx-swap="none" 25 + hx-vals="js:{timezone: Intl.DateTimeFormat().resolvedOptions().timeZone}" 32 26 hx-disabled-elt="#save-button,#cancel-button,#start-timer-button,#pause-timer-button,#reset-timer-button,#stop-timer-button" 33 27 > 34 28 <h1 class="text-3xl font-bold">Log New Study Session</h1> ··· 339 333 </div> 340 334 </div> 341 335 <div 336 + x-data="{ 337 + today: (d => d.toISOString().slice(0,10))(new Date()), 338 + oneYearAgo: (d => d.toISOString().slice(0,10))(new Date(new Date().setFullYear(new Date().getFullYear() - 1))) 339 + }" 342 340 x-show="mode === 'manual'" 343 341 x-transition 344 342 class="flex flex-col gap-2" ··· 349 347 name="date" 350 348 id="date" 351 349 class="input w-full" 352 - value={ today } 353 - max={ tomorrow } 354 - min={ oneYearAgo } 350 + x-model="today" 351 + :min="oneYearAgo" 352 + :max="today" 355 353 /> 356 354 </div> 357 355 </div>
+7
internal/server/views/partials/partials.go
··· 164 164 } 165 165 166 166 func NewHeatmap(data db.HeatmapData) templ.Component { 167 + // TODO: Do this calculation in users local time. 168 + // loc, err := time.LoadLocation(userTimezone) 169 + // if err != nil { 170 + // loc = time.UTC // Fallback to UTC if unable to parse users timezone. 171 + // } 172 + // today := time.Now().In(loc) 173 + 167 174 today := time.Now() 168 175 oneYearAgo := today.AddDate(-1, 0, 0) 169 176 startOffset := int(oneYearAgo.Weekday())
+1 -1
internal/server/views/stats.templ
··· 46 46 </div> 47 47 </div> 48 48 </div> 49 - <div class=""> 49 + <div> 50 50 @partials.NewHeatmap(params.HeatmapData) 51 51 </div> 52 52 <div x-data="{ tab: 'week' }" class="card">