Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / globalization / timespanparse.cs
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 ---------*
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
124             public TimeSpanToken(int number) {
125                 ttt = TTT.Num;
126                 num = number;
127                 zeroes = 0;
128                 sep = null;
129             }
130
131             public TimeSpanToken(int leadingZeroes, int number) {
132                 ttt = TTT.Num;
133                 num = number;
134                 zeroes = leadingZeroes;
135                 sep = null;
136             }
137
138             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
167             internal void Init(String input) {
168                 Init(input, 0);
169             }
170             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
175             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
226             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             //<
284             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             //<
294             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)
303             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)
314             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)
321             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)
329             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)
339             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)
348             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)
358             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
386             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
403             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
433             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             }
442             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
463             internal void Init(TimeSpanThrowStyle canThrow) {
464                 parsedTimeSpan = default(TimeSpan);
465                 throwStyle = canThrow;               
466             }
467             internal void SetFailure(ParseFailureKind failure, string failureMessageID) {
468                 SetFailure(failure, failureMessageID, null, null);
469             }
470             internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
471                 SetFailure(failure, failureMessageID, failureMessageFormatArgument, null);
472             }
473             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
484             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
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
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         }
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         }
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         }
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         }
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         }
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         // 
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
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         // 
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         // 
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         // 
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         // 
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         // 
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         // 
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         // 
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
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         }
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         }
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         //
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
1370             internal void NextChar() {
1371                 if (pos < len) pos++;
1372                 ch = pos < len? str[pos]: (char) 0;
1373             }
1374
1375             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             
1385             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
1446             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
1472             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
1510             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         // 
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 }