Skip to content

Read a DateTime's fields and do its arithmetic on the ticks - #176

Open
pfriesch wants to merge 1 commit into
pfr-claude/dto-own-offsetfrom
pfr-claude/datetime-ticks
Open

pfriesch wants to merge 1 commit into
pfr-claude/dto-own-offsetfrom
pfr-claude/datetime-ticks

Conversation

@pfriesch

Copy link
Copy Markdown
Member

Summary

A Local/Unspecified DateTime read its fields and did its arithmetic through the JS Date's local fields. Around a DST switch, that moved or lost an hour. .NET treats all of this as tick arithmetic, whatever the kind. Stacked on #175. This PR is the large runtime rewrite of the stack, which is why it is on its own.

Berlin, new DateTime(2020, 3, 29, 2, 30, 0) (a skipped hour) Before After / .NET
.ToString("O") 03:30 02:30
.AddHours(1) 04:30 03:30
new DateTimeOffset(t, +1h).UtcDateTime 02:30Z 01:30Z
New York, 2020-11-01 01:30 (+1h) + 5h, as UTC 04:30Z 05:30Z

Key Changes

  • Runtime (BCL/Transpose.BCL/Resources/Date.js):
    • $clock(d) returns a Date whose UTC fields hold d's ticks. getYear…getMillisecond, getDayOfWeek, getDayOfYear and format read from it.
    • create takes its ticks from the clock fields, not from the local instant (a skipped clock time has none).
    • $addUnits follows .NET 7+'s AddUnits. addDays/addHours/addMinutes/addSeconds/addMilliseconds go through it.
    • addTicks, add/subtract, adddt/subdt, addMonths, getDate and getTimeOfDay are tick arithmetic.
  • DateTimeOperatorTests: the matrix gains DST gap and overlap clock times for Berlin, New York and Sydney.
  • CLAUDE.md: the bullet covers the tick model.

Implementation Details

  • The JS Date's instant stays real. JavaScript interop (DOM, hand-written JS) reads it. Only C#-visible fields and arithmetic moved to ticks. Storing the clock time in the Date's UTC fields instead would have broken every DateTime handed to JS.
  • Add* keeps sub-millisecond ticks now. Old bugs this removes: sub-ms ticks were dropped, and AddDays(-1.5) moved 2.5 days.
  • Behavior change: DateTime.ToString("u")/"r" on a Local/Unspecified DateTime prints the clock time without converting to UTC. That matches .NET. No test covers it.
  • Not changed: ToUniversalTime of a clock time inside a DST gap (.NET uses the standard offset there).

Testing

Covered: Debug runtime via TRANSPOSE_DLL_PATH.

  • Date/time tests (75): pass in UTC, Berlin and Tokyo.
  • Merged with D: translator 1295 / Newtonsoft 143 / System.Text.Json 165 pass in UTC and Berlin.
  • Before the split, the same runtime passed the full translator suite in UTC, Berlin, New York, Sydney and Kolkata.

Not covered: HttpClient/WatchMode suites, the Release runtime build, the tesserae byte-identical gate.

Needs manual verification: None


Generated by Claude Code

A Local/Unspecified DateTime is a JS Date whose local fields held its
clock time, and every field read, ToString and Add* went through them. A
clock time the zone skips (02:30 on a spring-forward night) therefore read
as 03:30, Add* across a DST switch gained or lost that hour, and a date
before the zone's first whole-minute offset read wrong fields. .NET treats
all of this as tick arithmetic, whatever the kind.

The Date's instant is kept real, since JavaScript interop reads it. Field
reads, getDate/getTimeOfDay/getDayOfYear and format go through $clock (a
Date whose UTC fields hold the ticks), create takes its ticks from the
clock fields, and every Add* is tick arithmetic through $addUnits, which
follows .NET 7+'s AddUnits (sub-millisecond ticks are kept; the old
AddDays(-1.5) moved 2.5 days).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013thoEGD8RmeKXh9jAqp6UV
@pfriesch
pfriesch added this pull request to stack #178 October 19, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant