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

    DateTimeOffsetwonderful 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 : DateTimeOffsetcan 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 DateTimeOffsetimplicit type casting operator is defined:


    // 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 menailsappropriate 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 improvisesome 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 DateTimeOffset, but an attempt to implicitly convert it from DateTimewill cause a compilation error, conversions from DateTime are possible only explicit ones. 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 a type UnsafeHtmlthat was implicitly converted from a string, but I IHtmlStringcould convert it back to a string or only by calling the sanitizer.


    Also popular now: