当后缀为“Z”时,ASP.NET Core API ISO8601 未在本地时间解析

Wil*_*lie 1 timezone-offset asp.net-web-api asp.net-core asp.net-core-2.2

场景:我有一个接受 POST 请求的 REST-API。作为正文数据,传递了 ISO8601 格式的日期时间。

{
    "validate": "2019-12-02T23:00:00Z",
    "configuration": {},
    "sortId": 1
}
Run Code Online (Sandbox Code Playgroud)

通过 MVC 中的模型绑定,可以自动解析日期时间。该变量应位于 api 服务器的本地时区。在本例中为欧洲/柏林。我预计时间(参考示例)为 2019-12-03:00:00:00。但这种情况并非如此。现在还有一小时休息时间。

但是当我发布以下内容时:

{
    "validate": "2019-12-02T23:00:00+00:00",
    "configuration": {},
    "sortId": 1
}
Run Code Online (Sandbox Code Playgroud)

解析为本地时区按预期进行。因为发布数据的客户端是用 JS 编写的,并使用默认的 Date.toISOString() 函数,所以我总是在结尾处得到一个“Z”。根据 ISO8601,这是完全没问题的。

Pan*_*vos 5

Z明确表示 UTC。+00:00没有。英国现在是00:00夏天01:00。1970 年,01:00全年使用夏令时 ( )。

这里涉及到几个概念和一些历史。首先,DateTime没有偏移量或时区的概念。根据其Kind属性,ADateTime只能是UTCLocal或。Undefined

使用DateTime意味着偏移信息丢失。结果值需要转换为某种东西。为此,需要使用机器的偏移量。也就是说,Web 服务计算机的偏移量,而不是数据库服务器的偏移量。

然后,我们的容器或应用程序故障转移到具有默认 UTC 时区而不是我们配置的时区的计算机。整个周末。

程序员对时间的看法值得一读,尤其是8. The machine that a program runs on will always be in the GMT time zone.

更好的解决方案是使用DateTimeOffset,尽管即使这样也无法处理 DST 规则更改。

更好的解决方案是使用 IANA 时区名称并传递Europe/Berlin而不是偏移量。但这不是常见用法。航空公司至少会发布带有偏移量和时区名称的航班时间。

日期时间解析规则

DateTime解析规则将转换Z或偏移量转换为Local带转换,将其他任何内容转换为Unspecified不转换。这听起来很奇怪,但考虑到它DateTime是为在桌面应用程序上工作而构建的,在桌面应用程序上Local时间是有意义的。

这段代码

var values=new[]{
"2019-12-02T23:00:00+00:00",
"2019-12-02T23:00:00Z",
"2019-12-02T23:00:00"
};
foreach(var value in values)
{
    var dt=DateTime.Parse(value);
    Console.WriteLine($"{dt:s}\t{dt.Kind}");
}
Run Code Online (Sandbox Code Playgroud)

生产:

2019-12-03T01:00:00  Local
2019-12-03T01:00:00  Local
2019-12-02T23:00:00  Unspecified
Run Code Online (Sandbox Code Playgroud)

这种UTC情况在这里消失了,作为航班预订系统的开发人员,我根本不喜欢这种情况。航班时间是机场当地时间,而不是我的服务器时间。现在,我必须先将该值转换回 UTC 或其他格式,然后再将其保存到数据库中。我必须将其转换回原始机场偏移量才能打印机票并发送通知。

如果出现任何错误,即使是由于航空公司的错误,我也必须赔偿你们。

JSON.NET(真正的API)解析规则

另一方面,JSON.NET 使用ZUTC 解析字符串,Local使用转换解析偏移量,不使用偏移量解析Undefined。对于从任何地方接收请求的 API,UTC 更有用。大多数托管服务商和云服务也默认提供 UTC 机器。

这段代码:

class It
{
    public DateTime Dt{get;set;}
}


var values=new[]{
"{'dt':'2019-12-02T23:00:00+00:00'}",
"{'dt':'2019-12-02T23:00:00Z'}",
"{'dt':'2019-12-02T23:00:00'}"
};
foreach(var value in values)
{
    var dt=JsonConvert.DeserializeObject<It>(value).Dt;
    Console.WriteLine($"{dt:s}\t{dt.Kind}");
}
Run Code Online (Sandbox Code Playgroud)

生产:

2019-12-03T01:00:00  Local
2019-12-02T23:00:00  Utc
2019-12-02T23:00:00  Unspecified
Run Code Online (Sandbox Code Playgroud)

更好,但不喜欢它。我仍然丢失信息。

带有 DateTimeOffset 的 JSON

DateTimeOffset包括偏移量,因此不会丢失任何信息。未指定的偏移量被视为Local时间。这个片段:

class It
{
    public DateTimeOffset Dt{get;set;}
}
void Main()
{
    var values=new[]{
    "{'dt':'2019-12-02T23:00:00+00:00'}",
    "{'dt':'2019-12-02T23:00:00Z'}",
    "{'dt':'2019-12-02T23:00:00'}"
    };
    foreach(var value in values)
    {
        var dt=JsonConvert.DeserializeObject<It>(value).Dt;
        Console.WriteLine($"{dt:o}");
    }
    
}
Run Code Online (Sandbox Code Playgroud)

生产:

2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+02:00
Run Code Online (Sandbox Code Playgroud)

为什么不是到处都是 UTC?

因为这会丢失大量信息,很容易使该时间值无法使用。有很多SO问题,发帖者试图比较 UTC 时间,但由于 DST 影响甚至 DST 变化而得到了意想不到的结果。

几年前,埃及在提前几周通知后更改了夏令时规则。航空公司和在线代理商并不高兴。

此外,如果游戏中存在两个以上时区怎么办?国际航班很少降落在同一时区,因此存储 UTC 不起作用。航空公司不会以 UTC 发布时刻表,而是以当地时间发布时刻表,并以偏移量和 IANA TZ 名称作为附加信息。

值得一读的是程序员对时间的看法,尤其是涉及 UTC 或 GMT 的部分。