Getting out of the DateTime mess, or how to get Utc DateTime in C/AL

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:

image

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:

image

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:

image

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:

image

Now, I need to assign something to the 0-index element of this array. If you say this:

image

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:

image

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:

image

Then, finally you get the TotalSeconds property:

image

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.

Vjeko

Vjeko has been writing code for living since 1995, and he has shared his knowledge and experience in presentations, articles, blogs, and elsewhere since 2002. Hopelessly curious, passionate about technology, avid language learner no matter human or computer.

This Post Has 13 Comments

  1. Bruno

    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).

    1. Vjeko

      Yes, but that’s string. The problem is properly getting it in a DateTime variable.

  2. Kenneth Fuglsang

    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.

  3. Chris

    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.

  4. Matthias König

    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);

    1. Pablo

      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;

      1. Vjeko

        Thanks for sharing!

  5. Lars Welander

    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);

  6. Erica

    Thanks! I think 1 line of code is missing, just before UnixTimeStamp:

    TypeOfTimeSpan := GETDOTNETTYPE(TimeSpan);

    1. Vjeko

      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.

Leave a Reply