【问题标题】:Chrome interprets ISO time without Z as UTC; C# issueChrome 将没有 Z 的 ISO 时间解释为 UTC; C# 问题
【发布时间】:2013-02-02 03:41:01
【问题描述】:

在 Chrome、FF 和 IE 上运行这个 jsfiddle:http://jsfiddle.net/E9gq9/7/,你会得到:

铬:

Chrome http://images.devs-on.net/Image/vBTz86J0f4o8zlL3-Region.png

火狐:

Firefox http://images.devs-on.net/Image/aNPxNPUpltyjVpSX-Region.png

IE:

IE http://images.devs-on.net/Image/WXLM5Ev1Viq4ecFq-Region.png

Safari:

Safari http://images.devs-on.net/Image/AEcyUouX04k2yIPo-Region.png

ISO 8601 似乎没有说明应该如何解释没有尾随 Z 的字符串。

我们的服务器 (ASP.NET MVC4) 将 UTC 时间从我们的数据库中提取为 DateTimes 并简单地将它们填充到 JSON 中。正如您所见,因此我们在浏览器上得到的结果不一致。

我们应该在服务器端将 Z 附加到它们吗?

【问题讨论】:

  • Opera 和 Chrome 一样。

标签: javascript asp.net datetime utc


【解决方案1】:

我们是否应该在服务器端将 Z 附加到它们?

TL;DR是的,您可能应该这样做。

遗憾的是,没有时区的日期和日期/时间的正确处理多年来一直在变化——无论是在规范方面,还是在 JavaScript 引擎对规范的遵守方面。

当这个答案最初是在 2013 年编写时,ES5 规范(第一个为 JavaScript 定义了日期/时间格式,本应是 ISO-8601 的子集)很明确:无时区 = UTC :

缺席时区偏移的值为“Z”。

但这与 ISO-8601 不一致,在 ISO-8601 中,没有时区指示符意味着“当地时间”。一些实现从未实现 ES5 的含义,而是坚持使用 ISO-8601。

ES2015(又名“ES6”)中,它已更改为匹配 ISO-8601:

如果不存在时区偏移,则日期时间被解释为本地时间。

然而,这会导致与现有代码的不兼容问题,尤其是像 2018-07-01 这样的仅日期形式,因此在 ES2016 中再次对其进行了更改:

当时区偏移不存在时,仅日期形式被解释为 UTC 时间,而日期时间形式被解释为本地时间。

所以new Date("2018-07-01") 被解析为UTC,但new Date("2018-07-01T00") 被解析为当地时间。

自 ES2017 和即将到来的 ES2018 以来,它一直保持一致; here's the link to the current editor's draft,它不再有确切的文本,但仍然以相同的方式定义它(尽管恕我直言不太清楚)。

您可以在这里测试您当前的浏览器:

function test(val, expect) {
  var result = +val === +expect ? "Good" : "ERROR";
  console.log(val.toISOString(), expect.toISOString(), result);
}
test(new Date("2018-07-01"), new Date(Date.UTC(2018, 6, 1)));
test(new Date("2018-07-01T00:00:00"), new Date(2018, 6, 1));

截至 2021 年 9 月的状态:

  • 现代版本的 Firefox、Chrome 和 Safari 都做到了这一点,包括 iOS 浏览器(这些浏览器目前都使用 Apple 的 JavaScriptCore JavaScript 引擎,因为 Apple 不会让他们使用自己的 JIT)
  • IE11 正确(有趣)

截至 2018 年 4 月的状态:

  • IE11 正确(有趣)
  • Firefox 做对了
  • Chrome 65(桌面、Android)做得对
  • Chrome 64 (iOS v11.3) 获取日期/时间格式错误(解析为 UTC)
  • v11.3 中的 iOS Safari 获取日期/时间格式错误(解析为 UTC)

奇怪的是,我在 v6.4(Chrome 64 中的 v8)和 v6​​.5(Chrome 65 中的 v8)之间修复的 v8 问题列表中找不到问题;我只能找到this issue,它仍然处于打开状态,但似乎已修复。

