Changing DateTime.Kind with .SpecifyKind

Topics: Developer Forum
Sep 21, 2007 at 9:16 PM
I found a way to set the .Kind property in datetime variables without creating a new object.

I wrote methods that front PublicDomain.TzTimeZone functions ToUniversalTime() and ToLocalTime(). My methods have a datetime parameter par_dtDateTime and I am setting a .Kind to the value of this parameter to be sure that the conversion will be done correctly. For those who are not familiar with this area, Microsoft recently added a .Kind property to the DateTime structure. Its values can be Unspecified (default), UTC and Local. For more info, see http://msdn2.microsoft.com/en-us/library/system.datetime.kind.aspx.

When you call PublicDomain.TzTimeZone.ToUniversalTime(), the Unspecified Kind is converted to Local and the conversion works, but when you call PublicDomain.TzTimeZone.ToLocalTime(), the Unspecified Kind isn't changed to UTC and the conversion treats the time as already local. As a result, the time remains the same. To overcome this, Kevin pointed to me that you need to set .Kind to UTC, but you can't do just dt.Kind = DateTimeKind.Utc. You can either create a new datetime object and set the Kind in it this way:

Dim dt as DateTime
dt = New DateTime(par_dtDateTime.Ticks, DateTimeKind.Local)

or, as I've discovered, you can use .SpecifyKind like this:

pardtDateTime = DateTime.SpecifyKind(pardtDateTime, DateTimeKind.Local)

Do you see any issues with this? Of course, if your application observes .Kind, you shouldn't just overwrite it. My application treats all datetimes as local and does not observe .Kind.

Here's a complete example of my class method:

Function UTCUTCToLocal(ByVal pardtDateTime As DateTime) As DateTime
'PURPOSE:
' Sends back the local date and time corresponding to the UTC datetime.
'PARAMETERS:
' par_dtDateTime: UTC Datetime.
'RETURNS:
' User's local datetime.
'EXAMPLE:
' Dim dtDateTime as datetime
' dtDateTime = "2008-08-06 14:00"
' dtDateTime = goTr.UTC_UTCToLocal(dtDatetime)

pardtDateTime = DateTime.SpecifyKind(pardtDateTime, DateTimeKind.Utc)

'goTR.oUserTimeZone is initialized during project initialization to the user's Olson TZ locale:
' goTR.oUserTimeZone = PublicDomain.TzTimeZone.GetTimeZone(sTZLocale)
'where sTZLocale is a string like 'America/Chicago'
Return goTR.oUserTimeZone.ToLocalTime(par_dtDateTime)

End Function
Sep 21, 2007 at 9:18 PM
Wherever you see a change to or from italics, mentally insert an underscore character (_) .
Coordinator
Sep 22, 2007 at 10:04 AM
Edited Sep 22, 2007 at 10:06 AM
Hi Mario, you're absolutely right and there is no issue with using SpecifyKind. If you decompile the DateTime.SpecifyKind method, it actually does the same thing you were doing manually:

public static DateTime SpecifyKind(DateTime value, DateTimeKind kind)
{
    return new DateTime(value.InternalTicks, kind);
}

Therefore, there shouldn't be any issues, and in fact I think this would be the suggested way to change the kind of your dates in case Microsoft adds any optimizations into this method in the future (I couldn't see how, but you never know). It seems like it is already slightly optimized by using InternalTicks, which Ticks calls, so you're saving a few register moves (you're not saving stack frames since the Ticks property is most likely inlined by JIT).

Also, just a little background on your preface -- the stringency of PublicDomain's TZ code on honoring the Kind property is certainly annoying to the user of the code, but ultimately it enforces a bottom-up re-evaluation of DateTime assumptions in application code, which I think is a good thing.

Also, you mention that your application "treats all datetimes as local," but the method UTC_UTCToLocal seems to be expecting a UTC time, just from its name.

Along these lines, one feature I added early on is the public static bool TreatUnspecifiedKindAsLocal field in TzTimeZone. This is set to true by default, but if it is set to false (global setting), then any time a DateTime with Kind of Unspecified is passed in, an exception is thrown. This is in case you want to your code to be incredibly stringent on date/time semantics.

Also, you may want to look into TzDateTime instead of using DateTime and then converting to local or UTC when necessary. TzDateTime carries the semantics along with it, and exposes DateTimeLocal and DateTimeUtc properties which automatically do the conversion, so instead of:

DateTime myDt = ...
DateTime localDt = goTr.UTC_UTCToLocal(myDt);

You would just be using TzDateTime everywhere, passing them throughout your code, and then when the code requires a real DateTime, it simply gets the kind it wants and the conversion is automatically done. This is also good because the time zone is passed along with the DateTime. I've also overloaded ToString() be default to print "+/-HH:MM" to the end, so that in case you output the datetime in debug or anywhere else, it will be unambiguous. TzDateTime.Parse understands this notation and infers the time zone. There is always ToStringLocal() if you want normal ToString().

DateTime myDt = ...
 
TzDateTime myDt = new TzDateTime(myDt, goTR.oUserTimeZone);
 
// or:
TzDateTime myDt2 = TzDateTime.Parse("...", goTR.oUserTimeZone);
 
// get the local datetime
DateTime localDt = myDt.DateTimeLocal;
 
// get the UTC datetime
DateTime utcDt = myDt.DateTimeUtc;

Thanks,
Kevin
Sep 26, 2007 at 3:31 PM
Thanks. TZDateTime is a good suggestion. I like the overloaded ToString that shows the offset.