From 9481978cadc1a8aa45890c35ccfec8dab01524fc Mon Sep 17 00:00:00 2001 From: Dan Moseley <6385855+danmoseley@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:36:18 -0600 Subject: [PATCH] Simplify TryParseFormatO fractional-seconds handling The "O" (ISO 8601 round-trip) format always contains exactly 7 fractional-second digits, matching DateTime's tick precision (TimeSpan.TicksPerSecond == 10_000_000). The integer value of those seven digits is therefore already the sub-second tick count. The existing code computed `n / 1e7` then `Math.Round(... * 1e7)` to get back to ticks -- a no-op for every value the parser will ever see. Each 7-digit fraction is <= 9_999_999 < 2^24, exactly representable as double; 1e7 is exactly representable; the two correctly-rounded ops bound the absolute error well under 0.5; Math.Round then recovers the original integer. (The round-trip must already be exact today, since DateTime.ToString("O") writes the same 7 digits -- otherwise format-then-parse would drift.) Keep the value as int and pass it straight to TryAddTicks. Bit-exact equivalent, smaller, more obvious. Co-Authored-By: Claude Opus 4 --- .../src/System/Globalization/DateTimeParse.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs index a6c3aa4e9983fc..a0ab14caadabeb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs @@ -5171,7 +5171,9 @@ private static bool TryParseFormatO(ReadOnlySpan source, scoped ref DateTi second = (int)(s1 * 10 + s2); } - double fraction; + // The seven fractional digits are sub-second ticks (1 tick = 100 ns = 10^-7 s), + // matching TimeSpan.TicksPerSecond, so we can use them directly without floating-point math. + int fractionTicks; { uint f1 = (uint)(source[20] - '0'); uint f2 = (uint)(source[21] - '0'); @@ -5187,12 +5189,12 @@ private static bool TryParseFormatO(ReadOnlySpan source, scoped ref DateTi return false; } - fraction = (f1 * 1000000 + f2 * 100000 + f3 * 10000 + f4 * 1000 + f5 * 100 + f6 * 10 + f7) / 10000000.0; + fractionTicks = (int)(f1 * 1000000 + f2 * 100000 + f3 * 10000 + f4 * 1000 + f5 * 100 + f6 * 10 + f7); } // Per ISO 8601, 24:00:00 represents the end of a calendar day // (the same instant as the next day's 00:00:00), but only when minute, second, and fraction are all zero - if (hour == 24 && (minute != 0 || second != 0 || fraction != 0)) + if (hour == 24 && (minute != 0 || second != 0 || fractionTicks != 0)) { result.SetBadDateTimeFailure(); return false; @@ -5204,7 +5206,7 @@ private static bool TryParseFormatO(ReadOnlySpan source, scoped ref DateTi return false; } - if (!dateTime.TryAddTicks((long)Math.Round(fraction * TimeSpan.TicksPerSecond), out result.parsedDate)) + if (!dateTime.TryAddTicks(fractionTicks, out result.parsedDate)) { result.SetBadDateTimeFailure(); return false;