【讨论】:

  • 但是,您也应该注意到older IEs fail to parse ISO 8601 entirely。 JS 日期解析中没有什么是一致的 :-(
  • 哇。这绝对是 ECMAScript 规范所说的 - 但它非常破碎,IMO - 并且直接违反 ISO-8601,其中指出:“如果使用符合 4.2.2.2 到 4.2.2.4 的本地时间,则区域指示符为空。”叹。请注意,鉴于它将被解析为只是一个瞬间,我认为整个 API 都已损坏。双重叹息。
  • @Bergi:那是因为这是 ECMAScript5 的新功能。令人惊讶的是,即使 ECMAScript3 也没有规定引擎必须解析的 任何 日期/时间字符串,只是说它必须解析 Date#toString 吐出的任何内容——但它没有规定那是什么。好消息是没有不会解析yyyy/MM/dd(必须是/,而不是-)的主流浏览器,即使它从未被标准化。坏消息是,不同的浏览器会根据本地或 UTC 进行不同的调用。
  • @JonSkeet:这委员会做 ECMAScript5 的日期/时间格式明确基于 ISO-8601(“简化”形式,因为完整的 ISO-8601 非常复杂),但在这方面却存在分歧。令人惊讶,真的。然而,它看起来会如此......
  • 好吧,它在最新版本的 Safari 中可以正常工作,这很好。感谢您的宝贵时间!
【解决方案2】:

除了@tig 的答案(这正是我想要的):

这是 .NetCore 1 的解决方案

services.AddMvc();
services.Configure<MvcJsonOptions>(o =>
{
    o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
});

services.AddMvc().AddJsonOptions(o => o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc);

对于 .NetCore 1.0.1

services
    .AddMvcCore()
    .AddJsonFormatters(o => o...);

【讨论】:

    【解决方案3】:

    David Hammond 的 answer 很棒,但并没有玩所有的把戏;所以这里是一个修改版:

    • 允许在日期/时间字符串中使用小数部分
    • 允许在日期/时间字符串中使用可选秒数
    • 考虑跨越夏令时
    appendTimezone = function (/*String*/ d) {
        // check for ISO 8601 date-time string (seconds and fractional part are optional)
        if (d.search(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1-3})?)?$/) == 0) {
            var pad = function (num) {
                norm = Math.abs(Math.floor(num));
                return (norm < 10 ? '0' : '') + norm;
            },
            tzo = -new Date(d).getTimezoneOffset(),
            sign = tzo >= 0 ? '+' : '-';
    
            var adjusted = d + sign + pad(tzo / 60) + ':' + pad(tzo % 60);
    
            // check whether timezone offsets are equal;
            // if not then the specified date is just within the hour when the clock
            // has been turned forward or back
            if (-new Date(adjusted).getTimezoneOffset() != tzo) {
                // re-adjust
                tzo -= 60;
                adjusted = d + sign + pad(tzo / 60) + ':' + pad(tzo % 60);
            }
    
            return adjusted;
        } else {
            return d;
        }
    }
    
    parseDate = function (/*String*/ d) {
        return new Date(appendTimezone(d));
    }
    

    【讨论】:

      【解决方案4】:

      我遇到了这个问题,使用本地时区解释日期比更改为“Z”更有意义,至少对于我的应用程序而言。我创建了此函数以在 ISO 日期中缺少本地时区信息时附加它。这可以用来代替 new Date()。部分来自这个答案: How to ISO 8601 format a Date with Timezone Offset in JavaScript?

      parseDate = function (/*String*/ d) {
          if (d.search(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/) == 0) {
              var pad = function (num) {
                  norm = Math.abs(Math.floor(num));
                  return (norm < 10 ? '0' : '') + norm;
              },
              tzo = -(new Date(d)).getTimezoneOffset(),
              sign = tzo >= 0 ? '+' : '-';
              return new Date(d + sign + pad(tzo / 60) + ':' + pad(tzo % 60));
          } else {
              return new Date(d);
          }
      }
      

      【讨论】:

        【解决方案5】:

        归根结底,如果我的服务器始终以所有浏览器都能正确处理的格式发送客户端 DateTime 对象,我在此应用中面临的问题就可以解决。

        这意味着末尾必须有那个“Z”。原来 ASP.NET MVC4 Json 序列化程序基于 Json.NET,但默认情况下没有打开 Utc。 DateTimeZoneHandling 的默认值似乎是 RoundtripKind,这不会输出带有 Z 的值,即使 DateTime.Kind == Utc 也是如此,这很烦人。

        所以修复似乎是,将 Json.NET 处理时区的方式设置为DateTimeZoneHandling.Utc

        var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        // Force Utc formatting ('Z' at end). 
        json.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
        

        现在,从我的服务器到浏览器的所有内容都被格式化为 ISO-8601,末尾带有“Z”。我测试过的所有浏览器都可以做正确的事情。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-09-01
          • 1970-01-01
          • 2013-01-30
          • 2021-03-05
          • 2023-03-23
          • 1970-01-01
          • 2011-05-02
          • 2020-03-09
          相关资源
          最近更新 更多