1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 //
5 // ==--==
6 // <OWNER>Microsoft</OWNER>
7 //
8 ////////////////////////////////////////////////////////////////////////////
9 //
10 //  Class:    TimeSpan Parse
11 //
12 //  Purpose:  This class is called by TimeSpan to parse a time interval string.
13 //
14 //  Standard Format:
15 //  -=-=-=-=-=-=-=-
16 //  "c":  Constant format.  [-][d'.']hh':'mm':'ss['.'fffffff]
17 //  Not culture sensitive.  Default format (and null/empty format string) map to this format.
18 //
19 //  "g":  General format, short:  [-][d':']h':'mm':'ss'.'FFFFFFF
20 //  Only print what's needed.  Localized (if you want Invariant, pass in Invariant).
21 //  The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
22 //
23 //  "G":  General format, long:  [-]d':'hh':'mm':'ss'.'fffffff
24 //  Always print days and 7 fractional digits.  Localized (if you want Invariant, pass in Invariant).
25 //  The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
26 //
27 //
28 //  * "TryParseTimeSpan" is the main method for Parse/TryParse
29 //
30 //  - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
31 //  - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
32 //  - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
33 //    The terminal states are attempted as follows:
34 //    foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
35 //       1 number  => d
36 //       2 numbers => h:m
37 //       3 numbers => h:m:s     | d.h:m   | h:m:.f
38 //       4 numbers => h:m:s.f   | d.h:m:s | d.h:m:.f
39 //       5 numbers => d.h:m:s.f
40 //
41 // Custom Format:
42 // -=-=-=-=-=-=-=
43 //
44 // * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
45 // * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
46 //    methods that take a String[] of formats
47 //
48 // - For single-letter formats "TryParseTimeSpan" is called (see above)
49 // - For multi-letter formats "TryParseByFormat" is called
50 // - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
51 //   which drive the underlying TimeSpanTokenizer.  However, unlike standard formatting which
52 //   operates on whole-tokens, ParseExact operates at the character-level.  As such,
53 //   TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
54 //
55 ////////////////////////////////////////////////////////////////////////////
56 namespace System.Globalization {
57     using System.Text;
58     using System;
59     using System.Diagnostics.Contracts;
60     using System.Globalization;
61 
62     [Flags]
63     public enum TimeSpanStyles {
64           None                  = 0x00000000,
65           AssumeNegative        = 0x00000001,
66     }
67 
68 
69     internal static class TimeSpanParse {
70         // ---- SECTION:  members for internal support ---------*
ValidateStyles(TimeSpanStyles style, String parameterName)71         internal static void ValidateStyles(TimeSpanStyles style, String parameterName) {
72             if (style != TimeSpanStyles.None && style != TimeSpanStyles.AssumeNegative)
73                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidTimeSpanStyles"), parameterName);
74         }
75 
76         internal const int unlimitedDigits = -1;
77         internal const int maxFractionDigits = 7;
78 
79         internal const int maxDays     = 10675199;
80         internal const int maxHours    = 23;
81         internal const int maxMinutes  = 59;
82         internal const int maxSeconds  = 59;
83         internal const int maxFraction = 9999999;
84 
85         #region InternalSupport
86         enum TimeSpanThrowStyle {
87             None    = 0,
88             All     = 1,
89         }
90 
91         private enum ParseFailureKind {
92             None                     = 0,
93             ArgumentNull             = 1,
94             Format                   = 2,
95             FormatWithParameter      = 3,
96             Overflow                 = 4,
97         }
98 
99         [Flags]
100         enum TimeSpanStandardStyles {     // Standard Format Styles
101             None                  = 0x00000000,
102             Invariant             = 0x00000001, //Allow Invariant Culture
103             Localized             = 0x00000002, //Allow Localized Culture
104             RequireFull           = 0x00000004, //Require the input to be in DHMSF format
105             Any                   = Invariant | Localized,
106         }
107 
108         // TimeSpan Token Types
109         private enum TTT {
110             None              = 0,    // None of the TimeSpanToken fields are set
111             End               = 1,    // '\0'
112             Num               = 2,    // Number
113             Sep               = 3,    // literal
114             NumOverflow       = 4,    // Number that overflowed
115         }
116 
117         private static readonly TimeSpanToken zero = new TimeSpanToken(0);
118         struct TimeSpanToken {
119             internal TTT ttt;
120             internal int num;           // Store the number that we are parsing (if any)
121             internal int zeroes;        // Store the number of leading zeroes (if any)
122             internal String sep;        // Store the literal that we are parsing (if any)
123 
TimeSpanTokenSystem.Globalization.TimeSpanParse.TimeSpanToken124             public TimeSpanToken(int number) {
125                 ttt = TTT.Num;
126                 num = number;
127                 zeroes = 0;
128                 sep = null;
129             }
130 
TimeSpanTokenSystem.Globalization.TimeSpanParse.TimeSpanToken131             public TimeSpanToken(int leadingZeroes, int number) {
132                 ttt = TTT.Num;
133                 num = number;
134                 zeroes = leadingZeroes;
135                 sep = null;
136             }
137 
IsInvalidNumberSystem.Globalization.TimeSpanParse.TimeSpanToken138             public bool IsInvalidNumber(int maxValue, int maxPrecision) {
139                 Contract.Assert(ttt == TTT.Num);
140                 Contract.Assert(num > -1);
141                 Contract.Assert(maxValue > 0);
142                 Contract.Assert(maxPrecision == maxFractionDigits || maxPrecision == unlimitedDigits);
143 
144                 if (num > maxValue)
145                     return true;
146                 if (maxPrecision == unlimitedDigits)
147                     return false; // all validation past this point applies only to fields with precision limits
148                 if (zeroes > maxPrecision)
149                     return true;
150                 if (num == 0 || zeroes == 0)
151                     return false;
152 
153                 // num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision
154                 return (num >= (maxValue/(long)Math.Pow(10, zeroes-1)));
155            }
156         }
157 
158         //
159         //  TimeSpanTokenizer
160         //
161         //  Actions: TimeSpanTokenizer.GetNextToken() returns the next token in the input string.
162         //
163         struct TimeSpanTokenizer {
164             private int m_pos;
165             private String m_value;
166 
InitSystem.Globalization.TimeSpanParse.TimeSpanTokenizer167             internal void Init(String input) {
168                 Init(input, 0);
169             }
InitSystem.Globalization.TimeSpanParse.TimeSpanTokenizer170             internal void Init(String input, int startPosition) {
171                 m_pos = startPosition;
172                 m_value = input;
173             }
174             // used by the parsing routines that operate on standard-formats
GetNextTokenSystem.Globalization.TimeSpanParse.TimeSpanTokenizer175             internal TimeSpanToken GetNextToken() {
176                 Contract.Assert(m_pos > -1);
177 
178                 TimeSpanToken tok = new TimeSpanToken();
179                 char ch = CurrentChar;
180 
181                 if (ch == (char)0) {
182                     tok.ttt = TTT.End;
183                     return tok;
184                 }
185 
186                 if (ch >= '0' && ch <= '9') {
187                     tok.ttt = TTT.Num;
188                     tok.num = 0;
189                     tok.zeroes = 0;
190                     do {
191                         if ((tok.num & 0xF0000000) != 0) {
192                             tok.ttt = TTT.NumOverflow;
193                             return tok;
194                         }
195                         tok.num = tok.num * 10 + ch - '0';
196                         if (tok.num == 0) tok.zeroes++;
197                         if (tok.num < 0) {
198                             tok.ttt = TTT.NumOverflow;
199                             return tok;
200                         }
201                         ch = NextChar;
202                     } while (ch >= '0' && ch <= '9');
203                     return tok;
204                 }
205                 else {
206                     tok.ttt = TTT.Sep;
207                     int startIndex = m_pos;
208                     int length = 0;
209 
210                     while (ch != (char)0 && (ch < '0' || '9' < ch)) {
211                         ch = NextChar;
212                         length++;
213                     }
214                     tok.sep = m_value.Substring(startIndex, length);
215                     return tok;
216                 }
217             }
218 
219             internal Boolean EOL {
220                 get {
221                     return m_pos >= (m_value.Length-1);
222                 }
223             }
224             // BackOne, NextChar, CurrentChar - used by ParseExact (ParseByFormat) to operate
225             // on custom-formats where exact character-by-character control is allowed
BackOneSystem.Globalization.TimeSpanParse.TimeSpanTokenizer226             internal void BackOne() {
227                 if (m_pos > 0) --m_pos;
228             }
229 
230             internal char NextChar {
231                 get {
232                     m_pos++;
233                     return CurrentChar;
234                 }
235             }
236             internal char CurrentChar {
237                 get {
238                     if (m_pos > -1 && m_pos < m_value.Length) {
239                         return m_value[m_pos];
240                     }
241                     else {
242                         return (char) 0;
243                     }
244                 }
245             }
246         }
247 
248 
249 
250         // This stores intermediary parsing state for the standard formats
251         struct TimeSpanRawInfo {
252             internal TimeSpanFormat.FormatLiterals PositiveInvariant {
253                 get {
254                     return TimeSpanFormat.PositiveInvariantFormatLiterals;
255                 }
256             }
257             internal TimeSpanFormat.FormatLiterals NegativeInvariant {
258                 get {
259                     return TimeSpanFormat.NegativeInvariantFormatLiterals;
260                 }
261             }
262 
263             internal TimeSpanFormat.FormatLiterals PositiveLocalized {
264                 get {
265                     if (!m_posLocInit) {
266                         m_posLoc = new TimeSpanFormat.FormatLiterals();
267                         m_posLoc.Init(m_fullPosPattern, false);
268                         m_posLocInit = true;
269                     }
270                     return m_posLoc;
271                 }
272             }
273             internal TimeSpanFormat.FormatLiterals NegativeLocalized {
274                 get {
275                     if (!m_negLocInit) {
276                         m_negLoc = new TimeSpanFormat.FormatLiterals();
277                         m_negLoc.Init(m_fullNegPattern, false);
278                         m_negLocInit = true;
279                     }
280                     return m_negLoc;
281                 }
282             }
283             //<
FullAppCompatMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo284             internal Boolean FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
285                 return SepCount                  == 5
286                     && NumCount                  == 4
287                     && pattern.Start             == literals[0]
288                     && pattern.DayHourSep        == literals[1]
289                     && pattern.HourMinuteSep     == literals[2]
290                     && pattern.AppCompatLiteral  == literals[3]
291                     && pattern.End               == literals[4];
292             }
293             //<
PartialAppCompatMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo294             internal Boolean PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
295                 return SepCount                  == 4
296                     && NumCount                  == 3
297                     && pattern.Start             == literals[0]
298                     && pattern.HourMinuteSep     == literals[1]
299                     && pattern.AppCompatLiteral  == literals[2]
300                     && pattern.End               == literals[3];
301             }
302             // DHMSF (all values matched)
FullMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo303             internal Boolean FullMatch(TimeSpanFormat.FormatLiterals pattern) {
304                 return SepCount                  == MaxLiteralTokens
305                     && NumCount                  == MaxNumericTokens
306                     && pattern.Start             == literals[0]
307                     && pattern.DayHourSep        == literals[1]
308                     && pattern.HourMinuteSep     == literals[2]
309                     && pattern.MinuteSecondSep   == literals[3]
310                     && pattern.SecondFractionSep == literals[4]
311                     && pattern.End               == literals[5];
312             }
313             // D (no hours, minutes, seconds, or fractions)
FullDMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo314             internal Boolean FullDMatch(TimeSpanFormat.FormatLiterals pattern) {
315                 return SepCount                  == 2
316                     && NumCount                  == 1
317                     && pattern.Start             == literals[0]
318                     && pattern.End               == literals[1];
319             }
320             // HM (no days, seconds, or fractions)
FullHMMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo321             internal Boolean FullHMMatch(TimeSpanFormat.FormatLiterals pattern) {
322                 return SepCount                  == 3
323                     && NumCount                  == 2
324                     && pattern.Start             == literals[0]
325                     && pattern.HourMinuteSep     == literals[1]
326                     && pattern.End               == literals[2];
327             }
328             // DHM (no seconds or fraction)
FullDHMMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo329             internal Boolean FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) {
330                 return SepCount                  == 4
331                     && NumCount                  == 3
332                     && pattern.Start             == literals[0]
333                     && pattern.DayHourSep        == literals[1]
334                     && pattern.HourMinuteSep     == literals[2]
335                     && pattern.End               == literals[3];
336 
337             }
338             // HMS (no days or fraction)
FullHMSMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo339             internal Boolean FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
340                 return SepCount                  == 4
341                     && NumCount                  == 3
342                     && pattern.Start             == literals[0]
343                     && pattern.HourMinuteSep     == literals[1]
344                     && pattern.MinuteSecondSep   == literals[2]
345                     && pattern.End               == literals[3];
346             }
347             // DHMS (no fraction)
FullDHMSMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo348             internal Boolean FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
349                 return SepCount                  == 5
350                     && NumCount                  == 4
351                     && pattern.Start             == literals[0]
352                     && pattern.DayHourSep        == literals[1]
353                     && pattern.HourMinuteSep     == literals[2]
354                     && pattern.MinuteSecondSep   == literals[3]
355                     && pattern.End               == literals[4];
356             }
357             // HMSF (no days)
FullHMSFMatchSystem.Globalization.TimeSpanParse.TimeSpanRawInfo358             internal Boolean FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) {
359                 return SepCount                  == 5
360                     && NumCount                  == 4
361                     && pattern.Start             == literals[0]
362                     && pattern.HourMinuteSep     == literals[1]
363                     && pattern.MinuteSecondSep   == literals[2]
364                     && pattern.SecondFractionSep == literals[3]
365                     && pattern.End               == literals[4];
366             }
367 
368             internal TTT lastSeenTTT;
369             internal int tokenCount;
370             internal int SepCount;
371             internal int NumCount;
372             internal String[] literals;
373             internal TimeSpanToken[] numbers;  // raw numbers
374 
375             private TimeSpanFormat.FormatLiterals m_posLoc;
376             private TimeSpanFormat.FormatLiterals m_negLoc;
377             private Boolean m_posLocInit;
378             private Boolean m_negLocInit;
379             private String m_fullPosPattern;
380             private String m_fullNegPattern;
381 
382             private const int MaxTokens = 11;
383             private const int MaxLiteralTokens = 6;
384             private const int MaxNumericTokens = 5;
385 
InitSystem.Globalization.TimeSpanParse.TimeSpanRawInfo386             internal void Init(DateTimeFormatInfo dtfi) {
387                 Contract.Assert(dtfi != null);
388 
389                 lastSeenTTT = TTT.None;
390                 tokenCount = 0;
391                 SepCount = 0;
392                 NumCount = 0;
393 
394                 literals = new String[MaxLiteralTokens];
395                 numbers  = new TimeSpanToken[MaxNumericTokens];
396 
397                 m_fullPosPattern = dtfi.FullTimeSpanPositivePattern;
398                 m_fullNegPattern = dtfi.FullTimeSpanNegativePattern;
399                 m_posLocInit = false;
400                 m_negLocInit = false;
401             }
402 
ProcessTokenSystem.Globalization.TimeSpanParse.TimeSpanRawInfo403             internal Boolean ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result) {
404                 if (tok.ttt == TTT.NumOverflow) {
405                     result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge", null);
406                     return false;
407                 }
408                 if (tok.ttt != TTT.Sep && tok.ttt != TTT.Num) {
409                     // Some unknown token or a repeat token type in the input
410                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
411                     return false;
412                 }
413 
414                 switch (tok.ttt) {
415                     case TTT.Sep:
416                         if (!AddSep(tok.sep, ref result)) return false;
417                         break;
418                     case TTT.Num:
419                         if (tokenCount == 0) {
420                             if (!AddSep(String.Empty, ref result)) return false;
421                         }
422                         if (!AddNum(tok, ref result)) return false;
423                         break;
424                     default:
425                         break;
426                 }
427 
428                 lastSeenTTT = tok.ttt;
429                 Contract.Assert(tokenCount == (SepCount + NumCount), "tokenCount == (SepCount + NumCount)");
430                 return true;
431             }
432 
AddSepSystem.Globalization.TimeSpanParse.TimeSpanRawInfo433             private bool AddSep(String sep, ref TimeSpanResult result) {
434                 if (SepCount >= MaxLiteralTokens || tokenCount >= MaxTokens) {
435                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
436                     return false;
437                 }
438                 literals[SepCount++] = sep;
439                 tokenCount++;
440                 return true;
441             }
AddNumSystem.Globalization.TimeSpanParse.TimeSpanRawInfo442             private bool AddNum(TimeSpanToken num, ref TimeSpanResult result) {
443                 if (NumCount >= MaxNumericTokens || tokenCount >= MaxTokens) {
444                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
445                     return false;
446                 }
447                 numbers[NumCount++] = num;
448                 tokenCount++;
449                 return true;
450             }
451         }
452 
453         // This will store the result of the parsing.  And it will eventually be used to construct a TimeSpan instance.
454         struct TimeSpanResult {
455             internal TimeSpan parsedTimeSpan;
456             internal TimeSpanThrowStyle throwStyle;
457 
458             internal ParseFailureKind m_failure;
459             internal string m_failureMessageID;
460             internal object m_failureMessageFormatArgument;
461             internal string m_failureArgumentName;
462 
InitSystem.Globalization.TimeSpanParse.TimeSpanResult463             internal void Init(TimeSpanThrowStyle canThrow) {
464                 parsedTimeSpan = default(TimeSpan);
465                 throwStyle = canThrow;
466             }
SetFailureSystem.Globalization.TimeSpanParse.TimeSpanResult467             internal void SetFailure(ParseFailureKind failure, string failureMessageID) {
468                 SetFailure(failure, failureMessageID, null, null);
469             }
SetFailureSystem.Globalization.TimeSpanParse.TimeSpanResult470             internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
471                 SetFailure(failure, failureMessageID, failureMessageFormatArgument, null);
472             }
SetFailureSystem.Globalization.TimeSpanParse.TimeSpanResult473             internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument,
474                                      string failureArgumentName) {
475                 m_failure = failure;
476                 m_failureMessageID = failureMessageID;
477                 m_failureMessageFormatArgument = failureMessageFormatArgument;
478                 m_failureArgumentName = failureArgumentName;
479                 if (throwStyle != TimeSpanThrowStyle.None) {
480                     throw GetTimeSpanParseException();
481                 }
482             }
483 
GetTimeSpanParseExceptionSystem.Globalization.TimeSpanParse.TimeSpanResult484             internal Exception GetTimeSpanParseException() {
485                 switch (m_failure) {
486                 case ParseFailureKind.ArgumentNull:
487                     return new ArgumentNullException(m_failureArgumentName, Environment.GetResourceString(m_failureMessageID));
488 
489                 case ParseFailureKind.FormatWithParameter:
490                     return new FormatException(Environment.GetResourceString(m_failureMessageID, m_failureMessageFormatArgument));
491 
492                 case ParseFailureKind.Format:
493                     return new FormatException(Environment.GetResourceString(m_failureMessageID));
494 
495                 case ParseFailureKind.Overflow:
496                     return new OverflowException(Environment.GetResourceString(m_failureMessageID));
497 
498                 default:
499                     Contract.Assert(false, "Unknown TimeSpanParseFailure: " + m_failure);
500                     return new FormatException(Environment.GetResourceString("Format_InvalidString"));
501                 }
502             }
503         }
504 
TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)505         static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result) {
506             if (days.IsInvalidNumber(maxDays, unlimitedDigits)
507              || hours.IsInvalidNumber(maxHours, unlimitedDigits)
508              || minutes.IsInvalidNumber(maxMinutes, unlimitedDigits)
509              || seconds.IsInvalidNumber(maxSeconds, unlimitedDigits)
510              || fraction.IsInvalidNumber(maxFraction, maxFractionDigits)) {
511                 result = 0;
512                 return false;
513             }
514 
515             Int64 ticks = ((Int64)days.num * 3600 * 24 + (Int64)hours.num * 3600 + (Int64)minutes.num * 60 + seconds.num) * 1000;
516             if (ticks > TimeSpan.MaxMilliSeconds || ticks < TimeSpan.MinMilliSeconds) {
517                 result = 0;
518                 return false;
519             }
520 
521             // Normalize the fraction component
522             //
523             // string representation => (zeroes,num) => resultant fraction ticks
524             // ---------------------    ------------    ------------------------
525             // ".9999999"            => (0,9999999)  => 9,999,999 ticks (same as constant maxFraction)
526             // ".1"                  => (0,1)        => 1,000,000 ticks
527             // ".01"                 => (1,1)        =>   100,000 ticks
528             // ".001"                => (2,1)        =>    10,000 ticks
529             long f = fraction.num;
530             if (f != 0) {
531                 long lowerLimit = TimeSpan.TicksPerTenthSecond;
532                 if (fraction.zeroes > 0) {
533                     long divisor = (long)Math.Pow(10, fraction.zeroes);
534                     lowerLimit = lowerLimit / divisor;
535                 }
536                 while (f < lowerLimit) {
537                     f *= 10;
538                 }
539             }
540             result = ((long)ticks * TimeSpan.TicksPerMillisecond) + f;
541             if (positive && result < 0) {
542                 result = 0;
543                 return false;
544             }
545             return true;
546         }
547         #endregion
548 
549 
550         // ---- SECTION:  internal static methods called by System.TimeSpan ---------*
551         //
552         //  [Try]Parse, [Try]ParseExact, and [Try]ParseExactMultiple
553         //
554         //  Actions: Main methods called from TimeSpan.Parse
555         #region ParseMethods
Parse(String input, IFormatProvider formatProvider)556         internal static TimeSpan Parse(String input, IFormatProvider formatProvider) {
557             TimeSpanResult parseResult = new TimeSpanResult();
558             parseResult.Init(TimeSpanThrowStyle.All);
559 
560             if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
561                 return parseResult.parsedTimeSpan;
562             }
563             else {
564                 throw parseResult.GetTimeSpanParseException();
565             }
566         }
TryParse(String input, IFormatProvider formatProvider, out TimeSpan result)567         internal static Boolean TryParse(String input, IFormatProvider formatProvider, out TimeSpan result) {
568             TimeSpanResult parseResult = new TimeSpanResult();
569             parseResult.Init(TimeSpanThrowStyle.None);
570 
571             if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
572                 result = parseResult.parsedTimeSpan;
573                 return true;
574             }
575             else {
576                 result = default(TimeSpan);
577                 return false;
578             }
579         }
ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles)580         internal static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles) {
581             TimeSpanResult parseResult = new TimeSpanResult();
582             parseResult.Init(TimeSpanThrowStyle.All);
583 
584             if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
585                 return parseResult.parsedTimeSpan;
586             }
587             else {
588                 throw parseResult.GetTimeSpanParseException();
589             }
590         }
TryParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)591         internal static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) {
592             TimeSpanResult parseResult = new TimeSpanResult();
593             parseResult.Init(TimeSpanThrowStyle.None);
594 
595             if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
596                 result = parseResult.parsedTimeSpan;
597                 return true;
598             }
599             else {
600                 result = default(TimeSpan);
601                 return false;
602             }
603         }
ParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles)604         internal static TimeSpan ParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) {
605             TimeSpanResult parseResult = new TimeSpanResult();
606             parseResult.Init(TimeSpanThrowStyle.All);
607 
608             if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
609                 return parseResult.parsedTimeSpan;
610             }
611             else {
612                 throw parseResult.GetTimeSpanParseException();
613             }
614         }
TryParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)615         internal static Boolean TryParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) {
616             TimeSpanResult parseResult = new TimeSpanResult();
617             parseResult.Init(TimeSpanThrowStyle.None);
618 
619             if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
620                 result = parseResult.parsedTimeSpan;
621                 return true;
622             }
623             else {
624                 result = default(TimeSpan);
625                 return false;
626             }
627         }
628         #endregion
629 
630 
631         // ---- SECTION:  private static methods that do the actual work ---------*
632         #region TryParseTimeSpan
633         //
634         //  TryParseTimeSpan
635         //
636         //  Actions: Common private Parse method called by both Parse and TryParse
637         //
TryParseTimeSpan(String input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result)638         private static Boolean TryParseTimeSpan(String input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result) {
639             if (input == null) {
640                 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
641                 return false;
642             }
643 
644             input = input.Trim();
645             if (input == String.Empty) {
646                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
647                 return false;
648             }
649 
650             TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
651             tokenizer.Init(input);
652 
653             TimeSpanRawInfo raw = new TimeSpanRawInfo();
654             raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
655 
656             TimeSpanToken tok = tokenizer.GetNextToken();
657 
658             /* The following loop will break out when we reach the end of the str or
659              * when we can determine that the input is invalid. */
660             while (tok.ttt != TTT.End) {
661                 if (!raw.ProcessToken(ref tok, ref result)) {
662                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
663                     return false;
664                 }
665                 tok = tokenizer.GetNextToken();
666             }
667             if (!tokenizer.EOL) {
668                 // embedded nulls in the input string
669                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
670                 return false;
671             }
672             if (!ProcessTerminalState(ref raw, style, ref result)) {
673                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
674                 return false;
675             }
676             return true;
677         }
678 
679 
680 
681         //
682         //  ProcessTerminalState
683         //
684         //  Actions: Validate the terminal state of a standard format parse.
685         //           Sets result.parsedTimeSpan on success.
686         //
687         // Calculates the resultant TimeSpan from the TimeSpanRawInfo
688         //
689         // try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
690         // 1) Verify Start matches
691         // 2) Verify End matches
692         // 3) 1 number  => d
693         //    2 numbers => h:m
694         //    3 numbers => h:m:s | d.h:m | h:m:.f
695         //    4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
696         //    5 numbers => d.h:m:s.f
ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)697         private static Boolean ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
698             if (raw.lastSeenTTT == TTT.Num) {
699                 TimeSpanToken tok = new TimeSpanToken();
700                 tok.ttt = TTT.Sep;
701                 tok.sep = String.Empty;
702                 if (!raw.ProcessToken(ref tok, ref result)) {
703                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
704                     return false;
705                 }
706             }
707 
708             switch (raw.NumCount) {
709                 case 1:
710                     return ProcessTerminal_D(ref raw, style, ref result);
711                 case 2:
712                     return ProcessTerminal_HM(ref raw, style, ref result);
713                 case 3:
714                     return ProcessTerminal_HM_S_D(ref raw, style, ref result);
715                 case 4:
716                     return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
717                 case 5:
718                     return ProcessTerminal_DHMSF(ref raw, style, ref result);
719                 default:
720                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
721                     return false;
722             }
723         }
724 
725         //
726         //  ProcessTerminal_DHMSF
727         //
728         //  Actions: Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.
729         //           Sets result.parsedTimeSpan on success.
730         //
ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)731         private static Boolean ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
732             if (raw.SepCount != 6 || raw.NumCount != 5) {
733                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
734                 return false;
735             }
736 
737             bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
738             bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
739 
740             bool positive = false;
741             bool match = false;
742 
743             if (inv) {
744                 if (raw.FullMatch(raw.PositiveInvariant)) {
745                     match = true;
746                     positive = true;
747                 }
748                 if (!match && raw.FullMatch(raw.NegativeInvariant)) {
749                     match = true;
750                     positive = false;
751                 }
752             }
753             if (loc) {
754                 if (!match && raw.FullMatch(raw.PositiveLocalized)) {
755                     match = true;
756                     positive = true;
757                 }
758                 if (!match && raw.FullMatch(raw.NegativeLocalized)) {
759                     match = true;
760                     positive = false;
761                 }
762             }
763             long ticks;
764             if (match) {
765                 if (!TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], raw.numbers[4], out ticks)) {
766                     result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
767                     return false;
768                 }
769                 if (!positive) {
770                     ticks = -ticks;
771                     if (ticks > 0) {
772                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
773                         return false;
774                     }
775                 }
776                 result.parsedTimeSpan._ticks = ticks;
777                 return true;
778             }
779 
780             result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
781             return false;
782         }
783 
784         //
785         //  ProcessTerminal_HMS_F_D
786         //
787         //  Actions: Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds", or "Days.Hours:Minutes:.Fraction" terminal case.
788         //           Sets result.parsedTimeSpan on success.
789         //
ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)790         private static Boolean ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
791             if (raw.SepCount != 5 || raw.NumCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
792                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
793                 return false;
794             }
795 
796             bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
797             bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
798 
799             long ticks = 0;
800             bool positive = false;
801             bool match = false;
802             bool overflow = false;
803 
804             if (inv) {
805                 if (raw.FullHMSFMatch(raw.PositiveInvariant)) {
806                     positive = true;
807                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
808                     overflow = overflow || !match;
809                 }
810                 if (!match && raw.FullDHMSMatch(raw.PositiveInvariant)) {
811                     positive = true;
812                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
813                     overflow = overflow || !match;
814                 }
815                 if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant)) {
816                     positive = true;
817                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
818                     overflow = overflow || !match;
819                 }
820                 if (!match && raw.FullHMSFMatch(raw.NegativeInvariant)) {
821                     positive = false;
822                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
823                     overflow = overflow || !match;
824                 }
825                 if (!match && raw.FullDHMSMatch(raw.NegativeInvariant)) {
826                     positive = false;
827                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
828                     overflow = overflow || !match;
829                 }
830                 if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant)) {
831                     positive = false;
832                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
833                     overflow = overflow || !match;
834                 }
835             }
836             if (loc) {
837                 if (!match && raw.FullHMSFMatch(raw.PositiveLocalized)) {
838                     positive = true;
839                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
840                     overflow = overflow || !match;
841                 }
842                 if (!match && raw.FullDHMSMatch(raw.PositiveLocalized)) {
843                     positive = true;
844                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
845                     overflow = overflow || !match;
846                 }
847                 if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized)) {
848                     positive = true;
849                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
850                     overflow = overflow || !match;
851                 }
852                 if (!match && raw.FullHMSFMatch(raw.NegativeLocalized)) {
853                     positive = false;
854                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
855                     overflow = overflow || !match;
856                 }
857                 if (!match && raw.FullDHMSMatch(raw.NegativeLocalized)) {
858                     positive = false;
859                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
860                     overflow = overflow || !match;
861                 }
862                 if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized)) {
863                     positive = false;
864                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
865                     overflow = overflow || !match;
866                 }
867             }
868 
869             if (match) {
870                 if (!positive) {
871                     ticks = -ticks;
872                     if (ticks > 0) {
873                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
874                         return false;
875                     }
876                 }
877                 result.parsedTimeSpan._ticks = ticks;
878                 return true;
879             }
880 
881             if (overflow) {
882                 // we found at least one literal pattern match but the numbers just didn't fit
883                 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
884                 return false;
885             }
886             else {
887                 // we couldn't find a thing
888                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
889                 return false;
890             }
891         }
892 
893         //
894         //  ProcessTerminal_HM_S_D
895         //
896         //  Actions: Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case
897         //
ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)898         private static Boolean ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
899             if (raw.SepCount != 4 || raw.NumCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
900                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
901                 return false;
902             }
903 
904             bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
905             bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
906 
907             bool positive = false;
908             bool match = false;
909             bool overflow = false;
910 
911             long ticks = 0;
912 
913             if (inv) {
914                 if (raw.FullHMSMatch(raw.PositiveInvariant)) {
915                     positive = true;
916                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
917                     overflow = overflow || !match;
918                 }
919                 if (!match && raw.FullDHMMatch(raw.PositiveInvariant)) {
920                     positive = true;
921                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
922                     overflow = overflow || !match;
923                 }
924                 if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant)) {
925                     positive = true;
926                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
927                     overflow = overflow || !match;
928                 }
929                 if (!match && raw.FullHMSMatch(raw.NegativeInvariant)) {
930                     positive = false;
931                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
932                     overflow = overflow || !match;
933                 }
934                 if (!match && raw.FullDHMMatch(raw.NegativeInvariant)) {
935                     positive = false;
936                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
937                     overflow = overflow || !match;
938                 }
939                 if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant)) {
940                     positive = false;
941                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
942                     overflow = overflow || !match;
943                 }
944             }
945             if (loc) {
946                 if (!match && raw.FullHMSMatch(raw.PositiveLocalized)) {
947                     positive = true;
948                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
949                     overflow = overflow || !match;
950                 }
951                 if (!match && raw.FullDHMMatch(raw.PositiveLocalized)) {
952                     positive = true;
953                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
954                     overflow = overflow || !match;
955                 }
956                 if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized)) {
957                     positive = true;
958                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
959                     overflow = overflow || !match;
960                 }
961                 if (!match && raw.FullHMSMatch(raw.NegativeLocalized)) {
962                     positive = false;
963                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
964                     overflow = overflow || !match;
965                 }
966                 if (!match && raw.FullDHMMatch(raw.NegativeLocalized)) {
967                     positive = false;
968                     match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
969                     overflow = overflow || !match;
970                 }
971                 if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized)) {
972                     positive = false;
973                     match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
974                     overflow = overflow || !match;
975                 }
976             }
977 
978             if (match) {
979                 if (!positive) {
980                     ticks = -ticks;
981                     if (ticks > 0) {
982                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
983                         return false;
984                     }
985                 }
986                 result.parsedTimeSpan._ticks = ticks;
987                 return true;
988             }
989 
990             if (overflow) {
991                 // we found at least one literal pattern match but the numbers just didn't fit
992                 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
993                 return false;
994             }
995             else {
996                 // we couldn't find a thing
997                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
998                 return false;
999             }
1000         }
1001 
1002         //
1003         //  ProcessTerminal_HM
1004         //
1005         //  Actions: Validate the 2-number "Hours:Minutes" terminal case
1006         //
ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)1007         private static Boolean ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
1008             if (raw.SepCount != 3 || raw.NumCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
1009                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1010                 return false;
1011             }
1012 
1013             bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
1014             bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
1015 
1016             bool positive = false;
1017             bool match = false;
1018 
1019             if (inv) {
1020                 if (raw.FullHMMatch(raw.PositiveInvariant)) {
1021                     match = true;
1022                     positive = true;
1023                 }
1024                 if (!match && raw.FullHMMatch(raw.NegativeInvariant)) {
1025                     match = true;
1026                     positive = false;
1027                 }
1028             }
1029             if (loc) {
1030                 if (!match && raw.FullHMMatch(raw.PositiveLocalized)) {
1031                     match = true;
1032                     positive = true;
1033                 }
1034                 if (!match && raw.FullHMMatch(raw.NegativeLocalized)) {
1035                     match = true;
1036                     positive = false;
1037                 }
1038             }
1039 
1040             long ticks = 0;
1041             if (match) {
1042                 if (!TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, zero, out ticks)) {
1043                     result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1044                     return false;
1045                 }
1046                 if (!positive) {
1047                     ticks = -ticks;
1048                     if (ticks > 0) {
1049                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1050                         return false;
1051                     }
1052                 }
1053                 result.parsedTimeSpan._ticks = ticks;
1054                 return true;
1055             }
1056 
1057             result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1058             return false;
1059         }
1060 
1061 
1062         //
1063         //  ProcessTerminal_D
1064         //
1065         //  Actions: Validate the 1-number "Days" terminal case
1066         //
ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)1067         private static Boolean ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
1068             if (raw.SepCount != 2 || raw.NumCount != 1 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
1069                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1070                 return false;
1071             }
1072 
1073             bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
1074             bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
1075 
1076             bool positive = false;
1077             bool match = false;
1078 
1079             if (inv) {
1080                 if (raw.FullDMatch(raw.PositiveInvariant)) {
1081                     match = true;
1082                     positive = true;
1083                 }
1084                 if (!match && raw.FullDMatch(raw.NegativeInvariant)) {
1085                     match = true;
1086                     positive = false;
1087                 }
1088             }
1089             if (loc) {
1090                 if (!match && raw.FullDMatch(raw.PositiveLocalized)) {
1091                     match = true;
1092                     positive = true;
1093                 }
1094                 if (!match && raw.FullDMatch(raw.NegativeLocalized)) {
1095                     match = true;
1096                     positive = false;
1097                 }
1098             }
1099 
1100             long ticks = 0;
1101             if (match) {
1102                 if (!TryTimeToTicks(positive, raw.numbers[0], zero, zero, zero, zero, out ticks)) {
1103                     result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1104                     return false;
1105                 }
1106                 if (!positive) {
1107                     ticks = -ticks;
1108                     if (ticks > 0) {
1109                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1110                         return false;
1111                     }
1112                 }
1113                 result.parsedTimeSpan._ticks = ticks;
1114                 return true;
1115             }
1116 
1117             result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1118             return false;
1119         }
1120         #endregion
1121 
1122         #region TryParseExactTimeSpan
1123         //
1124         //  TryParseExactTimeSpan
1125         //
1126         //  Actions: Common private ParseExact method called by both ParseExact and TryParseExact
1127         //
TryParseExactTimeSpan(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)1128         private static Boolean TryParseExactTimeSpan(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) {
1129             if (input == null) {
1130                 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
1131                 return false;
1132             }
1133             if (format == null) {
1134                 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format");
1135                 return false;
1136             }
1137             if (format.Length == 0) {
1138                 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1139                 return false;
1140             }
1141 
1142             if (format.Length == 1) {
1143                 TimeSpanStandardStyles style = TimeSpanStandardStyles.None;
1144 
1145                 if (format[0] == 'c' || format[0] == 't' || format[0] == 'T') {
1146                     // fast path for legacy style TimeSpan formats.
1147                     return TryParseTimeSpanConstant(input, ref result);
1148                 }
1149                 else if (format[0] == 'g') {
1150                     style = TimeSpanStandardStyles.Localized;
1151                 }
1152                 else if (format[0] == 'G') {
1153                     style = TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull;
1154                 }
1155                 else {
1156                     result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1157                     return false;
1158                 }
1159                 return TryParseTimeSpan(input, style, formatProvider, ref result);
1160             }
1161 
1162             return TryParseByFormat(input, format, styles, ref result);
1163         }
1164 
1165         //
1166         //  TryParseByFormat
1167         //
1168         //  Actions: Parse the TimeSpan instance using the specified format.  Used by TryParseExactTimeSpan.
1169         //
TryParseByFormat(String input, String format, TimeSpanStyles styles, ref TimeSpanResult result)1170         private static Boolean TryParseByFormat(String input, String format, TimeSpanStyles styles, ref TimeSpanResult result) {
1171             Contract.Assert(input != null, "input != null");
1172             Contract.Assert(format != null, "format != null");
1173 
1174             bool seenDD = false;      // already processed days?
1175             bool seenHH = false;      // already processed hours?
1176             bool seenMM = false;      // already processed minutes?
1177             bool seenSS = false;      // already processed seconds?
1178             bool seenFF = false;      // already processed fraction?
1179             int dd = 0;               // parsed days
1180             int hh = 0;               // parsed hours
1181             int mm = 0;               // parsed minutes
1182             int ss = 0;               // parsed seconds
1183             int leadingZeroes = 0;    // number of leading zeroes in the parsed fraction
1184             int ff = 0;               // parsed fraction
1185             int i = 0;                // format string position
1186             int tokenLen = 0;         // length of current format token, used to update index 'i'
1187 
1188             TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
1189             tokenizer.Init(input, -1);
1190 
1191             while (i < format.Length) {
1192                 char ch = format[i];
1193                 int nextFormatChar;
1194                 switch (ch) {
1195                     case 'h':
1196                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1197                         if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh)) {
1198                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1199                             return false;
1200                         }
1201                         seenHH = true;
1202                         break;
1203                     case 'm':
1204                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1205                         if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm)) {
1206                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1207                             return false;
1208                         }
1209                         seenMM = true;
1210                         break;
1211                     case 's':
1212                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1213                         if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss)) {
1214                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1215                             return false;
1216                         }
1217                         seenSS = true;
1218                         break;
1219                     case 'f':
1220                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1221                         if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF || !ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff)) {
1222                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1223                             return false;
1224                         }
1225                         seenFF = true;
1226                         break;
1227                     case 'F':
1228                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1229                         if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF) {
1230                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1231                             return false;
1232                         }
1233                         ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
1234                         seenFF = true;
1235                         break;
1236                     case 'd':
1237                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1238                         int tmp = 0;
1239                         if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen<2) ? 1 : tokenLen, (tokenLen<2) ? 8 : tokenLen, out tmp, out dd)) {
1240                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1241                             return false;
1242                         }
1243                         seenDD = true;
1244                         break;
1245                     case '\'':
1246                     case '\"':
1247                         StringBuilder enquotedString = new StringBuilder();
1248                         if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen)) {
1249                             result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch);
1250                             return false;
1251                         }
1252                         if (!ParseExactLiteral(ref tokenizer, enquotedString)) {
1253                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1254                             return false;
1255                         }
1256                         break;
1257                     case '%':
1258                         // Optional format character.
1259                         // For example, format string "%d" will print day
1260                         // Most of the cases, "%" can be ignored.
1261                         nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
1262                         // nextFormatChar will be -1 if we already reach the end of the format string.
1263                         // Besides, we will not allow "%%" appear in the pattern.
1264                         if (nextFormatChar >= 0 && nextFormatChar != (int)'%') {
1265                             tokenLen = 1; // skip the '%' and process the format character
1266                             break;
1267                         }
1268                         else {
1269                             // This means that '%' is at the end of the format string or
1270                             // "%%" appears in the format string.
1271                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1272                             return false;
1273                         }
1274                     case '\\':
1275                         // Escaped character.  Can be used to insert character into the format string.
1276                         // For example, "\d" will insert the character 'd' into the string.
1277                         //
1278                         nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
1279                         if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar) {
1280                             tokenLen = 2;
1281                         }
1282                         else {
1283                             // This means that '\' is at the end of the format string or the literal match failed.
1284                             result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1285                             return false;
1286                         }
1287                         break;
1288                     default:
1289                         result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1290                         return false;
1291                 }
1292                 i += tokenLen;
1293             }
1294 
1295 
1296             if (!tokenizer.EOL) {
1297                 // the custom format didn't consume the entire input
1298                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1299                 return false;
1300             }
1301 
1302             long ticks = 0;
1303             bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0;
1304             if (TryTimeToTicks(positive, new TimeSpanToken(dd),
1305                                          new TimeSpanToken(hh),
1306                                          new TimeSpanToken(mm),
1307                                          new TimeSpanToken(ss),
1308                                          new TimeSpanToken(leadingZeroes, ff),
1309                                          out ticks)) {
1310                 if (!positive) ticks = -ticks;
1311                 result.parsedTimeSpan._ticks = ticks;
1312                 return true;
1313             }
1314             else {
1315                 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1316                 return false;
1317 
1318             }
1319         }
1320 
ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result)1321         private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result) {
1322             result = 0;
1323             int zeroes = 0;
1324             int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
1325             return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
1326         }
ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result)1327         private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result) {
1328             result = 0;
1329             zeroes = 0;
1330 
1331             int tokenLength = 0;
1332             while (tokenLength < maxDigitLength) {
1333                 char ch = tokenizer.NextChar;
1334                 if (ch < '0' || ch > '9') {
1335                     tokenizer.BackOne();
1336                     break;
1337                 }
1338                 result = result * 10 + (ch - '0');
1339                 if (result == 0) zeroes++;
1340                 tokenLength++;
1341             }
1342             return (tokenLength >= minDigitLength);
1343         }
ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString)1344         private static Boolean ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString) {
1345             for (int i = 0; i < enquotedString.Length; i++) {
1346                 if (enquotedString[i] != tokenizer.NextChar)
1347                     return false;
1348             }
1349             return true;
1350         }
1351         #endregion
1352 
1353         #region TryParseTimeSpanConstant
1354         //
1355         // TryParseTimeSpanConstant
1356         //
1357         // Actions: Parses the "c" (constant) format.  This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
1358         //          and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
1359         //
TryParseTimeSpanConstant(String input, ref TimeSpanResult result)1360         private static Boolean TryParseTimeSpanConstant(String input, ref TimeSpanResult result) {
1361             return (new StringParser().TryParse(input, ref result));
1362         }
1363 
1364         private struct StringParser {
1365             private String str;
1366             private char ch;
1367             private int pos;
1368             private int len;
1369 
NextCharSystem.Globalization.TimeSpanParse.StringParser1370             internal void NextChar() {
1371                 if (pos < len) pos++;
1372                 ch = pos < len? str[pos]: (char) 0;
1373             }
1374 
NextNonDigitSystem.Globalization.TimeSpanParse.StringParser1375             internal char NextNonDigit() {
1376                 int i = pos;
1377                 while (i < len) {
1378                     char ch = str[i];
1379                     if (ch < '0' || ch > '9') return ch;
1380                     i++;
1381                 }
1382                 return (char) 0;
1383             }
1384 
TryParseSystem.Globalization.TimeSpanParse.StringParser1385             internal bool TryParse(String input, ref TimeSpanResult result) {
1386                 result.parsedTimeSpan._ticks = 0;
1387 
1388                 if (input == null) {
1389                     result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
1390                     return false;
1391                 }
1392                 str = input;
1393                 len = input.Length;
1394                 pos = -1;
1395                 NextChar();
1396                 SkipBlanks();
1397                 bool negative = false;
1398                 if (ch == '-') {
1399                     negative = true;
1400                     NextChar();
1401                 }
1402                 long time;
1403                 if (NextNonDigit() == ':') {
1404                     if (!ParseTime(out time, ref result)) {
1405                         return false;
1406                     };
1407                 }
1408                 else {
1409                     int days;
1410                     if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result)) {
1411                         return false;
1412                     }
1413                     time = days * TimeSpan.TicksPerDay;
1414                     if (ch == '.') {
1415                         NextChar();
1416                         long remainingTime;
1417                         if (!ParseTime(out remainingTime, ref result)) {
1418                             return false;
1419                         };
1420                         time += remainingTime;
1421                     }
1422                 }
1423                 if (negative) {
1424                     time = -time;
1425                     // Allow -0 as well
1426                     if (time > 0) {
1427                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1428                         return false;
1429                     }
1430                 }
1431                 else {
1432                     if (time < 0) {
1433                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1434                         return false;
1435                     }
1436                 }
1437                 SkipBlanks();
1438                 if (pos < len) {
1439                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1440                     return false;
1441                 }
1442                 result.parsedTimeSpan._ticks = time;
1443                 return true;
1444             }
1445 
ParseIntSystem.Globalization.TimeSpanParse.StringParser1446             internal bool ParseInt(int max, out int i, ref TimeSpanResult result) {
1447                 i = 0;
1448                 int p = pos;
1449                 while (ch >= '0' && ch <= '9') {
1450                     if ((i & 0xF0000000) != 0) {
1451                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1452                         return false;
1453                     }
1454                     i = i * 10 + ch - '0';
1455                     if (i < 0) {
1456                         result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1457                         return false;
1458                     }
1459                     NextChar();
1460                 }
1461                 if (p == pos) {
1462                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1463                     return false;
1464                 }
1465                 if (i > max) {
1466                     result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1467                     return false;
1468                 }
1469                 return true;
1470             }
1471 
ParseTimeSystem.Globalization.TimeSpanParse.StringParser1472             internal bool ParseTime(out long time, ref TimeSpanResult result) {
1473                 time = 0;
1474                 int unit;
1475                 if (!ParseInt(23, out unit, ref result)) {
1476                     return false;
1477                 }
1478                 time = unit * TimeSpan.TicksPerHour;
1479                 if (ch != ':') {
1480                     result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1481                     return false;
1482                 }
1483                 NextChar();
1484                 if (!ParseInt(59, out unit, ref result)) {
1485                     return false;
1486                 }
1487                 time += unit * TimeSpan.TicksPerMinute;
1488                 if (ch == ':') {
1489                     NextChar();
1490                     // allow seconds with the leading zero
1491                     if (ch != '.') {
1492                         if (!ParseInt(59, out unit, ref result)) {
1493                             return false;
1494                         }
1495                         time += unit * TimeSpan.TicksPerSecond;
1496                     }
1497                     if (ch == '.') {
1498                         NextChar();
1499                         int f = (int)TimeSpan.TicksPerSecond;
1500                         while (f > 1 && ch >= '0' && ch <= '9') {
1501                             f /= 10;
1502                             time += (ch - '0') * f;
1503                             NextChar();
1504                         }
1505                     }
1506                 }
1507                 return true;
1508             }
1509 
SkipBlanksSystem.Globalization.TimeSpanParse.StringParser1510             internal void SkipBlanks() {
1511                 while (ch == ' ' || ch == '\t') NextChar();
1512             }
1513         }
1514         #endregion
1515 
1516         #region TryParseExactMultipleTimeSpan
1517         //
1518         //  TryParseExactMultipleTimeSpan
1519         //
1520         //  Actions: Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple
1521         //
TryParseExactMultipleTimeSpan(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)1522         private static Boolean TryParseExactMultipleTimeSpan(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) {
1523             if (input == null) {
1524                 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
1525                 return false;
1526             }
1527             if (formats == null) {
1528                 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats");
1529                 return false;
1530             }
1531 
1532             if (input.Length == 0) {
1533                 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1534                 return false;
1535             }
1536 
1537             if (formats.Length == 0) {
1538                 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1539                 return false;
1540             }
1541 
1542             //
1543             // Do a loop through the provided formats and see if we can parse succesfully in
1544             // one of the formats.
1545             //
1546             for (int i = 0; i < formats.Length; i++) {
1547                 if (formats[i] == null || formats[i].Length == 0) {
1548                     result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1549                     return false;
1550                 }
1551 
1552                 // Create a new non-throwing result each time to ensure the runs are independent.
1553                 TimeSpanResult innerResult = new TimeSpanResult();
1554                 innerResult.Init(TimeSpanThrowStyle.None);
1555 
1556                 if(TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult)) {
1557                     result.parsedTimeSpan = innerResult.parsedTimeSpan;
1558                     return true;
1559                 }
1560             }
1561 
1562             result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1563             return (false);
1564         }
1565         #endregion
1566     }
1567 }
1568