Unfortunately DateTime
is a weird type for Marshaling (or, at least, it has a pretty unexpected surprising behavior.)
First of all because it's a Auto
structure (and this makes it not-blittable and with all the consequences of the case) with just one single long
field.
You may think to wrap it inside a blittable struct (nothing new here, it's the common way to marshal non-blittable types):
public struct BlittableDateTime
{
private BlittableDateTime(long ticks)
{
_ticks = ticks;
}
public static implicit operator BlittableDateTime(DateTime value)
{
return new BlittableDateTime(value.Ticks);
}
public static implicit operator DateTime(BlittableDateTime value)
{
return new DateTime(value._ticks);
}
private readonly long _ticks;
}
So far so good, you may think. However we're converting a DateTime
(number of 100 ns ticks from 1/1/0001) to an 8 bytes integer value without any equivalent type in unmanaged world. In unmanaged world you may have: time_t
, FILETIME
, SYSTEMTIME
, DATE
and (many) others but none of them exactly matches granularity and range of .NET DateTime
. Even more annoying it's not actually a raw long long
value because some bits have a special meaning, from source code:
Bits 63-64: A four-state value that describes the DateTimeKind value of the date time...
You need a conversion, in this example I go with FILETIME
:
public static implicit operator BlittableDateTime(DateTime value)
{
return new BlittableDateTime(value.ToFileTime());
}
public static implicit operator DateTime(BlittableDateTime value)
{
return DateTime.FromFileTime(value._ticks);
}
Edit: how to use it? We defined two implicit operators then conversions to/from DateTime
are automatic, you do not need to directly manage FILETIME
structure in managed code (also note that constructor is private, all conversions are go through defined operators):
BlittableDateTime time1 = DateTime.UtcNow;
DateTime time2 = time1;
However we didn't define any comparison operators for this type. If you don't do it often you have two alternatives, first one is casting:
if ((DateTime)time1 == time2) {
// Do something...
}
Alternatively you can add a Value
property which returns DateTime
(to mimic Nullable<T>
usage):
public DateTime Value
{
get { return (DateTime)this; }
}
Used like this:
if (time1.Value == time2) {
// Do something...
}
One more note about conversions. Note that not every conversion is possible and - in this case - FILETIME
has a different range. FILETIME
begins at 1/1/1601 and with a 100 ns granularity it spans +/- 30,000 years (more or less) because it can be negative. DateTime
starts at 1/1/0001 and it effectively uses 62 bits of information (262 ticks) but maximum value is 31 December 9999.
Another problem: current implementation does not support negative values when converting back from FILETIME
then effective usable range is between 1 Jan 1601 (minimum positive FILETIME
) and 31 Dec 9999 (maximum DateTime
and DATE
value).
When working with dates don't forget that they're (almost) always associated with a calendar and some calendars may have different limits: for example Taiwan calendar begins at 1/1/0001 (which is 1/1/1912 in Gregorian calendar) and Um Al Qura calendar ends at 12/29/1450 (5/13/2029 in Gregorian calendar).