Showing posts with label DateTimeUtil. Show all posts
Showing posts with label DateTimeUtil. Show all posts

Friday, November 2, 2012

utcDateTime and Timezone Issues with Dynamics AX 2012

The Insanity of AX Timezones and DateTimeUtil

I am working on an implementation that imports employee data. Some of the data is employment related dates. Many of these dates use the utcDateTime type. When importing with a date I found dates to be off by one day. I figured it was related to the timezone so I have done some experimentation and found some shocking results.

The first issue is the msdn documentation seems to be misleading if not incorrect. This document states: 
When you are programming with dates, Best Practices are ... Use DateTimeUtil::getSystemDateTime instead of systemDateGet or today... Only DateTimeUtil::getSystemDateTime compensates for the time zone of the user.
However, when I execute the following code on November 2nd at 3:26 PM:

utcDateTime testDateTime;        
testDateTime = DateTimeUtil::getSystemDateTime();
info(
    strFmt(
        'testDateTime is %1 using timezone %2.', 
        testDateTime, 
        enum2str(
            DateTimeUtil::getOriginatingTimeZone(
                testDateTime)
            )
        )
    );
I am surprised to get this:
testDateTime is 11/2/2012 08:26:01 pm using timezone (GMT) Casablanca, Monrovia, Reykjavik.
The dateTimeUtils::getSystemDateTime did in fact compensate for the time zone of the user but not as I expected. I expected it to return a utcDateTime with the time system time in my timezone. I can live with this by simply applying my timezone. (I'll cover how to do this and why it doesn't work a little later.)

However, my issue was with importing times in a CSV file. So let's try this:
testDateTime = str2datetime('11/2/2012 15:26:00',213);
What I get is predictable:
testDateTime is 11/2/2012 03:26:00 pm using timezone (GMT) Casablanca, Monrovia, Reykjavik.
Since I didn't specify a timezone, GMT is assumed for the time I created. So let's apply a timezone offset to this time. My timezone is CST which is GMT minus 6 hours. The timezone enum for this is Timezone::GMTMINUS0600CENTRALTIME.
testDateTime =  DateTimeUtil::applyTimeZoneOffset(testDateTime,
    DateTimeUtil::getUserPreferredTimeZone());
I can't figure out what how I got this:
testDateTime is 11/2/2012 10:26:00 am using timezone (GMT) Casablanca, Monrovia, Reykjavik. 
There are two serious things wrong with this. First,  the time zone for the utcDateTime is still GMT. How can I possibly pass that data around and know if need to apply the timezone or not. Second, if you do the math you will see this is 5 hours different not 6.

There is another way to create a utcDateTime and apply a time zone offset so I thought I would try that. The DateTimeUtil class has a static method that will create a utcDateTime for you. The DateTimeUtil::newDateTime function takes the parameters. The third (optional) parameter is a Timezone enum value. The other two are a date and a time. I figure I can read the date string from the import, add a constant time value and apply the timezone and get a utcDateTime that is correct.

Here is the code:
DateTimeUtil::newDateTime(
    str2Date('11/2/2012', 213),
    str2time('15:26:00'),
    Timezone::GMTMINUS0600CENTRALTIME);
Maybe it was unreasonable for me to expect that I could create a utcDateTime value of November 2nd, 3:26 PM CST with that code, but that is what I expected. Instead I got this:
testDateTime is 11/2/2012 10:26:00 am using timezone (GMT) Casablanca, Monrovia, Reykjavik.
I didn't initially look at the documentation for that method because  simply saw the parameter in the intellisense and used it. With the unexpected result, I went to the documentation to see it names the third parameter "tzOffsetToRemove". Well there's my problem. I takes the time I want to use and removes the timezone offset. Now we have another problem. It actually added the timezone offset instead of removing it. Remember the timezone offset is minus 6 hours. It actually subtracted the (incorrect) 5 hours. So instead of having a GMT time of 9:26 PM I have ... oh who knows what I have.

To show how insane this is, let's re-apply the timezone offset. Here's the code:
testDateTime = DateTimeUtil::applyTimeZoneOffset(testDateTime,
    Timezone::GMTMINUS0600CENTRALTIME);
What do you think this will give me? Here it is:

testDateTime is 11/2/2012 05:26:00 am using timezone (GMT) Casablanca, Monrovia, Reykjavik.
Of course, it removed the (incorrect) 5 hours again. 

The final straw is this. If I create a GMT utcDateTime and then "remove" the timezone offset it actually sets the timezone to the one "removed" while actually removing the offset. Here is the code:
testDateTime = str2datetime('11/2/2012 15:26:00',213);
testDateTime = DateTimeUtil::removeTimeZoneOffset(testDateTime,
    DateTimeUtil::getUserPreferredTimeZone());
You will recognize the first line of code from my first attempt. This takes a string and converts it to a GMT utcDateTime. Then, I call DateTimeUtil::removeTimeZoneOffset telling it to remove the CST timezone offset, hoping to end up with a GMT date/time. Here is what I get:
testDateTime is 11/2/2012 08:26:00 pm using timezone (GMT-06:00) Central Time (US & Canada). 
I finally have a utcDateTime for CST but it contains the GMT time. Go figure.

So, the system attempts to provide time localization but instead manages to simply screw everything up. I don't know why they didn't just simply use the functionality built into the Windows OS since the client and server only run on Windows. I suspect the one hour difference is related to DST but I have not yet figured out how to take that into consideration.

Besides simply having buggy code, I think Microsoft has outdone themselves this time in complicating obfuscating their API. I would suggest they start by thinking how someone would want to use these utilities. Maybe someone would actually like to simply create a data and time for a specific timezone and have the value actually carry the timezone they specify.

Tory

Update:
I had to quit yesterday before I could test my last idea. I tested it this morning and found that by reapplying the removed timezone offset I finally ended up with the November 2nd 3:26 PM CST I was looking for. Here is the code and the result for the final (work around) solution:
testDateTime = str2datetime('11/2/2012 15:26:00',213);
testDateTime = DateTimeUtil::removeTimeZoneOffset(testDateTime,
    DateTimeUtil::getUserPreferredTimeZone());
testDateTime = DateTimeUtil::applyTimeZoneOffset(testDateTime, 
    DateTimeUtil::getUserPreferredTimeZone());
Result:
testDateTime is 11/2/2012 03:26:00 pm using timezone (GMT-06:00) Central Time (US & Canada).