Skip to content
This repository was archived by the owner on Nov 1, 2020. It is now read-only.

Commit bee9b2c

Browse files
tarekghjkotas
authored andcommitted
Fix TimeSpan parsing (dotnet/coreclr#21968)
* Fix TimeSpan parsing * Temporary disabling the failed CI tests Signed-off-by: dotnet-bot <[email protected]>
1 parent 1108683 commit bee9b2c

File tree

1 file changed

+46
-39
lines changed

1 file changed

+46
-39
lines changed

src/System.Private.CoreLib/shared/System/Globalization/TimeSpanParse.cs

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
//
99
// Standard Format:
1010
// -=-=-=-=-=-=-=-
11-
// "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
11+
// "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
1212
// Not culture sensitive. Default format (and null/empty format string) map to this format.
1313
//
14-
// "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
14+
// "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
1515
// Only print what's needed. Localized (if you want Invariant, pass in Invariant).
1616
// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
1717
//
@@ -43,8 +43,8 @@
4343
// - For multi-letter formats "TryParseByFormat" is called
4444
// - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
4545
// which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
46-
// operates on whole-tokens, ParseExact operates at the character-level. As such,
47-
// TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
46+
// operates on whole-tokens, ParseExact operates at the character-level. As such,
47+
// TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
4848
//
4949
////////////////////////////////////////////////////////////////////////////
5050

@@ -104,19 +104,50 @@ public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char>
104104
_sep = separator;
105105
}
106106

107-
public bool IsInvalidFraction()
107+
public bool NormalizeAndValidateFraction()
108108
{
109109
Debug.Assert(_ttt == TTT.Num);
110110
Debug.Assert(_num > -1);
111111

112-
if (_num > MaxFraction || _zeroes > MaxFractionDigits)
112+
if (_num == 0)
113113
return true;
114114

115-
if (_num == 0 || _zeroes == 0)
115+
if (_zeroes == 0 && _num > MaxFraction)
116116
return false;
117117

118-
// num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision
119-
return _num >= MaxFraction / Pow10(_zeroes - 1);
118+
int totalDigitsCount = ((int) Math.Floor(Math.Log10(_num))) + 1 + _zeroes;
119+
120+
if (totalDigitsCount == MaxFractionDigits)
121+
{
122+
// Already normalized. no more action needed
123+
// .9999999 normalize to 9,999,999 ticks
124+
// .0000001 normalize to 1 ticks
125+
return true;
126+
}
127+
128+
if (totalDigitsCount < MaxFractionDigits)
129+
{
130+
// normalize the fraction to the 7-digits
131+
// .999999 normalize to 9,999,990 ticks
132+
// .99999 normalize to 9,999,900 ticks
133+
// .000001 normalize to 10 ticks
134+
// .1 normalize to 1,000,000 ticks
135+
136+
_num *= (int) Pow10(MaxFractionDigits - totalDigitsCount);
137+
return true;
138+
}
139+
140+
// totalDigitsCount is greater then MaxFractionDigits, we'll need to do the rounding to 7-digits length
141+
// .00000001 normalized to 0 ticks
142+
// .00000005 normalized to 1 ticks
143+
// .09999999 normalize to 1,000,000 ticks
144+
// .099999999 normalize to 1,000,000 ticks
145+
146+
Debug.Assert(_zeroes > 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction
147+
_num = (int) Math.Round((double)_num / Pow10(totalDigitsCount - MaxFractionDigits), MidpointRounding.AwayFromZero);
148+
Debug.Assert(_num < MaxFraction);
149+
150+
return true;
120151
}
121152
}
122153

@@ -184,7 +215,7 @@ internal TimeSpanToken GetNextToken()
184215
}
185216

186217
num = num * 10 + digit;
187-
if ((num & 0xF0000000) != 0)
218+
if ((num & 0xF0000000) != 0) // Max limit we can support 268435455 which is FFFFFFF
188219
{
189220
return new TimeSpanToken(TTT.NumOverflow);
190221
}
@@ -557,7 +588,7 @@ private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanTo
557588
hours._num > MaxHours ||
558589
minutes._num > MaxMinutes ||
559590
seconds._num > MaxSeconds ||
560-
fraction.IsInvalidFraction())
591+
!fraction.NormalizeAndValidateFraction())
561592
{
562593
result = 0;
563594
return false;
@@ -570,31 +601,7 @@ private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanTo
570601
return false;
571602
}
572603

573-
// Normalize the fraction component
574-
//
575-
// string representation => (zeroes,num) => resultant fraction ticks
576-
// --------------------- ------------ ------------------------
577-
// ".9999999" => (0,9999999) => 9,999,999 ticks (same as constant maxFraction)
578-
// ".1" => (0,1) => 1,000,000 ticks
579-
// ".01" => (1,1) => 100,000 ticks
580-
// ".001" => (2,1) => 10,000 ticks
581-
long f = fraction._num;
582-
if (f != 0)
583-
{
584-
long lowerLimit = InternalGlobalizationHelper.TicksPerTenthSecond;
585-
if (fraction._zeroes > 0)
586-
{
587-
long divisor = Pow10(fraction._zeroes);
588-
lowerLimit = lowerLimit / divisor;
589-
}
590-
591-
while (f < lowerLimit)
592-
{
593-
f *= 10;
594-
}
595-
}
596-
597-
result = ticks * TimeSpan.TicksPerMillisecond + f;
604+
result = ticks * TimeSpan.TicksPerMillisecond + fraction._num;
598605
if (positive && result < 0)
599606
{
600607
result = 0;
@@ -1338,7 +1345,7 @@ private static bool TryParseByFormat(ReadOnlySpan<char> input, ReadOnlySpan<char
13381345

13391346
case '%':
13401347
// Optional format character.
1341-
// For example, format string "%d" will print day
1348+
// For example, format string "%d" will print day
13421349
// Most of the cases, "%" can be ignored.
13431350
nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
13441351

@@ -1455,7 +1462,7 @@ private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBui
14551462

14561463
/// <summary>
14571464
/// Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
1458-
/// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
1465+
/// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
14591466
/// </summary>
14601467
private static bool TryParseTimeSpanConstant(ReadOnlySpan<char> input, ref TimeSpanResult result) =>
14611468
new StringParser().TryParse(input, ref result);
@@ -1628,7 +1635,7 @@ internal bool ParseTime(out long time, ref TimeSpanResult result)
16281635
if (_ch == ':')
16291636
{
16301637
NextChar();
1631-
1638+
16321639
// allow seconds with the leading zero
16331640
if (_ch != '.')
16341641
{

0 commit comments

Comments
 (0)