Search code examples
datetimedelphigithub-apiutc

What am I doing wrong converting these times from UTC to Local?


Using the GitHub API, timestamps are returned in UTC. I'm using the following to convert these into a Delphi TDateTime...

  with TXSDateTime.Create do
    try
      XSToNative('2019-07-27T19:33:02Z');
      Result:= AsDateTime;
    finally
      Free;
    end;

I don't recall where I found that function.

The value 2019-07-27T19:33:02Z comes directly from the GitHub API on a particular repository's "pushed_at" field (last pushed). After converting it using the function above, I get (formatted to string):

2019-07-27 11:33:02

Now I take this value and I try to convert it to local time. My local time is EST, and I know for a fact that particular repository I last pushed it yesterday at 3:33 PM. I confirmed that directly on GitHub's website.

I have used both methods as found in the top two answers on this question. Specifically the functions LocalDateTimeFromUTCDateTime and UnivDateTime2LocalDateTime However, the results are both backwards. Rather than adding 4 hours, both methods are instead subtracting 4 hours.

So the result I get from both is

2019-07-27 07:33:02

I know for a fact I didn't do that push at 7:33 AM. I wasn't even awake yet.

In fact, if I use the wrong function DateTime2UnivDateTime() then I actually get the correct result.

What am I doing wrong here, and how do I get the correct result for local time?

I barely understand the science behind time zones.


EDIT

It looks like that first function is resulting in double the time shift, so I didn't realize it was already attempting to convert to local time. But rather than subtracting 4 hours, it subtracted 8 hours. So I was thrown off and thinking I still needed to convert it to local time. But why is it shifting twice?


Solution

  • 1) convert ISO time to Delphi TDateTime:

    function ISOToDateTime(const AISODateTime: string): TDateTime;
    var
      I: Integer;
      VDate, VTime: TDateTime;
      VFormatSettings: TFormatSettings;
    begin
      // ISO format: 2009-07-06T01:53:23Z
    
      VFormatSettings.DateSeparator := '-';
      VFormatSettings.ShortDateFormat := 'yyyy-mm-dd';
      VFormatSettings.TimeSeparator := ':';
      VFormatSettings.ShortTimeFormat := 'hh:nn:ss';
    
      I := Pos('T', AISODateTime); 
      VDate := StrToDate(Copy(AISODateTime, 1, I - 1), VFormatSettings);
      VTime := StrToTime(Copy(AISODateTime, I + 1, 8), VFormatSettings);
    
      Result := Trunc(VDate) + Frac(VTime);
    end;
    

    2) convert UTC time to Local time:

    function UniversalToLocalTime(const AUtcTime: TDateTime): TDateTime;
    
      function _GetSystemTzOffset: Extended;
      var
        VTmpDate: TDateTime;
        ST1, ST2: TSystemTime;
        TZ: TTimeZoneInformation;
      begin
        GetTimeZoneInformation(TZ);
        DateTimeToSystemTime(AUtcTime, ST1);
        SystemTimeToTzSpecificLocalTime(@TZ, ST1, ST2);
        VTmpDate := SystemTimeToDateTime(ST2);
        Result := MinutesBetween(VTmpDate, AUtcTime) / 60;
        if VTmpDate < AUtcTime then begin
          Result := -Result;
        end;
      end;
    
    var
      VOffset: Extended;
    begin
      VOffset := _GetSystemTzOffset;
      if VOffset = 0 then begin
        Result := AUtcTime;
      end else begin
        Result := IncHour(AUtcTime, Trunc(VOffset));
        Result := IncMinute(Result, Round(Frac(VOffset) * 60));
      end;
    end;