Today at work I was trying to untangle one big bowl of spaghetti called DateTime. It’s the C/AL DateTime I am talking about, not System.DateTime from .NET.
The problem with C/AL DateTime is that no matter what you do it’s, according to documentation, “always displayed as local time”.
Another problem with C/AL DateTime is that C/AL is a bit rude when it comes to System.DateTime: whenever C/AL compiler (or runtime) encounters a value of System.DateTime it’s automatically converted to C/AL DateTime.
When you combine those two problems, you get the following problem: even though System.DateTime is perfectly capable of handling time in both UTC or local kind, C/AL isn’t.
To prove this point, just run this:
MESSAGE(‘Current local time: %1\Current UTC time: %2’,SystemDateTime.Now,SystemDateTime.UtcNow);
It will show this:
And I am currently sitting in a UTC+1 time zone, mind you.
The particular issue I had today was to calculate Unix Time from current system time. The C# code for this is extremely simple:
var unixTime = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
However, C/AL is no friend of this. Firstly, it doesn’t allow you to call Subtract on UtcNow, because UtcNow becomes C/AL DateTime, and C/AL compiler doesn’t “see” its Subtract method. Secondly, even if it did allow me to call Subtract, it would fail because UtcNow is in fact my local time, courtesy of NAV runtime.
Thus, this is wrong:
So, two problems to solve here.
First problem is easily solved through reflection. I need to take the Subtract method out of the System.DateTime type:
To properly call Subtract we need to pass an array of arguments to the MethodInfo. Since this method receives one argument, I can do this:
Now, I need to assign something to the 0-index element of this array. If you say this:
Then this won’t work. This will return DateTime represented as local time, and we need either Utc or Unspecified kind.
One thing you can do is this:
Okay, now we are ready to invoke the Subtract method, however we need to specify what to subtract from. That would be current UTC date time.
I did it like this:
Then, finally you get the TotalSeconds property:
Much of this would not be required in C/AL should C/AL compiler treat System.DateTime as what it actually is, not attempting the clumsy implicit conversion to C/AL DateTime. The same problem exists for System.String and you need to get a little help from reflection to access what would (and should!) be accessible if C/AL compiler wasn’t attempting to be smarter than it should be.
Format(DateTime,0,9) does give date time in UTC (XML with Z) and can help in certain scenarios directly in C/AL. (Which I learnd from a amazing Blogger… https://vjeko.com/formatting-xml).
Yes, but that’s string. The problem is properly getting it in a DateTime variable.
Pingback: Getting out of the DateTime mess, or how to get Utc DateTime in C/AL – Nov 24, Vjeko.com |
Pingback: Getting out of the DateTime mess, or how to get Utc DateTime in C/AL | Pardaan.com
Before you get too far along with System.DateTime you might want to look at the NodaTime library, especially if you are going to have to manage multiple timezones.
dunno where my prev. comment is… again:
Use DateTimeOffset instead of Datetime 🙂
The reason is explained correctly (stumbled ofer this problem some months ago): NAV treats all times independent of the Kind-Property of DateTime-Object.
Hello,
this is my Solution:
// XmlConvert = System.Xml.XmlConvert.’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
// XmlDateTimeSerializationMode = System.Xml.XmlDateTimeSerializationMode.’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
UTCDate := XmlConvert.ToDateTime(FORMAT(LocalNAVDate,0,9), XmlDateTimeSerializationMode.Utc);
Hi! Great solution! 🙂 I have my own solution too, (it’s required more code, but skip string conversion), it’s kind of workaaround:
NetDateTime := NetDateTime.UtcNow;
dur:= NetDateTime.Millisecond;
dur+= NetDateTime.Second *1000;
dur+= NetDateTime.Minute *60000;
dur+= NetDateTime.Hour *3600000;
CurrentDateTime := CREATEDATETIME(DMY2DATE(NetDateTime.Day, NetDateTime.Month, NetDateTime.Year), 0T) + dur;
Thanks for sharing!
You could also make a “Old School” solution without .NET
DTText:= FORMAT(CREATEDATETIME(TODAY,TIME),0,9);
EVALUATE(Year,COPYSTR(DTText,1,4));
EVALUATE(Month,COPYSTR(DTText,6,2));
EVALUATE(Day,COPYSTR(DTText,9,2));
_Date:= DMY2DATE(Day,Month,Year);
EVALUATE(_Time,COPYSTR(DTText,12,5));
_DT:= CREATEDATETIME(_Date,_Time);
Thanks! I think 1 line of code is missing, just before UnixTimeStamp:
TypeOfTimeSpan := GETDOTNETTYPE(TimeSpan);
Hi Vjeko,
Thanks for this post!
Can you please share this code unit object with me.
please………….. My email: me@tabrezajaz.com / tabrezajaz@gmail.com
Hi,
Sorry, but I cannot do this. What you have here is what you get, and I can’t provide one-on-one help if you can’t make anything out of the stuff that’s publicly available here on my blog. Hope you understand that.