如何在 React Router v7 中处理时区
以用户的本地时区显示时间
问题
当应用程序在显示时间时未考虑用户的所在地,会导致混淆和错误。例如,服务器可能将事件时间存储为 "2024-03-15T20:00:00Z",并直接从该 UTC 值渲染为 "8:00 PM"。不同时区的用户看到相同的时钟时间,但会将其解释为他们的本地时间,从而导致错过预约和日程冲突。根本问题在于,同一时刻在不同的地理位置有不同的表示方式,而在错误的时区显示时间会使其变得毫无意义或具有误导性。
当用户跨地区协作时,这一问题会更加严重。例如,服务器上安排在下午 3:00 的事件,在用户界面中会显示为各地的下午 3:00,尽管同一时刻在东京应显示为早上 6:00,在伦敦应显示为下午 2:00,在纽约应显示为早上 9:00。
解决方案
将所有时间戳存储为通用格式,例如带有 UTC 指示符的 ISO 8601 字符串或 Unix 时间戳。在向用户显示这些时间时,将其转换为 JavaScript 的 Date 对象,并使用尊重浏览器时区的国际化 API 进行格式化。现代浏览器会自动检测用户的本地时区,格式化库利用这一点将 UTC 时间戳转换为正确的本地表示,而无需手动计算偏移量。
这种方法将存储与展示分离。服务器发送一个规范的时间戳,每个客户端根据其自身的时区渲染时间,确保每个人都能看到同一全球时刻的正确本地时间。
步骤
1. 从数据源以 ISO 8601 格式发送时间戳
确保您的 API 或数据加载器返回的时间戳为带有 UTC 指示符的 ISO 8601 字符串或以毫秒为单位的 Unix 时间戳。
export async function loader() {
const event = await fetchEvent();
return {
title: event.title,
startTime: "2024-03-15T20:00:00Z",
endTime: "2024-03-15T22:00:00Z",
};
}
Z 后缀表示 UTC 时间。当此字符串被解析为 JavaScript 的 Date 对象时,浏览器会将其内部存储为 UTC 时间戳。
2. 创建一个使用 react-intl 格式化时间的组件
从 react-intl 导入格式化组件,并传递由 ISO 字符串创建的 Date 对象。
import { FormattedDate, FormattedTime } from "react-intl";
import type { Route } from "./+types/event";
export default function EventDetails({ loaderData }: Route.ComponentProps) {
const startTime = new Date(loaderData.startTime);
const endTime = new Date(loaderData.endTime);
return (
<div>
<h1>{loaderData.title}</h1>
<p>
<FormattedDate
value={startTime}
year="numeric"
month="long"
day="numeric"
/>
{" 于 "}
<FormattedTime value={startTime} />
{" 到 "}
<FormattedTime value={endTime} />
</p>
</div>
);
}
FormattedDate 和 FormattedTime 组件会自动使用浏览器检测到的时区来显示正确的本地时间。纽约的用户会看到 "2024 年 3 月 15 日 下午 3:00",而东京的用户会看到 "2024 年 3 月 16 日 上午 5:00",对于同一个 UTC 时间戳。
3. 对于动态格式化需求,使用命令式 API
当您需要在属性、计算值或非 JSX 上下文中格式化时间时,可以使用 useIntl 钩子。
import { useIntl } from "react-intl";
import type { Route } from "./+types/event";
export default function EventCard({ loaderData }: Route.ComponentProps) {
const intl = useIntl();
const startTime = new Date(loaderData.startTime);
const formattedDate = intl.formatDate(startTime, {
year: "numeric",
month: "short",
day: "numeric",
});
const formattedTime = intl.formatTime(startTime, {
hour: "numeric",
minute: "2-digit",
});
return (
<div>
<h2>{loaderData.title}</h2>
<time dateTime={loaderData.startTime}>
{formattedDate} 于 {formattedTime}
</time>
</div>
);
}
formatDate 和 formatTime 方法会根据用户的语言环境和时区返回格式化的字符串。dateTime 属性保留了原始的 ISO 字符串以便机器读取,而显示的文本则展示了用户友好的本地时间。