DateTimeToUnix in UTC?

后端 未结 2 1916
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-03 02:25

I need UTC variants of the functions DateTimeToUnix and UnixToDateTime, so a Chinese customer is able to interact with the server in Germany. Both

2条回答
  •  死守一世寂寞
    2021-01-03 03:15

    I think I have found some solutions for my question. All 3 solutions gave the same output, but I will try to find out which one is best and I will test it on several machines with different locales.

    Solution #1 using TzSpecificLocalTimeToSystemTime and SystemTimeToTzSpecificLocalTime works fine, but requires Windows XP and above:

    (Source: https://stackoverflow.com/a/15567777/3544341 , modified)

    // Statically binds Windows API functions instead of calling them dynamically.
    // Requires Windows XP for the compiled application to run.
    {.$DEFINE USE_NEW_WINDOWS_API}
    
    {$IFDEF USE_NEW_WINDOWS_API}
    function SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall; external kernel32 name 'SystemTimeToTzSpecificLocalTime';
    {$ELSE}
    function SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall;
    var
      h: HModule;
      f: function(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall;
    begin
      h := LoadLibrary(kernel32);
      if h = 0 then RaiseLastOSError;
    
      @f := GetProcAddress(h, 'SystemTimeToTzSpecificLocalTime');
      if @f = nil then RaiseLastOSError;
    
      result := f(lpTimeZoneInformation, lpUniversalTime, lpLocalTime);
    end;
    {$ENDIF}
    
    {$IFDEF USE_NEW_WINDOWS_API}
    function TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall; external kernel32 name 'TzSpecificLocalTimeToSystemTime';
    {$ELSE}
    function TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall;
    var
      h: HModule;
      f: function(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall;
    begin
      h := LoadLibrary(kernel32);
      if h = 0 then RaiseLastOSError;
    
      @f := GetProcAddress(h, 'TzSpecificLocalTimeToSystemTime');
      if @f = nil then RaiseLastOSError;
    
      result := f(lpTimeZoneInformation, lpLocalTime, lpUniversalTime);
    end;
    {$ENDIF}
    
    function UTCToLocalDateTime_WinXP(d: TDateTime): TDateTime;
    var
      TZI: TTimeZoneInformation;
      LocalTime, UniversalTime: TSystemTime;
    begin
      GetTimeZoneInformation(tzi);
      DateTimeToSystemTime(d,UniversalTime);
      SystemTimeToTzSpecificLocalTime(@tzi,UniversalTime,LocalTime);
      Result := SystemTimeToDateTime(LocalTime);
    end;
    
    function LocalDateTimeToUTC_WinXP(d: TDateTime): TDateTime;
    var
      TZI: TTimeZoneInformation;
      LocalTime, UniversalTime: TSystemTime;
    begin
      GetTimeZoneInformation(tzi);
      DateTimeToSystemTime(d,LocalTime);
      TzSpecificLocalTimeToSystemTime(@tzi,LocalTime,UniversalTime);
      Result := SystemTimeToDateTime(UniversalTime);
    end;
    

    Solution #2 as workaround for older operating systems does also work fine:

    (Source: http://www.delphipraxis.net/299286-post4.html )

    uses DateUtils;
    
    function GetDateTimeForBiasSystemTime(GivenDateTime: TSystemTime; GivenYear: integer): TDateTime;
    var
      Year, Month, Day: word;
      Hour, Minute, Second, MilliSecond: word;
    begin
      GivenDateTime.wYear := GivenYear;
      while not TryEncodeDayOfWeekInMonth(GivenDateTime.wYear, GivenDateTime.wMonth, GivenDateTime.wDay, GivenDateTime.wDayOfWeek, Result) do
        Dec(GivenDateTime.wDay);
    
      DecodeDateTime(Result, Year, Month, Day, Hour, Minute, Second, MilliSecond);
      Result := EncodeDateTime(Year, Month, Day, GivenDateTime.wHour, GivenDateTime.wMinute, GivenDateTime.wSecond, GivenDateTime.wMilliseconds);
    end;
    
    function GetBiasForDate(GivenDateTime: TDateTime): integer;
    var
      tzi: TIME_ZONE_INFORMATION;
    begin
      GetTimeZoneInformation(tzi);
      if (GivenDateTime < GetDateTimeForBiasSystemTime(tzi.StandardDate, YearOf(GivenDateTime))) and
         (GivenDateTime >= GetDateTimeForBiasSystemTime(tzi.DaylightDate, YearOf(GivenDateTime))) then
        Result := (tzi.Bias + tzi.DaylightBias) * -1
      else
        Result := (tzi.Bias + tzi.StandardBias) * -1;
    end;
    
    function UTCToLocalDateTime_OldWin(aUTC: TDateTime): TDateTime;
    begin
      Result := IncMinute(aUTC, GetBiasForDate(aUTC));
    end;
    
    function LocalDateTimeToUTC_OldWin(aLocal: TDateTime): TDateTime;
    begin
      Result := IncMinute(aLocal, GetBiasForDate(aLocal) * -1);
    end;
    

    Solution #3 using TTimeZone for users of newer versions of Delphi, does give the same results as the codes above:

    (Solution by David Heffernan, alas not possible in my current project, because I am bound to Delphi 6)

    uses DateUtils;
    
    {$IF Declared(TTimeZone)}
    function UTCToLocalDateTime_XE(aUTC: TDateTime): TDateTime;
    begin
      result := TTimeZone.Local.ToLocalTime(aUTC);
    end;
    
    function LocalDateTimeToUTC_XE(aLocal: TDateTime): TDateTime;
    begin
      result := TTimeZone.Local.ToUniversalTime(aLocal);
    end;
    {$IFEND}
    

    Now we can put all 3 solutions together! :-)

    function UTCToLocalDateTime(aUTC: TDateTime): TDateTime;
    begin
      {$IF Declared(UTCToLocalDateTime_XE)}
      result := UTCToLocalDateTime_XE(aUTC);
      {$ELSE}
        {$IFDEF USE_NEW_WINDOWS_API}
        result := UTCToLocalDateTime_WinXP(aUTC);
        {$ELSE}
        try
          result := UTCToLocalDateTime_WinXP(aUTC);
        except
          on E: EOSError do
          begin
            // Workaround for Windows versions older than Windows XP
            result := UTCToLocalDateTime_OldWin(aUTC);
          end
          else raise;
        end;
        {$ENDIF}
      {$IFEND}
    end;
    
    function LocalDateTimeToUTC(aLocal: TDateTime): TDateTime;
    begin
      {$IF Declared(LocalDateTimeToUTC_XE)}
      result := LocalDateTimeToUTC_XE(aLocal);
      {$ELSE}
        {$IFDEF USE_NEW_WINDOWS_API}
        result := LocalDateTimeToUTC_WinXP(aLocal);
        {$ELSE}
        try
          result := LocalDateTimeToUTC_WinXP(aLocal);
        except
          on E: EOSError do
          begin
            // Workaround for Windows versions older than Windows XP
            result := LocalDateTimeToUTC_OldWin(aLocal);
          end
          else raise;
        end;
        {$ENDIF}
      {$IFEND}
    end;
    

    An easy method to get the current UTC unix timestamp is

    function NowUTC: TDateTime;
    var
      st: TSystemTime;
    begin
      GetSystemTime(st);
      result := EncodeDateTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    end;
    
    function CurrentUnixUTCTimestamp: int64;
    begin
      result := DateTimeToUnix(NowUTC);
    end;
    

提交回复
热议问题