Skip to content

[browser] Fix DateTime marshalling to be timezone-independent#127340

Merged
pavelsavara merged 2 commits intodotnet:mainfrom
pavelsavara:js_datetime_tz_marshaling_fix
Apr 24, 2026
Merged

[browser] Fix DateTime marshalling to be timezone-independent#127340
pavelsavara merged 2 commits intodotnet:mainfrom
pavelsavara:js_datetime_tz_marshaling_fix

Conversation

@pavelsavara
Copy link
Copy Markdown
Member

Problem

ToJS(DateTime) used new DateTimeOffset(value) which applies the local timezone offset when DateTime.Kind is Unspecified or Local. The ToManaged side always returns .UtcDateTime (offset=0). This asymmetry causes the roundtripped DateTime to shift by the local UTC offset.

With Emscripten 3.1.56, the WASM environment effectively ignored the TZ environment variable, so local time always equaled UTC — masking the bug. Emscripten 5.0.6 correctly processes TZ, exposing the issue on any machine with a non-UTC timezone.

Failing tests (CET/CEST, +02:00)

  • DateTimeMaxValueBoundaryCondition — 1h shift (CET for Dec 31)
  • DateTimeMarshallingLosesMicrosecondComponentPrecisionLoss — 2h shift (CEST for Apr 1)
  • DateTimeMinValueBoundaryConditionArgumentOutOfRangeException (MinValue + offset underflows year 0)
  • JsExportFuncOfDateTime_Argument_OverflowNETDateTime — MaxValue+60s no longer overflows
  • JsExportFunctionDateTimeDateTimeDateTime.Now roundtrip loses offset hours

Fix

Replace new DateTimeOffset(value) with new DateTimeOffset(value.Ticks, TimeSpan.Zero) in both ToJS(DateTime) and ToJS(DateTime?). This reinterprets ticks as UTC, matching the ToManaged side which already returns .UtcDateTime.

The DateTimeOffset overloads are unaffected — ToUnixTimeMilliseconds() already accounts for the stored offset.

Before (broken in non-UTC timezone)

C# ToJS:    new DateTimeOffset(20:00 local, +02:00) → epoch for 18:00 UTC
JS:         new Date(epoch) → 18:00 UTC internally
C# ToManaged: FromUnixTimeMilliseconds(...).UtcDateTime → 18:00 (clock value shifted!)

After (timezone-independent)

C# ToJS:    new DateTimeOffset(20:00 ticks, +00:00) → epoch for 20:00 UTC
JS:         new Date(epoch) → 20:00 UTC internally
C# ToManaged: FromUnixTimeMilliseconds(...).UtcDateTime → 20:00 (clock value preserved)

Test coverage

Added TZ=Europe/Berlin to WasmXHarnessMonoArgs in the test csproj so CI (which runs in UTC) exercises the same non-UTC code paths as local development.

@pavelsavara pavelsavara added this to the 11.0.0 milestone Apr 23, 2026
@pavelsavara pavelsavara self-assigned this Apr 23, 2026
Copilot AI review requested due to automatic review settings April 23, 2026 20:19
@pavelsavara pavelsavara added arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript os-browser Browser variant of arch-wasm labels Apr 23, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes DateTime marshalling between .NET and JavaScript in browser/WASM so roundtrips are timezone-independent, avoiding tick shifts when the local timezone is non-UTC (notably exposed with newer Emscripten behavior around TZ).

Changes:

  • Marshal DateTime/DateTime? to JS using a zero-offset DateTimeOffset(value.Ticks, TimeSpan.Zero) to avoid applying the local timezone offset.
  • Configure the WASM unit test run to use a non-UTC timezone (TZ=Europe/Berlin) so CI exercises the previously-masked code paths.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Marshaling/JSMarshalerArgument.DateTime.cs Makes DateTime and nullable DateTime marshalling interpret ticks as UTC (offset 0) when converting to Unix epoch milliseconds.
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj Sets TZ=Europe/Berlin for WASM test execution to validate behavior in a non-UTC timezone in CI.

@pavelsavara pavelsavara marked this pull request as ready for review April 24, 2026 06:12
@pavelsavara pavelsavara requested a review from lewing as a code owner April 24, 2026 06:12
Copilot AI review requested due to automatic review settings April 24, 2026 06:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Member

@maraf maraf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me and my copilot 👍

@pavelsavara
Copy link
Copy Markdown
Member Author

/ba-g known CI badexit

@pavelsavara pavelsavara merged commit e02138f into dotnet:main Apr 24, 2026
105 of 107 checks passed
@pavelsavara pavelsavara deleted the js_datetime_tz_marshaling_fix branch April 24, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants