在 Javascript 中使用时区和夏令时

Posted

技术标签:

【中文标题】在 Javascript 中使用时区和夏令时【英文标题】:Working with timezones and daylight savings time in Javascript 【发布时间】:2011-06-21 16:23:10 【问题描述】:

我的单页 javascript 应用程序通过 REST 调用以 JSON 格式检索数据。日期使用标准 ISO8601 格式的 UTC 时区格式化,例如2011-02-04T19:31:09Z

注册服务时,用户从下拉列表中选择他们的时区。此时区可能与用户浏览器的时区不同。 javascript 应用程序始终知道用户选择的时区。

我知道如何将 UTC 字符串转换为日期。我了解 Javascript 仅代表本地时区的日期。

但我无法弄清楚如何显示针对用户本地时区以外的时区格式化的日期。它必须考虑所有日期的 DST。在内部,我想将所有日期作为 UTC 处理,并且只在显示时转换为另一个时区中日期的字符串表示形式。我需要在用户个人资料中选择的时区显示日期,而不是他们浏览器的时区。

我已经尝试使用服务器发送用户浏览器时区和用户配置文件时区之间的时区偏移差异(以毫秒为单位)。但我发现我不能只发送一个偏移量值,而是需要为每个日期发送一个偏移量以说明 DST 的变化。

关于如何显示不同时区格式的日期的任何建议或示例代码?到目前为止我发现的选项:

    服务器将日期作为已在正确时区格式化的字符串发送,并且没有在客户端上进行日期解析或操作。这使得在客户端上做某些事情即使不是不可能也很困难。 使用诸如https://github.com/mde/timezone-js 之类的库,它将整个Olson TZ 数据库包含到Javascript 中。这意味着更长的加载时间更多的内存使用等。 发送一个 timezoneOffsetMillis 值,每个日期都发送给客户端。这会导致混乱的 JSON 数据和非最佳 REST 接口。

有没有更简单或更好的解决方案?

【问题讨论】:

它可能在您的应用程序中不起作用,但在网络上,显示某物的年龄通常更容易,而不是尝试在用户时区中可靠地显示日期和时间。例如,SO 上的“6 分钟前询问”。 @Alexandre -- 谢谢,我已经在可能的情况下这样做了,例如上次登录日期、发布日期等。但是我的应用程序需要显示发生在各种事件中的特定日期和时间时区。我不能为此使用相对时间或年龄。 我肯定会选择 timezone-js,这将是“正确”的做法。您还可以将其与以下时区检测脚本结合使用:bitbucket.org/pellepim/jstimezonedetect。 @Jon,我同意这是正确的方法,但它肯定会增加一些臃肿。我希望 JS 从一开始就支持时区。感谢指向 jstimezonedetect 的指针。我以前看过它的演示页面,但忘记了。 在这种情况下,我会使用 zoneinfo 文件将 tz 工作卸载到服务器,并让它通过格式化的 json 返回本地日期时间。 【参考方案1】:

2 是个坏主意,因为正如您所指出的,它会增加加载时间。如果我是你,我会做 1 和 3 的组合。我不同意这会导致 JSON 数据混乱或 REST 接口非最优。

这是一个经典的权衡,在协议中接受更多的复杂性以简化客户端代码。

【讨论】:

谢谢,但我有一个保持 JSON 干净的想法。如果我以 UTC 格式(如 2011-02-03T09:31:35Z)发送所有应在用户本地时区显示的日期会怎样。任何应该在特定时区显示的日期都将使用该时区的格式发送,例如2011-02-03T14:00:00-0800。当我最初解析日期时,我可以在日期对象的自定义属性中保留偏移量。我要试试这个。 @Tauren:使用这种方法,如果您在客户端上进行任何日期数学运算(添加或删除时间),请注意自动时区转换。例如,如果您将 24 小时添加到日期并跨越夏令时,则当转换为字符串时,时间将显示为相差一个小时。此外,当我们向后移动时钟时,那个小时在 JavaScript 中是模棱两可的。当我们将时钟向前移动时,跳过的小时是不可表示的(假设当地时间遵守夏令时),即使这可能是您用户选择的时区中的时间。【参考方案2】:

有同样的问题。 #1是我猜的解决方案。您应该将特殊容器中的所有日期组件(年、月、日、小时)从客户端发送到服务器

【讨论】:

【参考方案3】:

快进到 2015 年,当涉及到使用特定区域设置从其他时区格式化日期时,有两种选择。

在示例中,我使用法语语言环境并将使用 (UTC+5) 日期 2016 年 3 月 27 日 2h15 在西欧没有观察到(因为 DST 变化,时钟从 2h00 移动到 3h00),这是常见的错误来源。

为获得最大的可移植性,请使用moment.js 库。 Moment 附带捆绑的80+ locales。

    使用时间戳和 UTC 偏移量:

    moment(1459026900000).utcOffset(300).locale('fr').format("LLLL")
    // "dimanche 27 mars 2016 02:15"
    

    使用整数数组(注意在moment中,月份是从0开始的,就像在原生JS Date对象中一样)

    moment.utc([2016,3-1,27,2,15]).locale('fr').format("LLLL")
    // "dimanche 27 mars 2016 02:15"
    

您可以通过在http://momentjs.com/上的浏览器开发工具中运行代码来测试这些方法@

这里的棘手部分是使用 moment.utc 创建一个带有 UTC 标志的时刻日期对象,这意味着当该日期被格式化时,它不会转换为用户的时区,而是按原样显示(并且由于 UTC不遵守夏令时,不易出现夏令时错误)。

未来的原生解决方案:使用Intl对象

(请注意,截至 2015 年底,Chrome、Firefox、IE11 支持此解决方案,但仍Safari 9 不支持

使用整数数组:

    new Intl.DateTimeFormat('fr-FR',  timeZone: 'UTC', weekday: 'long',
          month: 'long', year: 'numeric', day: 'numeric', hour: 'numeric',
          minute: 'numeric' )
      .format(Date.UTC(2016,3-1,27,2,15))
    // "dimanche 27 mars 2016 02:15"

这里的关键是再次使用Date.UTCtimeZone: 'UTC' 确保提供的日期不会转换为用户的本地时区。


请注意,在这两种情况下,我们都使用 UTC 方法,只是为了确保按原样使用日期而不进行转换。重要的是要意识到这些日期是 假 UTC 日期(它们代表给定任意时区的时间,而不是 UTC 时间),并且应该只用于日期格式和显示 - 不应该传递.

【讨论】:

2019 注意:还要检查 date-fns.org,它比 moment.js 小得多,从包大小的角度来看可能更合适

以上是关于在 Javascript 中使用时区和夏令时的主要内容,如果未能解决你的问题,请参考以下文章

时区转换和夏令时

如何在android中处理时区和夏令时?

如何将用户的本地时间转换为多伦多时区,然后在 Javascript 中根据多伦多时间检查 dst?

如何在 Dynamics CRM 中获取时区的夏令时开始和结束?

PHP 时区问题 |英国夏令时和格林威治标准时间

通用时区:你应该知道的数据库时区知识