Search code examples
delphicross-platform

Implement Time in Milliseconds for Cross platform in Delphi


I have a piece of code in Delphi (in Windows) which I have implemented for cross platform using RAD Studio. Here is the code:

uses SysUtils, Windows, DateUtils;

type
  TSystem_Time = record
    wYear: word;
    wMonth: word;
    wDayOfWeek: word;
    wDay: word;
    wHour: word;
    wMinute: word;
    wSecond: word;
    wMilliseconds: word;
  end;

function DatumInSeconds2(y, m, d, h, mi, s: word): longword;
var
  RelDay: longword;
begin
  if y >= 1970 then
    dec(y, 1900)
  else if y < 70 then
    inc(y, 100); // 00..69 -> 100..169
  if (y < 70) or (y > 169) or (m < 1) or (m > 12) or (d < 1) or (d > 31) then
  begin
    DatumInSeconds2 := 0;
    exit;
  end;
  while m > 1 do
  begin
    dec(m);
    d := d + DaysInMonth[m];
    if (m = 2) and ((y mod 4) = 0) then
      inc(d); { gilt auch fuer Jahr 2000 }
  end;
  RelDay := longword(365) * longword(y - 70) + // Tage in den Vorjahren
    longword((y - 69) div 4) + // Schalttage in den Vorjahren
    d - 1;
  DatumInSeconds2 := ((RelDay * longword(24) + h) * longword(60) + mi) *
    longword(60) + s
end;

function GetTimeInMilliSeconds_Windows: Int64; // For windows
var
  stt: TSystemTime;
  secFrom1900: Int64;
  UtcSystemTime: TSystemTime;
  LocalFileTime: TFileTime;
  UTCFileTime: TFileTime;

begin
  Writeln('Enter GetTimeInMilliSeconds_Windows');
  GetTimeInMilliSeconds_Windows := 0;
  { W1035 Return value of function 'GetTimeInMilliSeconds' might be undefined }
  GetSystemTime(stt);
  SystemTimeToFileTime(stt, LocalFileTime);
  // Local System Time -> Local File Time
  if LocalFileTimeToFileTime(LocalFileTime, UTCFileTime) then
    // Local File Time -> UTC File Time
    if FileTimeToSystemTime(UTCFileTime, UtcSystemTime) then
    begin // Local File Time -> UTC System Time
      with UtcSystemTime do
      begin
        secFrom1900 := DatumInSeconds2(wYear, wMonth, wDay, wHour, wMinute,
          wSecond) + 2208988800;
        Writeln('secFrom1900', secFrom1900);
        Writeln('wYear', wYear);
        Writeln('wMonth', wMonth);
        Writeln('wDay', wDay);
        Writeln('wHour', wHour);
        Writeln('wMinute', wMinute);
        Writeln('wSecond', wSecond);
        Writeln('wMilliseconds', wMilliseconds);
        GetTimeInMilliSeconds_Windows := (secFrom1900 * 1000) + wMilliseconds;
      end;
    end
    else
    begin
      // In case the conversion to UTC fails, return the timestamp in local time.
      with stt do
      begin
        secFrom1900 := DatumInSeconds2(wYear, wMonth, wDay, wHour, wMinute,
          wSecond) + 2208988800;
        GetTimeInMilliSeconds_Windows := (secFrom1900 * 1000) + wMilliseconds;
      end;
    end;
end;

function GetTimeInMilliSeconds_Crossplatform: Int64; // crossplatform code
var
  dtNow, newdt: TDateTime;
  st: TSystem_Time;
  secFrom1900: Int64;

