Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert Delphi TDateTime to String with microsecond precision

I need to convert a TDateTime to a String with microsecond precision. In case of millisecond precision it is possible to use formatting:

DateTimeToString(Result, 'd.m.yyyy hh:nn:ss.zzz', dateTime);

but I need three more digits (microseconds).

It is possible to take the fractional part and divide it by 1/86400/1000000 but I'm looking for more efficient way to convert it.

like image 824
nfc1 Avatar asked Feb 04 '16 11:02

nfc1


1 Answers

The accuracy of a date-time varies depending how far away from "zero" you are.

A Delphi TDateTime is actually an 8-byte floating point Double, with zero being 12/30/1899 12:00:00 am.

We can figure out the precision of a TDateTime by incrementing the floating point datetime by the smallest quantum possible:

function AddQuantumToDateTime(const dt: TDateTime): TDateTime;
var
   overlay: Int64 absolute Result;
begin
   Result := dt;
   overlay := overlay+1;
end;

With this, we can figure out the smallest increment that a TDateTime can even handle. It varies with the date being used, as the further you are from zero, the larger the quantum amount is:

  • 12/31/1899: ±0 ns
  • 1/1/1900: ±0 ns
  • 1/1/1970: ±314 ns
  • 1/1/2000: ±629 ns
  • 1/1/2016: ±629 ns
  • 1/1/2038: ±629 ns
  • 1/1/3000: ±5,029 ns
  • 1/1/4000: ±10,058 ns
  • 1/1/5000: ±20,117 ns
  • 1/1/6000: ±20,117 ns
  • 1/1/7000: ±20,117 ns
  • 1/1/8000: ±40,233 ns
  • 1/1/9000: ±40,233 ns
  • 1/1/9999: ±40,233 ns

So for the time being, a DateTime can give you a resolution of about half a microsecond.

While the Windows FILETIME structure does support a resolution of 100ns, the SYSTEMTIME structure only supports down to the millisecond:

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

Microsoft SQL Server's new datetime2(7) returns datetime strings with up to seven digit (100 ns) of fractional second accuracy:

SELECT CAST('20160802' AS datetime2(6)) AS TheNow

TheNow
==========================
2016-08-02 00:00:00.000000

Your question then is how to convert a TDateTime to a string that contains microsecond (billionths of a second) precision. You already have your answer:

function DateTimeToStrUs(dt: TDatetime): string;
var
    us: string;
begin
    //Spit out most of the result: '20160802 11:34:36.'
    Result := FormatDateTime('yyyymmdd hh":"nn":"ss"."', dt);

    //extract the number of microseconds    
    dt := Frac(dt); //fractional part of day
    dt := dt * 24*60*60; //number of seconds in that day
    us := IntToStr(Round(Frac(dt)*1000000));

    //Add the us integer to the end:
    // '20160801 11:34:36.' + '00' + '123456'
    Result := Result + StringOfChar('0', 6-Length(us)) + us;
end;
  

Where:

DateTimeToStrUs(Now)

Returns:

20160802 11:34:36.482364

like image 89
Ian Boyd Avatar answered Oct 18 '22 15:10

Ian Boyd