UTC <> Local time conversion functions not respecting DST

In Core Source\System.DateUtils.pas there are 4 UTC conversion functions:

function UniversalTimeToLocal(UT: TDateTime): TDateTime;
function UniversalTimeToLocal(UT: TDateTime; TZOffset : Integer): TDateTime;
function LocalTimeToUniversal(LT: TDateTime): TDateTime;
function LocalTimeToUniversal(LT: TDateTime;TZOffset: Integer): TDateTime;

Those with TZOffset as a 2nd parameter work correct while those without do not respect the daylight saving time (DST) of the passed UT/LT TDateTime parameter.

Here is why:

function LocalTimeToUniversal(LT: TDateTime) calls this:

Result:=LocalTimeToUniversal(LT,-GetLocalTimeOffset);

See that GetLocalTimeOffset is not getting any parameter!

Function GetLocalTimeOffset returns this:

Result:=TJSDate.New.getTimezoneOffset();

This is just the time zone offset at the CURRENT browser time at the current location this function is called!

But if, say, this gets called during summer time, but the actual UT/LT parameter holds a winter date, the UTC calculation is wrong by the winter/summer time zone difference.

How to reproduce:

Var
 EditDT,
 UTCDT   : TDateTime;
 TZO     : NativeInt;
 S       : String;

  ...

  EditDT := StrToDateTime('27.2.2025 10:11:12',TFormatSettings.Create('de'));
  S := DateTimeToStr(EditDT);
  console.log(S);

  UTCDT := LocalTimeToUniversal(EditDT);
  S := DateTimeToStr(UTCDT);
  console.log(S);

  EditDT := UniversalTimeToLocal(UTCDT);
  S := DateTimeToStr(EditDT);
  console.log(S);

This logs

27/02/2025 10:11:12
27/02/2025 07:11:12
27/02/2025 10:11:12

I live in a GMT+3 hours summer time offset region. Feb. 27th was in the winter time, where the time zone offset was GMT+2 hours only. So correctly the second time should instead read:

27/02/2025 08:11:12

Workaround until fixed:

Use this function to retrieve the correct time zone offset at your current browser location of the actual TDateTime parameter in question first:

Function GetLocalTimezoneOffset(LocalDateTime : TDateTime) : NativeInt;
Begin
 Result := 0;
 {$IFDEF PAS2JS}
  ASM
   let localMillis = (LocalDateTime - 25569) * 86400000;
   let d = new Date(localMillis);
   Result = d.getTimezoneOffset();
  END;
 {$ENDIF}
End;

Then use the 2nd variant of the conversion functions, like so:

  EditDT := StrToDateTime('27.2.2025 10:11:12',TFormatSettings.Create('de'));
  S := DateTimeToStr(EditDT);
  console.log(S);

  TZO := GetLocalTimezoneOffset(EditDT);
  console.log(TZO);

  UTCDT := LocalTimeToUniversal(EditDT,-TZO);
  S := DateTimeToStr(UTCDT);
  console.log(S);

  EditDT := UniversalTimeToLocal(UTCDT,-TZO);
  S := DateTimeToStr(EditDT);
  console.log(S);

This now correctly logs:

27/02/2025 10:11:12
-120
27/02/2025 08:11:12
27/02/2025 10:11:12

We will investigate and report.

We could trace & solve this issue. Next update should address this.