TanStack Start v1에서 시간대를 처리하는 방법
사용자의 현지 시간대로 시간 표시하기
문제
애플리케이션이 시간을 표시할 때, 사용자의 위치에 따라 다르게 보이는 단일 시점을 나타냅니다. 서버가 자체 시간대나 UTC로 "8:00 PM"을 렌더링하면, 다른 시간대에 있는 사용자들은 잘못된 현지 시간을 보게 되어 이벤트가 실제로 언제 발생하는지에 대한 혼란을 초래합니다. 사용자들은 시간이 자동으로 자신의 현지 시간대를 반영하기를 기대하지만, 적절한 처리가 없으면 서버가 렌더링 중에 사용한 시간대로 시간을 보게 됩니다. 이러한 불일치로 인해 사용자들은 약속을 놓치고, 일정을 오해하며, 애플리케이션의 신뢰성에 대한 신뢰를 잃게 됩니다.
해결책
날짜 형식 지정 메서드에 timeZone 옵션을 전달하여 날짜가 해석되고 표시되는 방식을 제어합니다. formatDate 함수는 시간대 구성을 포함하는 Intl.DateTimeFormatOptions를 허용합니다. 타임스탬프를 ISO 8601 문자열이나 유닉스 타임스탬프와 같은 범용 형식으로 저장한 다음, 브라우저의 시간대가 자동으로 제공되는 클라이언트에서 형식을 지정합니다. Intl.DateTimeFormat API는 timeZone 옵션이 지정되지 않은 경우 사용자의 기본 시간대를 사용하여 각 사용자의 위치에 맞게 시간이 올바르게 표시되도록 합니다.
단계
1. 시간대를 인식하는 날짜 형식 지정 컴포넌트 만들기
useIntl 훅을 사용하여 intl 객체와 그 formatDate 메서드에 접근합니다. 이 컴포넌트는 ISO 타임스탬프를 받아 사용자의 현지 시간대에 맞게 형식을 지정합니다.
import { useIntl } from "react-intl";
interface LocalTimeProps {
timestamp: string;
showDate?: boolean;
showTime?: boolean;
}
export function LocalTime({
timestamp,
showDate = true,
showTime = true,
}: LocalTimeProps) {
const intl = useIntl();
const date = new Date(timestamp);
const formatted = intl.formatDate(date, {
...(showDate && {
year: "numeric",
month: "short",
day: "numeric",
}),
...(showTime && {
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
}),
});
return <time dateTime={timestamp}>{formatted}</time>;
}
FormattedDate 컴포넌트와 formatDate 메서드는 DateTimeFormatOptions와 함께 Intl.DateTimeFormat API를 사용합니다. 브라우저는 명시적인 timeZone이 제공되지 않을 때 자동으로 사용자의 시간대를 적용하여 UTC 타임스탬프를 현지 시간으로 변환합니다.
2. 이벤트 시간을 표시하기 위한 컴포넌트 사용
타임스탬프를 표시해야 하는 곳마다 LocalTime 컴포넌트를 가져와 사용하세요. 이 컴포넌트는 각 사용자의 브라우저 설정을 기반으로 시간대 변환을 자동으로 처리합니다.
import { createFileRoute } from "@tanstack/react-router";
import { LocalTime } from "~/components/LocalTime";
export const Route = createFileRoute("/events/$eventId")({
component: EventDetail,
});
function EventDetail() {
const event = Route.useLoaderData();
return (
<div>
<h1>{event.title}</h1>
<p>
시작: <LocalTime timestamp={event.startTime} />
</p>
<p>
종료: <LocalTime timestamp={event.endTime} />
</p>
</div>
);
}
각 사용자는 서버에서 받은 ISO 타임스탬프가 자신의 로컬 시간대로 변환된 이벤트 시간을 볼 수 있습니다. 이 타임스탬프는 사용자의 로케일과 시간대 환경설정에 따라 파싱되고 포맷됩니다.
3. 명시적 시간대 표시와 함께 시간 포맷팅하기
사용자가 시간이 어떤 시간대를 나타내는지 알아야 할 때는 포맷팅 옵션에 시간대 이름을 포함하세요.
import { useIntl } from "react-intl";
interface ExplicitTimezoneProps {
timestamp: string;
timezone?: string;
}
export function ExplicitTimezone({
timestamp,
timezone,
}: ExplicitTimezoneProps) {
const intl = useIntl();
const date = new Date(timestamp);
const formatted = intl.formatDate(date, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: timezone,
timeZoneName: "long",
});
return <time dateTime={timestamp}>{formatted}</time>;
}
timeZone 옵션을 지정하면 포맷터가 해당 특정 시간대로 시간을 표시하도록 강제합니다. timezone이 정의되지 않은 경우, 브라우저는 사용자의 로컬 시간대를 사용합니다. 이는 사용자가 어디에 위치하든 상관없이 특정 시간대로 시간을 표시할 때 유용합니다.
4. 상대적 시간 표시를 위한 헬퍼 만들기
최근 이벤트의 경우, 툴팁에 절대 시간을 유지하면서 "2시간 전"과 같은 상대적 시간을 표시합니다.
import { useIntl } from "react-intl";
interface RelativeTimeProps {
timestamp: string;
}
export function RelativeTime({ timestamp }: RelativeTimeProps) {
const intl = useIntl();
const date = new Date(timestamp);
const now = Date.now();
const diffInSeconds = Math.floor((now - date.getTime()) / 1000);
const absoluteTime = intl.formatDate(date, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
let value: number;
let unit: Intl.RelativeTimeFormatUnit;
if (diffInSeconds < 60) {
value = -diffInSeconds;
unit = "second";
} else if (diffInSeconds < 3600) {
value = -Math.floor(diffInSeconds / 60);
unit = "minute";
} else if (diffInSeconds < 86400) {
value = -Math.floor(diffInSeconds / 3600);
unit = "hour";
} else {
value = -Math.floor(diffInSeconds / 86400);
unit = "day";
}
const relativeTime = intl.formatRelativeTime(value, unit);
return (
<time dateTime={timestamp} title={absoluteTime}>
{relativeTime}
</time>
);
}
상대적 시간은 사용자 친화적인 기간을 표시하도록 업데이트되며, title 속성은 사용자가 마우스를 올렸을 때 정확한 로컬 시간을 보여줍니다. 두 형식 모두 사용자의 시간대와 로케일을 자동으로 반영합니다.