📜 ⬆️ ⬇️

DateTimeOffset (Strict)

This morning my buddy kirillkos ran into a problem.


Problem code


Here is his code:


class Event { public string Message {get;set;} public DateTime EventTime {get;set;} } interface IEventProvider { IEnumerable<Event> GetEvents(); } 

And then there are many, many implementations of IEventProvider , data from different tables and databases.


Problem : in all these bases, all in different time zones. Accordingly, when trying to display events on the UI, everything is horribly confused.


Glory to Hejlsberg, we have types, let them save us!


Attempt 1


 class Event { public string Message {get;set;} public DateTimeOffset EventTime {get;set; } } 

DateTimeOffset remarkable type, it stores offset information relative to UTC. It is perfectly supported by MS SQL and Entity Framework (and in version 6.3 it will be supported even better ). In our code style, it is mandatory for all new code.


Now we can collect information from these providers and consistently, relying on the types, bring everything to the UI. Victory!


Problem : DateTimeOffset can implicitly convert from DateTime .
The following code compiles beautifully:


 class Event { public string Message {get;set;} public DateTimeOffset EventTime {get;set; } } IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"}, }; } 

This is because the implicit type casting operator is defined in DateTimeOffset :


 // Local and Unspecified are both treated as Local public static implicit operator DateTimeOffset (DateTime dateTime); 

This is not what we need. We wanted the programmer to think when writing the code: “But in what own time zone did this event happen? Where does the zone come from? ”. Often completely from other fields, sometimes from related tables. And here it is very easy to make a mistake without thinking .


Damn implicit conversions!


Attempt 2


Since I heard about hammer static analyzers everything seems to me nails appropriate occasions for them. We need to write a static analyzer that bans this implicit conversion, and explains why ... It looks like a lot of work. Anyway, this is the work of the compiler, to check the types. For now, let's postpone this idea as wordy.


Attempt 3


Now if we would be in the F # world, said kirillkos .
We would then:


 type DateTimeOffsetStrict = Value of DateTimeOffset 

And further did not invent improvise some kind of magic would save us. It’s a pity that in our office we don’t write F #, and we don’t really know kirillkos :-)


Attempt 4


Is it really impossible to do something like this in C #? You can, but you are tormented to convert back and forth. Stop, but we just saw how you can make implicit conversions!


 /// <summary> /// Same as <see cref="DateTimeOffset"/> /// but w/o implicit conversion from <see cref="DateTime"/> /// </summary> public readonly struct DateTimeOffsetStrict { private DateTimeOffset Internal { get; } private DateTimeOffsetStrict(DateTimeOffset @internal) { Internal = @internal; } public static implicit operator DateTimeOffsetStrict(DateTimeOffset dto) => new DateTimeOffsetStrict(dto); public static implicit operator DateTimeOffset(DateTimeOffsetStrict strict) => strict.Internal; } 

The most interesting thing about this type is that it is implicitly converted back and forth from a DateTimeOffset , but an attempt to implicitly convert it from DateTime will cause a compilation error; conversions from DateTime can only be explicit. The compiler cannot call a “chain” of implicit conversions, if they are defined in our code, it is forbidden to it by the standard ( quoted in SO ). That is, it works like this:


 class Event { public string Message {get;set;} public DateTimeOffsetStrict EventTime {get;set; } } IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTimeOffset.Now, Message = "Hello from unknown time!"}, }; } 

but not like this:


 IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"}, }; } 

What we needed!


Total


We do not know yet whether we will implement it. Only everyone was accustomed to DateTimeOffset, and now it is a bit dumb to replace it with our type. Yes, and probably emerge problems at the level of EF, ASP.NET parameter binding and another thousand places. But the solution itself seems interesting to me. I used similar tricks to monitor user input security - I did the UnsafeHtml type, which is implicitly converted from a string, but I can only convert it back to a string or IHtmlString by calling the sanitizer.



Source: https://habr.com/ru/post/438946/