Skip to content

Read Microsecond and Nanosecond straight from the ticks - #715

Merged
SimonCropp merged 1 commit into
mainfrom
perf/micro-nanosecond-ticks
Oct 12, 2026
Merged

SimonCropp merged 1 commit into
mainfrom
perf/micro-nanosecond-ticks

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

The Microsecond / Nanosecond polyfills of DateTime and DateTimeOffset, and Microseconds / Nanoseconds of TimeSpan (all below net7), rebuilt the value from its components (new DateTime(Year, Month, Day, Hour, Minute, 0, Kind) and a subtraction) to get at the ticks within the minute.

They now use the formulas the BCL uses:

Microsecond => (int) (Ticks / TicksPerMicrosecond % 1000)
Nanosecond  => (int) (Ticks % TicksPerMicrosecond) * 100

DateTimeOffset reads UtcTicks: an offset is a whole number of minutes, so the UTC ticks have the same sub-second part as the clock time, and UtcTicks does not have to build the clock DateTime first.

The results are identical. The old code effectively computed Ticks % TicksPerMinute; a minute is a whole number of microseconds and of milliseconds, so dividing and taking the remainder on the full tick count gives the same digits, with the same sign for negative TimeSpans (C# division and remainder both truncate toward zero, as the TimeSpan components do).

Tests

  • DateTime: for each DateTimeKind, MinValue, MaxValue, values within a tick, a microsecond, a millisecond, a second and a minute of both ends, and 2000 values spread over the whole range. Each is checked against the formula and against what is left of the ticks once the value rebuilt from year to millisecond is taken out
  • DateTimeOffset: the same over the valid range for offsets of 0, +14:00, -14:00, +05:30, +05:45, -03:30, +00:01 and -00:01, also checked against UtcDateTime.Microsecond / Nanosecond
  • TimeSpan: MinValue, MaxValue, zero, small positive and negative values, values around zero and 2000 values spread over the whole range (negative ones included): checked against the formula, that the components (days to nanoseconds) add back up to the ticks, and that every component carries the sign of the value
  • the limits spelled out: TimeSpan.MinValue is -580 µs / -800 ns, TimeSpan.MaxValue 580 µs / 700 ns, DateTime.MaxValue and DateTimeOffset.MaxValue 999 µs / 900 ns

The new tests pass against the previous implementation and against the BCL on net7+.

Measurements

Stopwatch, best of 9 rounds after warm-up, Release. Nothing allocates before or after.

net48 before net48 after net6.0 before net6.0 after
DateTime.Microsecond 50.2 ns 2.8 ns 43.7 ns 2.6 ns
DateTime.Nanosecond 51.3 ns 2.5 ns 40.6 ns 1.7 ns
DateTimeOffset.Microsecond 93.7 ns 4.5 ns 75.0 ns 3.5 ns
DateTimeOffset.Nanosecond 91.6 ns 3.6 ns 73.9 ns 1.9 ns
TimeSpan.Microseconds 11.8 ns 3.1 ns 20.3 ns 2.4 ns
TimeSpan.Nanoseconds 10.7 ns 2.5 ns 19.7 ns 1.6 ns

(The figures include about 2 ns of delegate call from the harness.)

@SimonCropp SimonCropp modified the milestones: 11.4.4, 11.4.5 Oct 12, 2026
@SimonCropp
SimonCropp merged commit 2cd3c1c into main Oct 12, 2026
8 of 9 checks passed
@SimonCropp
SimonCropp deleted the perf/micro-nanosecond-ticks branch October 12, 2026 12:14
This was referenced Oct 12, 2026
This was referenced Oct 14, 2026
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