fix date timezone issues: use JST for GW events, local time for user dates
This commit is contained in:
parent
472672ebc8
commit
77cb109dd2
10 changed files with 65 additions and 86 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) &&
|
||||
|
|
|
|||
|
|
@ -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
49
src/lib/utils/date.ts
Normal 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'
|
||||
})
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue