ASP.NET Core API ISO8601 not parsed in local time when suffix is 'Z'

旧街凉风 提交于 2021-01-28 10:16:15

问题


Scenario: I have a REST-API that takes a POST-Request. As Body-Data there is passed a Datetime in ISO8601 Format.

{
    "validate": "2019-12-02T23:00:00Z",
    "configuration": {},
    "sortId": 1
}

With Modelbinding in MVC the Datetime gets parsed automaticly. The variable should be in the local timezone of the api-server. In this case Europe/Berlin. I would expect the time to be (refering to the example) to be on 2019-12-03:00:00:00. But this is not the case. It is still one hour off.

But when i post the following:

{
    "validate": "2019-12-02T23:00:00+00:00",
    "configuration": {},
    "sortId": 1
}

The parsing into the local timezone works as expected. Because the Client posting the data is written in JS and uses the default Date.toISOString() function, i always get a 'Z' in the ending. According to the ISO8601 this is totally fine.


回答1:


Z explicitly means UTC. +00:00 doesn't. The UK is at 00:00 now, but 01:00 in the summer. In 1970, summer time (01:00) was used for the entire year.

There are a couple of concepts involved here, and a bit of history. First of all, DateTime has no concept of offset or timezone. A DateTime can only be UTC, Local or Undefined, according to its Kind property.

Using DateTime means that the offset information is lost. The resulting value needs to be converted to something. To do that, the machine's offset is used. That is, the web service machine's offset, not the database server's.

And then, our container or application fails over to a machine with a default UTC timezone instead of our configured timezone. Over the weekend.

It's worth reading Falsehoods programmers believe about time, especially 8. The machine that a program runs on will always be in the GMT time zone.

A better solution would be to use DateTimeOffset, although even that won't be able to handle DST rule changes.

An even better solution would be to use IANA timezone names and pass Europe/Berlin instead of offsets. That's not common usage though. Airlines at least post flight times both with offsets and timezone names.

DateTime Parsing rules

DateTime parsing rules convert Z or offsets to Local with conversion and anything else to Unspecified without conversion. This sounds strange but consider that DateTime was built to work on desktop applications, where Local time makes sense.

This code

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}");
}

Produces :

2019-12-03T01:00:00  Local
2019-12-03T01:00:00  Local
2019-12-02T23:00:00  Unspecified

The UTC kind is lost here and, as a developer of flight reservation systems, I don't like that at all. Flight times are in the airport's local time, not my servers. Now I'll have to convert that value back to UTC or something, before saving it to a database. I have to convert it back to the original airport offset to print the tickets and send notifications.

And I'll have to reimburse you guys if there's any error, even if it's due to an airline's error.

JSON.NET (really API) parsing rules

JSON.NET on the other hand, parses strings with Z to UTC, Offsets to Local with conversion and no offset to Undefined. For an API that receives requests from anywhere, UTC is far more useful. Most hosters and cloud services provide UTC machines by default too.

This code :

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}");
}

Produces :

2019-12-03T01:00:00  Local
2019-12-02T23:00:00  Utc
2019-12-02T23:00:00  Unspecified

Better, but I don't like it. I still lose information.

JSON with DateTimeOffset

DateTimeOffset includes the offset so no information is lost. Unspecified offsets are treated as Local time. This snippet :

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}");
    }
    
}

Produces :

2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+00:00
2019-12-02T23:00:00.0000000+02:00

Why not UTC everywhere?

Because that loses a lot of information that could easily make that time value unusable. There are a lot of SO questions where the poster tried to compare UTC time and got unexpected results because of DST effects or even DST changes.

A few years back Egypt changed its DST rules with a couple of weeks notice. Airlines and online agents weren't thrilled.

Besides, what if there are more than two timezones in play? International flights rarely land in the same timezone, so storing UTC doesn't work. Airlines don't publish schedules in UTC, they publish them as local time, with offset and IANA TZ names as additional info.

It's worth reading Falsehoods programmers believe about time, especially the parts that refer to UTC or GMT.



来源:https://stackoverflow.com/questions/59156514/asp-net-core-api-iso8601-not-parsed-in-local-time-when-suffix-is-z

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!