如何在 Next.js (Pages Router) v16 中处理时区
在用户的本地时区显示时间
问题
当应用程序在显示时间时未考虑用户的所在地,会引发混淆。一个安排在“下午 3:00”的会议,对于身处纽约、伦敦或东京的用户来说,意味着不同的时间点。如果服务器以 UTC 或其自身的时区渲染时间,其他地区的用户会看到错误的本地时间,从而导致错过会议和排程错误。同一个时间戳必须根据每个用户的时区进行不同的解释。
解决方案
将时间戳存储为通用格式,例如 ISO 8601 或 Unix 时间戳,然后在客户端根据用户的本地时区格式化显示。使用浏览器的国际化 API 检测用户的时区,并将其传递给 react-intl 的格式化函数。这确保了每个用户都能看到调整为其自身时区的时间,从而消除事件发生时间的歧义。
步骤
1. 在客户端检测用户的时区
浏览器的 Intl.DateTimeFormat().resolvedOptions().timeZone 返回用户的 IANA 时区标识符,例如 America/New_York 或 Europe/London。创建一个自定义 hook 来获取此值。
import { useState, useEffect } from "react";
export function useUserTimeZone() {
const [timeZone, setTimeZone] = useState<string>("UTC");
useEffect(() => {
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(detected);
}, []);
return timeZone;
}
此 hook 仅在客户端运行,避免了水合不匹配问题,并在检测到时区之前默认使用 UTC。
2. 使用 FormattedDate 按用户时区格式化日期
将 timeZone 选项传递给 react-intl 的格式化方法,以控制用于显示的时区。使用带有检测到的时区的 <FormattedDate> 组件。
import { FormattedDate } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface EventDateProps {
timestamp: string;
}
export function EventDate({ timestamp }: EventDateProps) {
const userTimeZone = useUserTimeZone();
return (
<FormattedDate
value={new Date(timestamp)}
timeZone={userTimeZone}
year="numeric"
month="long"
day="numeric"
hour="numeric"
minute="2-digit"
timeZoneName="short"
/>
);
}
timeZone 属性确保日期以用户的本地时区而非服务器或 UTC 格式化显示。
3. 使用 useIntl 以命令式格式化时间
在需要格式化字符串而不是组件的场景中,可以使用 useIntl 钩子访问命令式格式化 API。
import { useIntl } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface MeetingTimeProps {
startTime: string;
}
export function MeetingTime({ startTime }: MeetingTimeProps) {
const intl = useIntl();
const userTimeZone = useUserTimeZone();
const formattedTime = intl.formatDate(new Date(startTime), {
timeZone: userTimeZone,
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
return <span title={startTime}>{formattedTime}</span>;
}
formatDate 方法接受一个包含 DateTimeFormatOptions 的第二个参数,其中包括 timeZone,从而可以完全控制时间戳的显示方式。
4. 将服务器时间戳作为 ISO 字符串传递
在 getServerSideProps 中,在请求时获取数据并将其作为 props 传递给页面组件。将日期序列化为 ISO 8601 字符串以保留时区信息。
import { GetServerSideProps } from "next";
interface Event {
id: string;
title: string;
startTime: string;
}
interface EventPageProps {
event: Event;
}
export const getServerSideProps: GetServerSideProps<
EventPageProps
> = async () => {
const event = {
id: "1",
title: "Team Meeting",
startTime: new Date("2025-02-15T15:00:00Z").toISOString(),
};
return {
props: { event },
};
};
export default function EventPage({ event }: EventPageProps) {
return (
<div>
<h1>{event.title}</h1>
<EventDate timestamp={event.startTime} />
</div>
);
}
ISO 字符串保留了精确的时间点,允许客户端格式化将其准确转换为任何时区。