如何在 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 hook,访问命令式格式化 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 字符串可以保留精确的时间点,使客户端格式化能够准确转换为任意时区。