begin
  Writeln('Enter GetTimeInMilliSeconds_Crossplatform');
  GetTimeInMilliSeconds_Crossplatform := 0;
  dtNow := Now;
  newdt := TTimeZone.Local.ToUniversalTime(dtNow);
  DecodeDateTime(newdt, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute,
    st.wSecond, st.wMilliseconds);
  with st do
  begin
    secFrom1900 := DatumInSeconds2(wYear, wMonth, wDay, wHour, wMinute, wSecond)
      + 2208988800;
    // secFrom1900:= DateTimeToUnix(newdt) + 2208988800;
    Writeln('secFrom1900', secFrom1900);
    Writeln('wYear', wYear);
    Writeln('wMonth', wMonth);
    Writeln('wDay', wDay);
    Writeln('wHour', wHour);
    Writeln('wMinute', wMinute);
    Writeln('wSecond', wSecond);
    Writeln('wMilliseconds', wMilliseconds);
    GetTimeInMilliSeconds_Crossplatform := (secFrom1900 * 1000) +
      st.wMilliseconds;
  end;
end;

Here is the output:

Enter GetTimeInMilliSeconds_Windows
secFrom19003830898396
wYear2021
wMonth5
wDay25
wHour2
wMinute26
wSecond36
wMilliseconds551
Original_GetTimeInMilliSeconds>>3830898396551
sec3830898396
Enter GetTimeInMilliSeconds_Crossplatform
secFrom19003830918196
wYear2021
wMonth5
wDay25
wHour7
wMinute56
wSecond36
wMilliseconds560
Test_GetTimeInMilliSeconds>>3830918196560

So the issue here is when I try to execute them both differs in hour and Minute. Can anyone suggest some idea how to resolve this? or may be some another approach is needed to implement it? Thanks in advance .


Solution

  • First about the error in your current function GetTimeInMilliSeconds_Windows: Int64;. The error is not a fixed 3 hours as I said. It is 3 hours for me as I live in Finland and our time zone is UTC + 2 hours + 1 hour DST. The error is the same as the diff between UTC time and local time.

    The error happens because you call two different functions that convert/return from local time to UTC. Below the function calls and a brief description from MSDN.

    GetSystemTime(stt); - Retrieves the current system date and time in Coordinated Universal Time (UTC) format

    result: 2021, 5, 2, 25, 15, 23, 39, 219, Time: 15.23.39. My actual local time is 18.23.39

    SystemTimeToFileTime(stt, LocalFileTime); - Converts a system time to file time format. System time is based on Coordinated Universal Time (UTC).

    LocalFileTimeToFileTime(LocalFileTime, UTCFileTime) - Converts a local file time to a file time based on the Coordinated Universal Time (UTC).

    FileTimeToSystemTime(UTCFileTime, UtcSystemTime) - Converts a file time to system time format. System time is based on Coordinated Universal Time (UTC).

    result: 2021, 5, 2, 25, 12, 23, 39, 219, Time: 12.23.39

    This is my suggestion for a better (in my mind) solution. As you did not say what kind of use cases you have you may have to apply it as needed. Anyway, the result is nr of milliseconds since beginning of 1900.

    function GetTimeInMilliSeconds_Crossplatform: Int64;
    var
      dtNow: TDateTime;
      epoch: TDatetime;
      st: TSystemTime;
      secsbetween, msecsBetween: int64;
    begin
      // define
      epoch := EncodeDateTime(1900, 1, 1, 0, 0, 0, 0);
    
      // get current local date time
      dtNow := Now;
      // Memo1.Lines.Add('DtToS, loc: '+DateTimeToStr(dtNow));
    
      // or get current date time as UTC
      dtNow := TTimeZone.Local.ToUniversalTime(Now);
      // Memo1.Lines.Add('DtToS, utc: '+DateTimeToStr(dtNow));
    
      // Convert to date and time record if required
      DecodeDateTime(dtNow, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    
      // convert to milliseconds from 1900.01.01 00.00.00
      msecsBetween := MilliSecondsBetween(dtNow, epoch);
      // Memo1.Lines.Add(Format('Milliseconds since epoch = %d',[msecsBetween]));
      
      result := msecsBetween;
    end;