ASP.NET 核心 API 当后缀为 'Z' 时,ISO8601 未在本地时间解析
ASP.NET Core API ISO8601 not parsed in local time when suffix is 'Z'
场景:
我有一个 REST-API 需要一个 POST-Request。作为主体数据,传递了一个 ISO8601 格式的日期时间。
{
"validate": "2019-12-02T23:00:00Z",
"configuration": {},
"sortId": 1
}
使用 MVC 中的模型绑定,日期时间会自动解析。该变量应该在 api-server 的本地时区。在这种情况下 Europe/Berlin。我希望时间(参考示例)在 2019-12-03:00:00:00。但这种情况并非如此。还差一小时
但是当我 post 以下内容时:
{
"validate": "2019-12-02T23:00:00+00:00",
"configuration": {},
"sortId": 1
}
本地时区的解析按预期进行。因为客户端 posting 数据是用 JS 编写的,并使用默认的 Date.toISOString() 函数,所以我总是在结尾得到一个 'Z'。根据 ISO8601,这完全没问题。
Z
明确表示 UTC。 +00:00
没有。英国现在是 00:00
,但夏天是 01:00
。 1970 年全年使用夏令时(01:00
)。
这里涉及到几个概念和一些历史。首先,DateTime
没有偏移量或时区的概念。一个DateTime
只能是UTC
、Local
或Undefined
,根据其Kind属性.
使用DateTime
表示偏移信息丢失。结果值需要转换为 something。为此,使用了机器的偏移量。也就是说,Web 服务计算机的 偏移量,而不是数据库服务器的偏移量。
然后,我们的容器或应用程序故障转移到具有默认 UTC 时区而不是我们配置的时区的机器。周末。
值得一读Falsehoods programmers believe about time,尤其是8. The machine that a program runs on will always be in the GMT time zone.
更好的解决方案是使用 DateTimeOffset
,尽管即使这样也无法处理 DST 规则更改。
更好的解决方案是使用 IANA 时区名称并传递 Europe/Berlin
而不是偏移量。但这不是常见的用法。航空公司至少 post 飞行时间,包括偏移量和时区名称。
日期时间解析规则
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}");
}
产生:
2019-12-03T01:00:00 Local
2019-12-03T01:00:00 Local
2019-12-02T23:00:00 Unspecified
UTC
类型在这里消失了,作为航班预订系统的开发者,我一点也不喜欢 。飞行时间是机场当地时间,不是我的服务器。现在我必须将该值转换回 UTC 或其他格式,然后再将其保存到数据库中。我必须将其转换回原始 airport 偏移量以打印机票并发送通知。
如果有任何错误,即使是由于航空公司的错误,我也必须赔偿你们。
JSON.NET(真的API)解析规则
另一方面,JSON.NET 将带有 Z
的字符串解析为 UTC,偏移量为 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}");
}
产生:
2019-12-03T01:00:00 Local
2019-12-02T23:00:00 Utc
2019-12-02T23:00:00 Unspecified
更好,但是我不喜欢。我仍然丢失信息。
JSON 与 DateTimeOffset
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}");
}
}
产生:
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+02:00
为什么不是到处都是 UTC?
因为这会丢失大量信息,很容易使该时间值无法使用。有 很多 的 SO 问题,其中 poster 试图比较 UTC 时间并由于 DST 影响甚至 DST 更改而得到意想不到的结果。
几年前,埃及在提前几周通知的情况下更改了夏令时规则。航空公司和在线代理商并不高兴。
此外,如果有两个以上的时区在玩怎么办?国际航班很少在同一时区降落,因此存储 UTC 不起作用。 航空公司不以 UTC 发布时刻表,他们以当地时间发布,并带有偏移量和 IANA TZ 名称作为附加信息。
值得一读Falsehoods programmers believe about time,尤其是涉及 UTC 或 GMT 的部分。
场景: 我有一个 REST-API 需要一个 POST-Request。作为主体数据,传递了一个 ISO8601 格式的日期时间。
{
"validate": "2019-12-02T23:00:00Z",
"configuration": {},
"sortId": 1
}
使用 MVC 中的模型绑定,日期时间会自动解析。该变量应该在 api-server 的本地时区。在这种情况下 Europe/Berlin。我希望时间(参考示例)在 2019-12-03:00:00:00。但这种情况并非如此。还差一小时
但是当我 post 以下内容时:
{
"validate": "2019-12-02T23:00:00+00:00",
"configuration": {},
"sortId": 1
}
本地时区的解析按预期进行。因为客户端 posting 数据是用 JS 编写的,并使用默认的 Date.toISOString() 函数,所以我总是在结尾得到一个 'Z'。根据 ISO8601,这完全没问题。
Z
明确表示 UTC。 +00:00
没有。英国现在是 00:00
,但夏天是 01:00
。 1970 年全年使用夏令时(01:00
)。
这里涉及到几个概念和一些历史。首先,DateTime
没有偏移量或时区的概念。一个DateTime
只能是UTC
、Local
或Undefined
,根据其Kind属性.
使用DateTime
表示偏移信息丢失。结果值需要转换为 something。为此,使用了机器的偏移量。也就是说,Web 服务计算机的 偏移量,而不是数据库服务器的偏移量。
然后,我们的容器或应用程序故障转移到具有默认 UTC 时区而不是我们配置的时区的机器。周末。
值得一读Falsehoods programmers believe about time,尤其是8. The machine that a program runs on will always be in the GMT time zone.
更好的解决方案是使用 DateTimeOffset
,尽管即使这样也无法处理 DST 规则更改。
更好的解决方案是使用 IANA 时区名称并传递 Europe/Berlin
而不是偏移量。但这不是常见的用法。航空公司至少 post 飞行时间,包括偏移量和时区名称。
日期时间解析规则
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}");
}
产生:
2019-12-03T01:00:00 Local
2019-12-03T01:00:00 Local
2019-12-02T23:00:00 Unspecified
UTC
类型在这里消失了,作为航班预订系统的开发者,我一点也不喜欢 。飞行时间是机场当地时间,不是我的服务器。现在我必须将该值转换回 UTC 或其他格式,然后再将其保存到数据库中。我必须将其转换回原始 airport 偏移量以打印机票并发送通知。
如果有任何错误,即使是由于航空公司的错误,我也必须赔偿你们。
JSON.NET(真的API)解析规则
另一方面,JSON.NET 将带有 Z
的字符串解析为 UTC,偏移量为 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}");
}
产生:
2019-12-03T01:00:00 Local
2019-12-02T23:00:00 Utc
2019-12-02T23:00:00 Unspecified
更好,但是我不喜欢。我仍然丢失信息。
JSON 与 DateTimeOffset
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}");
}
}
产生:
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+02:00
为什么不是到处都是 UTC?
因为这会丢失大量信息,很容易使该时间值无法使用。有 很多 的 SO 问题,其中 poster 试图比较 UTC 时间并由于 DST 影响甚至 DST 更改而得到意想不到的结果。
几年前,埃及在提前几周通知的情况下更改了夏令时规则。航空公司和在线代理商并不高兴。
此外,如果有两个以上的时区在玩怎么办?国际航班很少在同一时区降落,因此存储 UTC 不起作用。 航空公司不以 UTC 发布时刻表,他们以当地时间发布,并带有偏移量和 IANA TZ 名称作为附加信息。
值得一读Falsehoods programmers believe about time,尤其是涉及 UTC 或 GMT 的部分。