diff --git a/src/lib/components/crew/InvitationsModal.svelte b/src/lib/components/crew/InvitationsModal.svelte index 687d9fa6..743da9da 100644 --- a/src/lib/components/crew/InvitationsModal.svelte +++ b/src/lib/components/crew/InvitationsModal.svelte @@ -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() diff --git a/src/lib/components/crew/MemberRow.svelte b/src/lib/components/crew/MemberRow.svelte index 8c641b9e..7911093b 100644 --- a/src/lib/components/crew/MemberRow.svelte +++ b/src/lib/components/crew/MemberRow.svelte @@ -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) && diff --git a/src/lib/components/crew/PhantomRow.svelte b/src/lib/components/crew/PhantomRow.svelte index f12d52a8..ad35a368 100644 --- a/src/lib/components/crew/PhantomRow.svelte +++ b/src/lib/components/crew/PhantomRow.svelte @@ -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' diff --git a/src/lib/utils/date.ts b/src/lib/utils/date.ts new file mode 100644 index 00000000..24d3229e --- /dev/null +++ b/src/lib/utils/date.ts @@ -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' + }) +} diff --git a/src/routes/(app)/crew/+page.svelte b/src/routes/(app)/crew/+page.svelte index 9cf63f95..992c432b 100644 --- a/src/routes/(app)/crew/+page.svelte +++ b/src/routes/(app)/crew/+page.svelte @@ -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 @@ - {formatDate(event.startDate)} – {formatDate(event.endDate)} + {formatDateJST(event.startDate)} – {formatDateJST(event.endDate)} {#if event.crewTotalScore !== undefined} diff --git a/src/routes/(app)/crew/events/[eventNumber]/+page.svelte b/src/routes/(app)/crew/events/[eventNumber]/+page.svelte index 8aef0b34..c14d676f 100644 --- a/src/routes/(app)/crew/events/[eventNumber]/+page.svelte +++ b/src/routes/(app)/crew/events/[eventNumber]/+page.svelte @@ -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'} - {formatDate(gwEvent.startDate)} – {formatDate(gwEvent.endDate)} + {formatDateJST(gwEvent.startDate)} – {formatDateJST(gwEvent.endDate)}
diff --git a/src/routes/(app)/crew/join/+page.svelte b/src/routes/(app)/crew/join/+page.svelte index 43b355a6..1fc5f4bf 100644 --- a/src/routes/(app)/crew/join/+page.svelte +++ b/src/routes/(app)/crew/join/+page.svelte @@ -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() diff --git a/src/routes/(app)/crew/members/+page.svelte b/src/routes/(app)/crew/members/+page.svelte index 1d07f0f9..5ac9c811 100644 --- a/src/routes/(app)/crew/members/+page.svelte +++ b/src/routes/(app)/crew/members/+page.svelte @@ -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() diff --git a/src/routes/(app)/database/gw-events/+page.svelte b/src/routes/(app)/database/gw-events/+page.svelte index a0503be3..3407e150 100644 --- a/src/routes/(app)/database/gw-events/+page.svelte +++ b/src/routes/(app)/database/gw-events/+page.svelte @@ -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 @@ - {formatDate(event.startDate)} - {formatDate(event.endDate)} + {formatDateJST(event.startDate)} - {formatDateJST(event.endDate)} diff --git a/src/routes/(app)/database/gw-events/[id]/+page.svelte b/src/routes/(app)/database/gw-events/[id]/+page.svelte index c459ee4e..8e18f723 100644 --- a/src/routes/(app)/database/gw-events/[id]/+page.svelte +++ b/src/routes/(app)/database/gw-events/[id]/+page.svelte @@ -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'} - - + + {#if event.createdAt} - + {#if event.updatedAt} - + {/if} {/if}