Skip to content

Commit

Permalink
Refactor some DateTime and TimeSpan formatting/parsing methods (#101640)
Browse files Browse the repository at this point in the history
* Refactor some DateTime and TimeSpan formatting/parsing methods

* Fix assertion in TimeSpanParse.Pow10

* Don't use Unsafe in TimeSpanParse.Pow10

* Revert changes to TimeSpanParse.Pow10

* Revert "Revert changes to TimeSpanParse.Pow10"

This reverts commit 267d5e8.

* Change method name to Pow10UpToMaxFractionDigits

* Fix TimeSpanParse.TimeSpanToken.NormalizeAndValidateFraction

* Address feedback in TimeSpanParse

* Change from Math.Round to uint divison in TimeSpanParse.NormalizeAndValidateFraction

* Comment for rounding division in TimeSpanParse.NormalizeAndValidateFraction

* Update src/libraries/System.Private.CoreLib/src/System/Globalization/TimeSpanParse.cs

Co-authored-by: xtqqczze <[email protected]>

---------

Co-authored-by: xtqqczze <[email protected]>
  • Loading branch information
lilinus and xtqqczze authored May 6, 2024
1 parent 4585e00 commit c81eca8
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,11 @@ private static void FormatCustomized<TChar>(
tokenLen = ParseRepeatPattern(format, i, ch);
if (tokenLen <= MaxSecondsFractionDigits)
{
long fraction = (dateTime.Ticks % Calendar.TicksPerSecond);
fraction /= (long)Math.Pow(10, 7 - tokenLen);
int fraction = (int)(dateTime.Ticks % Calendar.TicksPerSecond);
fraction /= TimeSpanParse.Pow10UpToMaxFractionDigits(MaxSecondsFractionDigits - tokenLen);
if (ch == 'f')
{
FormatFraction(ref result, (int)fraction, fixedNumberFormats[tokenLen - 1]);
FormatFraction(ref result, fraction, fixedNumberFormats[tokenLen - 1]);
}
else
{
Expand All @@ -507,7 +507,7 @@ private static void FormatCustomized<TChar>(
}
if (effectiveDigits > 0)
{
FormatFraction(ref result, (int)fraction, fixedNumberFormats[effectiveDigits - 1]);
FormatFraction(ref result, fraction, fixedNumberFormats[effectiveDigits - 1]);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3179,6 +3179,7 @@ internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDig

private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, scoped ref double result)
{
Debug.Assert(maxDigitLen <= DateTimeFormat.MaxSecondsFractionDigits);
if (!str.GetNextDigit())
{
str.Index--;
Expand All @@ -3197,7 +3198,7 @@ private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, scop
result = result * 10 + str.GetDigit();
}

result /= TimeSpanParse.Pow10(digitLen);
result /= TimeSpanParse.Pow10UpToMaxFractionDigits(digitLen);
return digitLen == maxDigitLen;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ private static void FormatCustomized<TChar>(TimeSpan value, scoped ReadOnlySpan<
int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
int fraction = (int)(time % TimeSpan.TicksPerSecond);

long tmp;
int tmp;
int i = 0;
int tokenLen;

Expand Down Expand Up @@ -356,8 +356,8 @@ private static void FormatCustomized<TChar>(TimeSpan value, scoped ReadOnlySpan<
}

tmp = fraction;
tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
DateTimeFormat.FormatFraction(ref result, (int)tmp, DateTimeFormat.fixedNumberFormats[tokenLen - 1]);
tmp /= TimeSpanParse.Pow10UpToMaxFractionDigits(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
DateTimeFormat.FormatFraction(ref result, tmp, DateTimeFormat.fixedNumberFormats[tokenLen - 1]);
break;
case 'F':
//
Expand All @@ -370,7 +370,7 @@ private static void FormatCustomized<TChar>(TimeSpan value, scoped ReadOnlySpan<
}

tmp = fraction;
tmp /= TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
tmp /= TimeSpanParse.Pow10UpToMaxFractionDigits(DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
int effectiveDigits = tokenLen;
while (effectiveDigits > 0)
{
Expand All @@ -386,7 +386,7 @@ private static void FormatCustomized<TChar>(TimeSpan value, scoped ReadOnlySpan<
}
if (effectiveDigits > 0)
{
DateTimeFormat.FormatFraction(ref result, (int)tmp, DateTimeFormat.fixedNumberFormats[effectiveDigits - 1]);
DateTimeFormat.FormatFraction(ref result, tmp, DateTimeFormat.fixedNumberFormats[effectiveDigits - 1]);
}
break;
case 'd':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
//
////////////////////////////////////////////////////////////////////////////

using System.Buffers.Text;
using System.Diagnostics;
using System.Text;

Expand Down Expand Up @@ -114,7 +115,7 @@ public bool NormalizeAndValidateFraction()
if (_zeroes == 0 && _num > MaxFraction)
return false;

int totalDigitsCount = ((int)Math.Floor(Math.Log10(_num))) + 1 + _zeroes;
int totalDigitsCount = FormattingHelpers.CountDigits((uint)_num) + _zeroes;

if (totalDigitsCount == MaxFractionDigits)
{
Expand All @@ -132,7 +133,7 @@ public bool NormalizeAndValidateFraction()
// .000001 normalize to 10 ticks
// .1 normalize to 1,000,000 ticks

_num *= (int)Pow10(MaxFractionDigits - totalDigitsCount);
_num *= Pow10UpToMaxFractionDigits(MaxFractionDigits - totalDigitsCount);
return true;
}

Expand All @@ -143,7 +144,18 @@ public bool NormalizeAndValidateFraction()
// .099999999 normalize to 1,000,000 ticks

Debug.Assert(_zeroes > 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction
_num = (int)Math.Round((double)_num / Pow10(totalDigitsCount - MaxFractionDigits), MidpointRounding.AwayFromZero);

if (_zeroes > MaxFractionDigits)
{
// If there are 8 leading zeroes, it rounds to zero
_num = 0;
return true;
}

Debug.Assert(totalDigitsCount - MaxFractionDigits <= MaxFractionDigits);
uint power = (uint)Pow10UpToMaxFractionDigits(totalDigitsCount - MaxFractionDigits);
// Unsigned integer division, rounding away from zero
_num = (int)(((uint)_num + power / 2) / power);
Debug.Assert(_num < MaxFraction);

return true;
Expand Down Expand Up @@ -563,20 +575,21 @@ internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null
}
}

internal static long Pow10(int pow)
internal static int Pow10UpToMaxFractionDigits(int pow)
{
return pow switch
{
0 => 1,
1 => 10,
2 => 100,
3 => 1000,
4 => 10000,
5 => 100000,
6 => 1000000,
7 => 10000000,
_ => (long)Math.Pow(10, pow),
};
ReadOnlySpan<int> powersOfTen =
[
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
];
Debug.Assert(powersOfTen.Length == MaxFractionDigits + 1);
return powersOfTen[pow];
}

private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)
Expand Down

0 comments on commit c81eca8

Please sign in to comment.