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;