From 17720814957cda5430e43964da36f3f5d05991e6 Mon Sep 17 00:00:00 2001 From: aarbit Date: Mon, 18 May 2026 20:59:55 -0500 Subject: [PATCH] Fixes time zone based stuff --- src/lib/components/DateTimePicker.svelte | 7 ++++--- src/lib/utils.ts | 23 +++++++++++++++++++++++ src/routes/admin/AdminHome.svelte | 12 +----------- src/routes/admin/HuntManage.svelte | 8 +------- src/routes/admin/PhotoReview.svelte | 5 +++-- src/routes/hunter/HuntList.svelte | 20 ++++---------------- src/routes/hunter/HuntLobby.svelte | 10 ++-------- 7 files changed, 38 insertions(+), 47 deletions(-) create mode 100644 src/lib/utils.ts diff --git a/src/lib/components/DateTimePicker.svelte b/src/lib/components/DateTimePicker.svelte index c806ddb..79835fe 100644 --- a/src/lib/components/DateTimePicker.svelte +++ b/src/lib/components/DateTimePicker.svelte @@ -29,7 +29,9 @@ const seed = untrack(() => { if (initialValue) { - const d = new Date(initialValue) + // Append Z if no timezone indicator is present so it's always parsed as UTC + const normalized = /Z$|[+-]\d{2}:?\d{2}$/.test(initialValue) ? initialValue : initialValue + 'Z' + const d = new Date(normalized) return { month: d.getMonth(), day: d.getDate(), year: d.getFullYear(), timeIndex: d.getHours() * 4 + Math.round(d.getMinutes() / 15) } } const today = new Date() @@ -48,10 +50,9 @@ }) $effect(() => { - const pad = (n: number) => n.toString().padStart(2, '0') const hour = Math.floor(timeIndex / 4) const minute = (timeIndex % 4) * 15 - value = new Date(`${year}-${pad(month + 1)}-${pad(day)}T${pad(hour)}:${pad(minute)}`).toISOString() + value = new Date(year, month, day, hour, minute).toISOString() }) diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..7ed99ff --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,23 @@ +import type { HuntResponse } from './api/types' + +// Ensures API datetime strings are always parsed as UTC, even if the Z suffix is missing. +export function parseUTC(iso: string): Date { + const normalized = /Z$|[+-]\d{2}:?\d{2}$/.test(iso) ? iso : iso + 'Z' + return new Date(normalized) +} + +export function huntStatus(hunt: HuntResponse): 'ONGOING' | 'UNSTARTED' | 'CLOSED' { + if (hunt.isTerminated) return 'CLOSED' + const now = Date.now() + if (now < parseUTC(hunt.startDateTime).getTime()) return 'UNSTARTED' + if (now > parseUTC(hunt.endDateTime).getTime()) return 'CLOSED' + return 'ONGOING' +} + +export function formatDateTime(iso: string): string { + return parseUTC(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) +} + +export function formatDate(iso: string): string { + return parseUTC(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) +} diff --git a/src/routes/admin/AdminHome.svelte b/src/routes/admin/AdminHome.svelte index 7612248..2e84d45 100644 --- a/src/routes/admin/AdminHome.svelte +++ b/src/routes/admin/AdminHome.svelte @@ -1,6 +1,7 @@
diff --git a/src/routes/admin/HuntManage.svelte b/src/routes/admin/HuntManage.svelte index 73cc4be..0be607f 100644 --- a/src/routes/admin/HuntManage.svelte +++ b/src/routes/admin/HuntManage.svelte @@ -1,6 +1,7 @@
diff --git a/src/routes/admin/PhotoReview.svelte b/src/routes/admin/PhotoReview.svelte index a3ec6b3..522a5ed 100644 --- a/src/routes/admin/PhotoReview.svelte +++ b/src/routes/admin/PhotoReview.svelte @@ -2,6 +2,7 @@ import {push} from 'svelte-spa-router' import {auth} from '../../lib/stores/auth.svelte' import {apiGetHunt, apiGetItemPhotos, apiGetItems, apiListTeams, apiReviewPhoto} from '../../lib/api/index' + import {parseUTC} from '../../lib/utils' import type {HuntResponse, ItemResponse, PhotoResponse, TeamResponse} from '../../lib/api/types' import StatusBadge from '../../lib/components/StatusBadge.svelte' import AuthImage from '../../lib/components/AuthImage.svelte' @@ -83,7 +84,7 @@

{photo.hunterName} · {photo.teamName}

-

{new Date(photo.photoUploadDateTime).toLocaleString()}

+

{parseUTC(photo.photoUploadDateTime).toLocaleString()}

@@ -181,7 +182,7 @@

{photo.hunterName} · {photo.teamName}

- {new Date(photo.photoUploadDateTime).toLocaleString()} + {parseUTC(photo.photoUploadDateTime).toLocaleString()}

diff --git a/src/routes/hunter/HuntList.svelte b/src/routes/hunter/HuntList.svelte index 00a6793..ed32eb8 100644 --- a/src/routes/hunter/HuntList.svelte +++ b/src/routes/hunter/HuntList.svelte @@ -1,6 +1,7 @@
@@ -184,7 +172,7 @@

- {formatDate(hunt.startDateTime)} – {formatDate(hunt.endDateTime)} + {formatDateTime(hunt.startDateTime)} – {formatDateTime(hunt.endDateTime)}

@@ -212,7 +200,7 @@

- {formatDate(hunt.startDateTime)} – {formatDate(hunt.endDateTime)} + {formatDateTime(hunt.startDateTime)} – {formatDateTime(hunt.endDateTime)}

{#if myTeam}

Team: {myTeam.name}

@@ -236,7 +224,7 @@

{sheetHunt.title}

-

{formatDate(sheetHunt.startDateTime)} – {formatDate(sheetHunt.endDateTime)}

+

{formatDateTime(sheetHunt.startDateTime)} – {formatDateTime(sheetHunt.endDateTime)}

diff --git a/src/routes/hunter/HuntLobby.svelte b/src/routes/hunter/HuntLobby.svelte index e197767..45f3dba 100644 --- a/src/routes/hunter/HuntLobby.svelte +++ b/src/routes/hunter/HuntLobby.svelte @@ -2,6 +2,7 @@ import {push} from 'svelte-spa-router' import {fade} from 'svelte/transition' import {auth} from '../../lib/stores/auth.svelte' + import {huntStatus, parseUTC} from '../../lib/utils' import { apiCreateTeam, apiGetHunt, @@ -163,13 +164,6 @@ } } - function huntStatus(h: HuntResponse): 'ONGOING' | 'UNSTARTED' | 'CLOSED' { - if (h.isTerminated) return 'CLOSED' - const now = Date.now() - if (now < new Date(h.startDateTime).getTime()) return 'UNSTARTED' - if (now > new Date(h.endDateTime).getTime()) return 'CLOSED' - return 'ONGOING' - } const approved = $derived(Object.values(itemStatuses).filter(s => s.itemFoundStatus === 'APPROVED').length) const submitted = $derived(Object.values(itemStatuses).filter(s => s.itemFoundStatus === 'SUBMITTED').length) @@ -414,7 +408,7 @@

{photo.hunterName}

-

{new Date(photo.photoUploadDateTime).toLocaleString()}

+

{parseUTC(photo.photoUploadDateTime).toLocaleString()}