6

Assume we have action:

[HttpGet]
public Task<IActionResult> Foo(DateTime date)
{
    var utc = date.ToUniversalTime();
}

It seems like MVC framework by default convert UTC DateTime to Local (somewhere in middleware). How can I turn off this behaviour and get rid of additional transformations?

UPD:

Chrome dev. console (network tab) shows me such query parameter: date:2017-12-01T00:00:00.000Z

but in controller I see: {01/12/2017 03:00:00}

Dzmitry Martavoi
  • 6,867
  • 6
  • 38
  • 59
  • Can you post a watch value of the date coming over the wire? – Ross Bush Dec 11 '17 at 16:31
  • I doubt the value was changed in transit. Can you verify the date value sent from the client? How is it different from what you are seeing in your controller? – Ross Bush Dec 11 '17 at 16:34
  • 1
    @RossBush: see upd – Dzmitry Martavoi Dec 11 '17 at 16:38
  • 1
    And the local machine is set to UTC+3? – Ross Bush Dec 11 '17 at 16:42
  • 1
    @RossBush: exactly – Dzmitry Martavoi Dec 11 '17 at 16:43
  • Is there any change in behavior if your controller accepts a `DateTimeOffset` instead of `DateTime`? – Crowcoder Dec 11 '17 at 16:51
  • That "Z" or Zulu is specified. The lines could be crossed between what the web client is sending and what you are expecting. See this post along similar lines-->https://stackoverflow.com/questions/1820915/how-can-i-format-datetime-to-web-utc-format – Ross Bush Dec 11 '17 at 16:56
  • @Crowcoder: DateTimeOffset returns correct DateTime, UtcDateTime and LocalDateTime – Dzmitry Martavoi Dec 11 '17 at 17:02
  • @RossBush: So, what should I change on FE to pass correct UTC datetime? – Dzmitry Martavoi Dec 11 '17 at 17:02
  • I avoid web dates as strings as much as possible. I have always accepted a date and time of day while knowing the clients TimeZoneInfo. That way I can reliably build back up the date and convert it to the time zone in which it should be stored. Mixing client time zone math and server tome zone math has never worked for me so I will let someone else who has had success with it chime in. – Ross Bush Dec 11 '17 at 17:11
  • You can implement custom model binder for DateTime type and parse date times in whatever way you like – Evk Dec 11 '17 at 19:37
  • Use a custom model binder and/or `ModelBinderAttribute`? https://greatrexpectations.com/2013/01/10/custom-date-formats-and-the-mvc-model-binder/ – Caramiriel Dec 11 '17 at 21:02
  • Possible duplicate of [Passing UTC DateTime to Web API HttpGet Method results in local time](https://stackoverflow.com/questions/22581138/passing-utc-datetime-to-web-api-httpget-method-results-in-local-time) – Markeli Jul 24 '18 at 16:19

1 Answers1

1

DateTime should not be used when accurate timezone-based times are necessary. That's why DateTimeOffset exists. By default, DateTime.Kind is DateTimeKind.Unspecified. In other words, it's up to you, after post, to determine what it should be interpreted as. The problem is that you can really only assume DateTimeKind.Utc, as that's the only thing you can interpret as a DateTime correctly. Posting the user's local time leaves you stranded, because DateTimeKind.Local actually means the server's local time, which usually will not the be same as the client's.

However, even in HTML5, posting a full datetime with timezone is virtually impossible. Although input types such as datetime and datetime-local exist, they are not implemented in any major browser. If you want to post dates with times and timezone, then you'll actually need three properties on your view model:

public DateTime Date { get; set; }
public TimeSpan Time { get; set; }
public string TimeZone { get; set; }

The TimeZone property assumes you'll use a dropdown list composed of values from TimeZoneInfo.GetSystemTimeZones(). If you want to use a different setup, you'll need some way to map to/from those values, since that's all you have to work with in C# to get an offset. However, you could simply allow the user to post an offset, instead, but that's less user-friendly, and may be difficult for some users. In particular, they would need to understand not only what the concept "offset from UTC" means, but also that take into account the current status of daylight savings time and adjust that offset according to the actual date.

Regardless, each of these properties can easily be mapped to a input type: date/time and a select/time in the case of the TimeZone, depending on whether you go with a dropdown or an manual offset entry. They are also all easily mapped back onto your view model by the modelbinder following a post. You then just need to create a DateTimeOffset from that information, which is what you'd actually persist on your entity class.

var offset = TimeZoneInfo
    .FindTimeZoneById(model.TimeZone)
    .GetUtcOffset(model.Date)
var dateTimeOffset = new DateTimeOffset(model.Date.Add(model.Time), offset);
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    "That's why DateTimeOffset exists." But DateTimeOffset doesn't offer a timezone-based date/time either; it only captures the offset at a single point in time, as you mention later. I think it would be better not to give the impression that DateTimeOffset includes a real time zone. It's not really clear why you've got Date and Time separated in your model either. (Doing so makes your use of GetUtcOffset invalid, IMO.) DateTime is fine to handle either of them. What *is* important is to make it clear whether that model is meant to represent local time or not. – Jon Skeet Dec 11 '17 at 20:42
  • Your final piece of code looks like it's expecting the model to represent a local time - but you don't mention talking about local date/time values which are repeated or skipped, or the situations in which it's best to use local date/time values vs UTC timestamps. – Jon Skeet Dec 11 '17 at 20:43
  • Actually, I'm not really interested in client time zone, but instead, I would like to treat passed DateTime as UTC (coz, 'Z' letter at the end of the timestamp means it must be UTC). I can easily configure JSON.NET serializer to handle UTC dates in such manner, but I don't understand why there is no way to say MVC not to convert to local datetime by default... – Dzmitry Martavoi Dec 11 '17 at 20:53