8
8
//
9
9
// Standard Format:
10
10
// -=-=-=-=-=-=-=-
11
- // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
11
+ // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
12
12
// Not culture sensitive. Default format (and null/empty format string) map to this format.
13
13
//
14
- // "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
14
+ // "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
15
15
// Only print what's needed. Localized (if you want Invariant, pass in Invariant).
16
16
// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
17
17
//
43
43
// - For multi-letter formats "TryParseByFormat" is called
44
44
// - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
45
45
// 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.
48
48
//
49
49
////////////////////////////////////////////////////////////////////////////
50
50
@@ -104,19 +104,50 @@ public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char>
104
104
_sep = separator ;
105
105
}
106
106
107
- public bool IsInvalidFraction ( )
107
+ public bool NormalizeAndValidateFraction ( )
108
108
{
109
109
Debug . Assert ( _ttt == TTT . Num ) ;
110
110
Debug . Assert ( _num > - 1 ) ;
111
111
112
- if ( _num > MaxFraction || _zeroes > MaxFractionDigits )
112
+ if ( _num == 0 )
113
113
return true ;
114
114
115
- if ( _num == 0 || _zeroes == 0 )
115
+ if ( _zeroes == 0 && _num > MaxFraction )
116
116
return false ;
117
117
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 ;
120
151
}
121
152
}
122
153
@@ -184,7 +215,7 @@ internal TimeSpanToken GetNextToken()
184
215
}
185
216
186
217
num = num * 10 + digit ;
187
- if ( ( num & 0xF0000000 ) != 0 )
218
+ if ( ( num & 0xF0000000 ) != 0 ) // Max limit we can support 268435455 which is FFFFFFF
188
219
{
189
220
return new TimeSpanToken ( TTT . NumOverflow ) ;
190
221
}
@@ -557,7 +588,7 @@ private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanTo
557
588
hours . _num > MaxHours ||
558
589
minutes . _num > MaxMinutes ||
559
590
seconds . _num > MaxSeconds ||
560
- fraction . IsInvalidFraction ( ) )
591
+ ! fraction . NormalizeAndValidateFraction ( ) )
561
592
{
562
593
result = 0 ;
563
594
return false ;
@@ -570,31 +601,7 @@ private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanTo
570
601
return false ;
571
602
}
572
603
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 ;
598
605
if ( positive && result < 0 )
599
606
{
600
607
result = 0 ;
@@ -1338,7 +1345,7 @@ private static bool TryParseByFormat(ReadOnlySpan<char> input, ReadOnlySpan<char
1338
1345
1339
1346
case '%' :
1340
1347
// Optional format character.
1341
- // For example, format string "%d" will print day
1348
+ // For example, format string "%d" will print day
1342
1349
// Most of the cases, "%" can be ignored.
1343
1350
nextFormatChar = DateTimeFormat . ParseNextChar ( format , i ) ;
1344
1351
@@ -1455,7 +1462,7 @@ private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBui
1455
1462
1456
1463
/// <summary>
1457
1464
/// 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.
1459
1466
/// </summary>
1460
1467
private static bool TryParseTimeSpanConstant ( ReadOnlySpan < char > input , ref TimeSpanResult result ) =>
1461
1468
new StringParser ( ) . TryParse ( input , ref result ) ;
@@ -1628,7 +1635,7 @@ internal bool ParseTime(out long time, ref TimeSpanResult result)
1628
1635
if ( _ch == ':' )
1629
1636
{
1630
1637
NextChar ( ) ;
1631
-
1638
+
1632
1639
// allow seconds with the leading zero
1633
1640
if ( _ch != '.' )
1634
1641
{
0 commit comments