在 Web 开发中处理时区问题

2025-06-07

在 Web 开发中处理时区问题

说实话,处理日期和时间是人类最棘手的问题之一,编程也不例外。如果您的应用需要处理来自世界各地用户的事件,您需要添加时区,甚至可能需要重复执行以保存可能多次发生的事件。本文将介绍一些处理此类应用的方法:

  • 如何在数据库中存储日期。
  • 如何处理复发。
  • 在哪里将时间转换为用户的当地时间。
  • 可以帮助完成这些任务的图书馆。

让我们开始吧。

如何在数据库中存储日期

当用户位于不同位置时,在数据库中保存日期的最常见方法是使用UTC(协调世界时)保存时间,这是时钟和时间调节的主要时间标准,但这并不总是最好的解决方案,您必须检查您的具体用例;例如:

  • 用户从哪里保存日期?
  • 所有用户都需要保存日期还是只需要一个管理员?
  • 事件发生在哪里?

例如,最近我必须为我国的一个教堂制作一个电视节目表插件,考虑到活动只在一个地方发生,以 UTC 格式保存日期时间是一种过度设计,因为这不是真正需要的,所以我将它保存在教堂当地时区。

但另一方面,在我的工作中,我遇到过用户可以保存和编辑世界各地的事件的情况,在这种情况下,以 UTC 保存更方便

如何管理日期重复

当我第一次遇到 Web 开发问题时,我总是寻找我使用的应用程序,因为它们为我提供了用户体验、界面,有时还提供 API(如果应用程序准备好与第三方应用程序集成)。所以我立即打开浏览器并查找 Google 日历。

他们有一个非常直观的接口来保存递归,并且在 API 文档中提到了RRule。RRule是处理递归的标准,在大多数编程语言中都有多种实现,在 JavaScript 中,rrule.js就是答案。

以下是每周举办一次活动的示例代码,直到 2021 年 9 月 30 日



// To create the rrule
const rule = new RRule({
  freq: RRule.WEEKLY,
  dtstart: new Date(Date.UTC(2021, 8, 18, 8, 17, 0)),
  until: new Date(Date.UTC(2021, 8, 30, 8, 17, 0)),
  count: 30,
  interval: 1
});

// to get the RRule in string
rule.toString();
// DTSTART:20210918T081700Z
// RRULE:FREQ=WEEKLY;UNTIL=20210930T081700Z;COUNT=30;INTERVAL=1;WKST=MO

// to get the ocurrence
rule.all();


Enter fullscreen mode Exit fullscreen mode

您可以将 RRule 字符串保存在数据库的字段中。但我认为最好将 RRule 的每个属性保存为单独的字段(frequencyinterval等),以便更好地从数据库中查询事件。

在哪里将时间转换为用户的当地时间?

时间转换是一个可视化的方面,即使你正在向移动和 Web 应用提供 API,最好也不要让后端代码参与这些转换,而让前端来处理。你可以使用IntlAPI 直接从 Web 浏览器检测用户的本地时区。



Intl.DateTimeFormat().resolvedOptions().timeZone


Enter fullscreen mode Exit fullscreen mode

它具有非常可接受的浏览器支持,您可以在MDN中阅读有关它的更多信息

另一个选项是要求用户指定他们的时区,并预先选择当前时区。

一旦我们获得用户时区,就可以将其从 UTC 或您保存在数据库中的时区转换为用户时区,我们在 javascript 中有一些不错的选择:luxondate-fns,还建议将这些库中的功能包装在一个中心位置,以防您出于某种原因需要更改,如果您在另一个应用程序中遇到类似情况,测试和移动都会更容易。

为了说明这一点,这里是我为管理时区转换而做的包装器的一个例子,以便让您了解:



import { DateTime } from "luxon";
export const ISO_TIME_FORMAT = "HH:mm";

export function useTime(zone, serverTimezone = "UTC") {
  const timeZone = zone;
  ...

  /**
   * Transform a JS Date in users' timezone to ISO date in UTC
   * @param {Date} date
   * @returns {Object}
   */
  const getIsoUtcDateTime = (date) => { ... };

  /**
   * Transform DB date and time in ISO to a JS Date in users' timezone
   * @param {String} isoDate
   * @param {String} isoTime
   * @returns {Object}
   */
  const getLocalDateTimeFromISO = (isoDate, isoTime) => { ... };

  return {
      ...
      getIsoUtcDateTime,
      getLocalDateTimeFromISO
  }


Enter fullscreen mode Exit fullscreen mode

我将省略实现细节,因为我只想向您展示包装器带来的一些好处。这里useTime定义了一个主函数,它只接受一次用户和数据库时区参数,它返回的函数将使用这些时区进行转换。

要使用我们的包装器,假设日期和时间保存为 ISO 字符串"yyyy-MM-dd""HH:mm"格式,我们可以按如下方式进行:



import { useTime } from "./useTime";
import constants from "./constants";

const { getLocalDateTimeFromISO } = useTime(user.timezone, constants.SERVER_TIMEZONE);

// ... code to fetch events would go here

// transform iso dates to users' timezone
const eventsInLocal = events.map((event) => {
const { date, time } = getLocalDateTimeFromISO(event.date, event.time);
event.date = date;
event.time = time;
return event;
}

Enter fullscreen mode Exit fullscreen mode




测试

为了测试开发中的行为,如果我们从浏览器获取时区,您可以通过单击检查器中顶部栏末尾的三个点 > 更多工具 > 传感器,在检查器中模拟浏览器中的不同时区。

浏览器检查中的传感器

这将在浏览器检查器底部打开一个部分,其中包含一个用于覆盖当前位置和时区的选项:

图像.png

不同地点的选择

现在,我们的浏览器时区已设置完毕Asia/Tokio,其new Date()行为将与东京时一样(Arigato)

图像.png

结论

每当我们克服一个又一个艰难的挑战,我们的技能就会得到提升。如果我能用一个数字来衡量处理日期的点数对你的技能的影响,我想象不出一个数字,但那会是一个很高的数字😂。值得庆幸的是,我们有一些人为我们铺平了道路,为我们制定了像UTCRRule这样的标准。

感谢您的阅读,我希望这篇文章可以为您节省一些时间,如果您有任何疑问,欢迎评论,或者如果您喜欢Twitter以及我的Github,我会在那里做一些实验和项目。

祝你有美好的一天。

资源

照片由Djim LoicUnsplash上拍摄

文章来源:https://dev.to/jesusantguerrero/dealing-with-timezones-in-web-development-2dgg
PREV
使用 Laravel 和 Docker 部署 RESTful API
NEXT
5分钟了解VueJS