11

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

铬合金:

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

火狐:

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

IE:

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

苹果浏览器:

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

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

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

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

4

5 回答 5

13

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

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

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

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

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

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

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

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

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

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

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

自 ES2017 和即将到来的 ES2018 以来,它一直保持一致;这是当前编辑草稿的链接,它不再具有确切的文本,但仍然以相同的方式定义它(尽管恕我直言不太清楚)。

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

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 问题列表中找不到问题;我只能找到这个仍然存在但似乎已修复的问题。

于 2013-02-17T17:42:52.903 回答
6

归根结底,如果我的服务器始终以所有浏览器都能正确处理的格式发送客户端 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”。我测试过的所有浏览器都可以做正确的事情。

于 2013-02-17T23:09:00.957 回答
2

我遇到了这个问题,用本地时区解释日期比更改为“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);
    }
}
于 2013-10-03T16:15:23.493 回答
0

除了@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...);
于 2019-05-30T20:37:43.123 回答
0

大卫哈蒙德的答案很棒,但并没有玩所有的把戏;所以这是一个修改版本:

  • 允许日期/时间字符串中的小数部分
  • 允许日期/时间字符串中的可选秒数
  • 考虑跨越夏令时
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));
}
于 2017-03-30T20:56:32.403 回答