DateTimeOffset (Strict)
This morning my buddy kirillkos ran into a problem.
Problem code
Here is his code:
classEvent {
publicstring Message {get;set;}
public DateTime EventTime {get;set;}
}
interfaceIEventProvider {
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
classEvent {
publicstring Message {get;set;}
public DateTimeOffset EventTime {get;set; }
}
DateTimeOffset
wonderful 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:
classEvent {
publicstring Message {get;set;}
public DateTimeOffset EventTime {get;set; }
}
IEnumerable<Event> GetEvents()
{
returnnew[] {
new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"},
};
}
This is because the DateTimeOffset
implicit type casting operator is defined:
// Local and Unspecified are both treated as LocalpublicstaticimplicitoperatorDateTimeOffset (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 hammerstatic 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>publicreadonlystruct DateTimeOffsetStrict
{
private DateTimeOffset Internal { get; }
privateDateTimeOffsetStrict(DateTimeOffset @internal)
{
Internal = @internal;
}
publicstaticimplicitoperatorDateTimeOffsetStrict(DateTimeOffset dto)
=> new DateTimeOffsetStrict(dto);
publicstaticimplicitoperatorDateTimeOffset(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 DateTime
will 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:
classEvent {
publicstring Message {get;set;}
public DateTimeOffsetStrict EventTime {get;set; }
}
IEnumerable<Event> GetEvents()
{
returnnew[] {
new Event() {EventTime = DateTimeOffset.Now, Message = "Hello from unknown time!"},
};
}
but not like this:
IEnumerable<Event> GetEvents()
{
returnnew[] {
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 UnsafeHtml
that was implicitly converted from a string, but I IHtmlString
could convert it back to a string or only by calling the sanitizer.