Some notes on MS SQL timestamps in Dynamics NAV 2016.

My data have a timestamp. A datetime datatype. And it is vital to me as business process dictates so some logic decisions must be made based on record datetime.  What I have noticed is that all my datetimes ends with 0 or 3 or 7 milliseconds. Like 01/01/2017 20:54:253. Or 01/01/2017 20:54:250. Or 01/01/2017 20:54:257. But never 01/01/2017 20:54:254.

Quite strange, as I have assumed the tick is a millisecond. Which is true in NAV – the smallest tick is a millisecond (1/1000 second) indeed. So why then always 0 3 or 7 in the database? I quickly created a nav code like this:

OnRun()
MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyTable."Create Date" := MyDatetime + i;
  MyTable.INSERT;
END;

What would you expect of this code? I was expecting 1000 records created with one tick datetime difference. I’m using CLEAR(MyTable); as my table has autoincrement primary key, so MyTable.INIT would not work as INIT does not clearing the primary key value, so INSERT will fail then as we let SQL server to decide primary key.

And indeed, I had 1000 records. And all of them ends with 0, 3 or 7. Mystery? There must be no mystery in IT. So I amended the code then to dump data to simple text file same time as I am inserting to table.

MyFile.TEXTMODE := TRUE;
MyFile.WRITEMODE := TRUE;
MyFile.CREATE('c:\temp\test.txt');
MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyTable."Create Date" := MyDatetime + i;
  MyTable.INSERT;
  MyFile.WRITE(FORMAT(MyTable."Create Date", 0, '<Day,2>/<Month,2>/<Year4> <Hours24,2>:<Minutes,2>:<Seconds,2><Second dec.><Comma,.>'));
END;
MyFile.CLOSE;

And results were truly surprising.

Text File SQL table
30/08/2017 11:58:29.271 30/08/2017 11:58:29.270
30/08/2017 11:58:29.272 30/08/2017 11:58:29.273
30/08/2017 11:58:29.273 30/08/2017 11:58:29.273
30/08/2017 11:58:29.274 30/08/2017 11:58:29.273
30/08/2017 11:58:29.275 30/08/2017 11:58:29.277
30/08/2017 11:58:29.276 30/08/2017 11:58:29.277
30/08/2017 11:58:29.277 30/08/2017 11:58:29.277
30/08/2017 11:58:29.278 30/08/2017 11:58:29.277
30/08/2017 11:58:29.279 30/08/2017 11:58:29.280
30/08/2017 11:58:29.280 30/08/2017 11:58:29.280

 

As you can see – in the file it is one tick 1/1000 of second, as expected. But in SQL the tick is about 1/300 second and rounded to nearest. Thank you, MS, for your infinite wisdom!

I can prove the point – https://stackoverflow.com/questions/715432/why-is-sql-server-losing-a-millisecond

Of course, there is a SQL datatype datetime2 with precision of 100 nanoseconds. But it is not available in NAV.

So, then conclusion: never use NAV datetime if you want one tick precision. It is not here. Yes, NAV can handle 1/1000 of a second, but SQL cannot!

To prove the point, I have added two new fields to my table: Create Date and Create Time, with NAV datatypes Date and Time respectively. And slightly amended the code

MyDatetime := CURRENTDATETIME;
FOR i := 1 TO 1000 DO BEGIN
  CLEAR(MyTable);
  MyDatetime += 1;
  MyTable."Create DateTime" := MyDatetime;
  MyTable."Create Date" := DT2DATE(MyDatetime);
  MyTable."Create Time" := DT2TIME(MyDatetime);
  MyTable.INSERT;
END;

I somehow was under impression that on SQL end NAV Time datatype handled differently. But it is not. On SQL end NAV Time = SQL datatype DateTime. Which is rounded to 1/300 of a second.

So, there is no way in NAV 2016, 2017 or any other version of NAV to record a time with precision of one tick, I mean 1/1000 second. No way.

Well, there is. A text string of length 23 and some stupid weird code to format datetime received from external sources (where it is high-precision) to string, and back to datetime when our external source read nav data via webservice. I know, it is ugly. But I cannot see better way.

DatetimeToString(MyDatetime : DateTime) : Text
IF MyDatetime = 0DT THEN
  MyDatetime := CREATEDATETIME(01011753D, 000000T);
String := FORMAT(MyDatetime, 0, '<Day,2>/<Month,2>/<Year4> <Hours24,2>:<Minutes,2>:<Seconds,2><Second dec.><Comma,.>');
IF STRLEN(String) = 19 THEN
  String := String + '.';
IF STRLEN(String) < 23 THEN
  String := PADSTR(String, 23, '0');
EXIT(String);
StringToDatetime(MyDatetimeAsString : Text) : DateTime
EVALUATE(Day, COPYSTR(MyDatetimeAsString, 1, 2));
EVALUATE(Month, COPYSTR(MyDatetimeAsString, 4, 2));
EVALUATE(Year, COPYSTR(MyDatetimeAsString, 7, 4));
EVALUATE(MyTime, COPYSTR(MyDatetimeAsString, 12, 12));
MyDate := DMY2DATE(Day, Month, Year);
MyDatetime := CREATEDATETIME(MyDate, MyTime);
EXIT(MyDatetime);

And there is another issue – the datetime stored in NAV always stored in UTC and then formatted to local time on NAV UI. On webservice it is UTC though. So, then all this handling needs to be implemented in your code when you do format datetime to string and vice versa, or to be precise, when you display this data in NAV.

I will cover this in separate post though.

UPDATE: I actually see a better way of managing this. Still using strings, but utilizing the power of .NET – so cleaner and faster code then. New post will follow on this.