fix date timezone issues: use JST for GW events, local time for user dates

This commit is contained in:
Justin Edmund 2025-12-18 00:49:23 -08:00
parent 472672ebc8
commit 77cb109dd2
10 changed files with 65 additions and 86 deletions

View file

@ -13,6 +13,7 @@
import ModalBody from '$lib/components/ui/ModalBody.svelte'
import Button from '$lib/components/ui/Button.svelte'
import Icon from '$lib/components/Icon.svelte'
import { formatDate } from '$lib/utils/date'
import type { CrewInvitation, PhantomPlayer } from '$lib/types/api/crew'
interface Props {
@ -98,15 +99,6 @@
}
}
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Check if invitation is expired
function isExpired(expiresAt: string): boolean {
return new Date(expiresAt) < new Date()

View file

@ -6,6 +6,7 @@
import DropdownMenu from '$lib/components/ui/DropdownMenu.svelte'
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
import { crewStore } from '$lib/stores/crew.store.svelte'
import { formatDate } from '$lib/utils/date'
import type { CrewMembership } from '$lib/types/api/crew'
interface Props {
@ -40,14 +41,6 @@
}
}
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
const canShowOfficerActions = $derived(
crewStore.isOfficer &&
crewStore.canActOnMember(member.role) &&

View file

@ -5,6 +5,7 @@
import DropdownMenu from '$lib/components/ui/DropdownMenu.svelte'
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
import { crewStore } from '$lib/stores/crew.store.svelte'
import { formatDate } from '$lib/utils/date'
import type { PhantomPlayer } from '$lib/types/api/crew'
interface Props {
@ -20,14 +21,6 @@
const { phantom, currentUserId, onEdit, onDelete, onAssign, onAccept, onDecline }: Props =
$props()
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Status badge type
type ClaimStatus = 'unclaimed' | 'pending' | 'claimed'

49
src/lib/utils/date.ts Normal file
View file

@ -0,0 +1,49 @@
/**
* Format a date string in JST (Japan Standard Time).
* Use this for game-related dates like GW events, as Granblue Fantasy uses JST.
*/
export function formatDateJST(
dateString: string,
options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric'
}
): string {
return new Date(dateString).toLocaleDateString(undefined, {
...options,
timeZone: 'Asia/Tokyo'
})
}
/**
* Format a date string in local time.
* Use this for user-related dates like join dates, invitation expiries, etc.
*
* For date-only strings (YYYY-MM-DD), appends T00:00:00 to parse as local midnight
* instead of UTC midnight, preventing the date from shifting.
*/
export function formatDate(
dateString: string,
options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric'
}
): string {
// If it's a date-only string (YYYY-MM-DD), parse as local time
const dateToFormat =
dateString.length === 10 ? new Date(dateString + 'T00:00:00') : new Date(dateString)
return dateToFormat.toLocaleDateString(undefined, options)
}
/**
* Format a date string with long month format in JST (e.g., "June 21, 2025")
*/
export function formatDateLongJST(dateString: string): string {
return formatDateJST(dateString, {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}

View file

@ -14,6 +14,7 @@
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
import Input from '$lib/components/ui/Input.svelte'
import CrewHeader from '$lib/components/crew/CrewHeader.svelte'
import { formatDateJST } from '$lib/utils/date'
import type { PageData } from './$types'
interface Props {
@ -159,15 +160,6 @@
settingsError = null
}
// Helper for formatting dates
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Helper for formatting scores with commas
function formatScore(score: number): string {
return score.toLocaleString()
@ -296,7 +288,7 @@
</span>
</div>
<span class="event-dates">
{formatDate(event.startDate)} {formatDate(event.endDate)}
{formatDateJST(event.startDate)} {formatDateJST(event.endDate)}
</span>
<span class="event-score">
{#if event.crewTotalScore !== undefined}

View file

@ -21,6 +21,7 @@
import EditCrewScoreModal from '$lib/components/crew/EditCrewScoreModal.svelte'
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
import { formatDateJST } from '$lib/utils/date'
import {
GW_ROUND_LABELS,
type GwRound,
@ -159,15 +160,6 @@
return parseInt(value.replace(/,/g, ''), 10)
}
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Navigate back
function handleBack() {
goto('/crew')
@ -406,7 +398,7 @@
{elementLabels[gwEvent.element] ?? 'Unknown'}
</span>
<span class="event-dates">
{formatDate(gwEvent.startDate)} {formatDate(gwEvent.endDate)}
{formatDateJST(gwEvent.startDate)} {formatDateJST(gwEvent.endDate)}
</span>
</div>
<div class="tab-control">

View file

@ -8,6 +8,7 @@
import { useAcceptInvitation, useRejectInvitation } from '$lib/api/mutations/crew.mutations'
import { crewStore } from '$lib/stores/crew.store.svelte'
import Button from '$lib/components/ui/Button.svelte'
import { formatDate } from '$lib/utils/date'
import type { PageData } from './$types'
interface Props {
@ -61,15 +62,6 @@
}
}
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Check if invitation is expired
function isExpired(expiresAt: string): boolean {
return new Date(expiresAt) < new Date()

View file

@ -29,6 +29,7 @@
import AssignPhantomModal from '$lib/components/crew/AssignPhantomModal.svelte'
import ConfirmClaimModal from '$lib/components/crew/ConfirmClaimModal.svelte'
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
import { formatDate } from '$lib/utils/date'
import type {
MemberFilter,
CrewMembership,
@ -303,15 +304,6 @@
}
}
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Check if invitation is expired
function isInvitationExpired(expiresAt: string): boolean {
return new Date(expiresAt) < new Date()

View file

@ -7,6 +7,7 @@
import { createQuery } from '@tanstack/svelte-query'
import { gwAdapter } from '$lib/api/adapters/gw.adapter'
import Button from '$lib/components/ui/Button.svelte'
import { formatDateJST } from '$lib/utils/date'
import type { GwEvent } from '$lib/types/api/gw'
import type { PageData } from './$types'
@ -61,15 +62,6 @@
)
})
// Format date for display
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
// Navigate to event detail/edit
function handleRowClick(event: GwEvent) {
goto(`/database/gw-events/${event.id}`)
@ -124,7 +116,7 @@
</td>
<td class="col-dates">
<span class="dates">
{formatDate(event.startDate)} - {formatDate(event.endDate)}
{formatDateJST(event.startDate)} - {formatDateJST(event.endDate)}
</span>
</td>
</tr>

View file

@ -9,6 +9,7 @@
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import SidebarHeader from '$lib/components/ui/SidebarHeader.svelte'
import { formatDateJST, formatDateLongJST } from '$lib/utils/date'
import type { PageData } from './$types'
interface Props {
@ -52,15 +53,6 @@
6: 'light'
}
// Format date for display
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
// Navigate to edit
function handleEdit() {
goto(`/database/gw-events/${eventId}/edit`)
@ -102,15 +94,15 @@
{elementLabels[event.element] ?? 'Unknown'}
</span>
</DetailItem>
<DetailItem label="Start Date" value={formatDate(event.startDate)} />
<DetailItem label="End Date" value={formatDate(event.endDate)} />
<DetailItem label="Start Date" value={formatDateLongJST(event.startDate)} />
<DetailItem label="End Date" value={formatDateLongJST(event.endDate)} />
</DetailsContainer>
{#if event.createdAt}
<DetailsContainer title="Metadata">
<DetailItem label="Created" value={formatDate(event.createdAt)} />
<DetailItem label="Created" value={formatDateJST(event.createdAt)} />
{#if event.updatedAt}
<DetailItem label="Updated" value={formatDate(event.updatedAt)} />
<DetailItem label="Updated" value={formatDateJST(event.updatedAt)} />
{/if}
</DetailsContainer>
{/if}