3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // <OWNER>Microsoft</OWNER>
8 ////////////////////////////////////////////////////////////////////////////
10 // Class: TimeSpan Parse
12 // Purpose: This class is called by TimeSpan to parse a time interval string.
16 // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
17 // Not culture sensitive. Default format (and null/empty format string) map to this format.
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.
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.
28 // * "TryParseTimeSpan" is the main method for Parse/TryParse
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
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
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
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.
55 ////////////////////////////////////////////////////////////////////////////
56 namespace System.Globalization {
59 using System.Diagnostics.Contracts;
60 using System.Globalization;
63 public enum TimeSpanStyles {
65 AssumeNegative = 0x00000001,
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);
76 internal const int unlimitedDigits = -1;
77 internal const int maxFractionDigits = 7;
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;
85 #region InternalSupport
86 enum TimeSpanThrowStyle {
91 private enum ParseFailureKind {
95 FormatWithParameter = 3,
100 enum TimeSpanStandardStyles { // Standard Format Styles
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,
108 // TimeSpan Token Types
110 None = 0, // None of the TimeSpanToken fields are set
114 NumOverflow = 4, // Number that overflowed
117 private static readonly TimeSpanToken zero = new TimeSpanToken(0);
118 struct TimeSpanToken {
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)
124 public TimeSpanToken(int number) {
131 public TimeSpanToken(int leadingZeroes, int number) {
134 zeroes = leadingZeroes;
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);
146 if (maxPrecision == unlimitedDigits)
147 return false; // all validation past this point applies only to fields with precision limits
148 if (zeroes > maxPrecision)
150 if (num == 0 || zeroes == 0)
153 // num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision
154 return (num >= (maxValue/(long)Math.Pow(10, zeroes-1)));
161 // Actions: TimeSpanTokenizer.GetNextToken() returns the next token in the input string.
163 struct TimeSpanTokenizer {
165 private String m_value;
167 internal void Init(String input) {
170 internal void Init(String input, int startPosition) {
171 m_pos = startPosition;
174 // used by the parsing routines that operate on standard-formats
175 internal TimeSpanToken GetNextToken() {
176 Contract.Assert(m_pos > -1);
178 TimeSpanToken tok = new TimeSpanToken();
179 char ch = CurrentChar;
186 if (ch >= '0' && ch <= '9') {
191 if ((tok.num & 0xF0000000) != 0) {
192 tok.ttt = TTT.NumOverflow;
195 tok.num = tok.num * 10 + ch - '0';
196 if (tok.num == 0) tok.zeroes++;
198 tok.ttt = TTT.NumOverflow;
202 } while (ch >= '0' && ch <= '9');
207 int startIndex = m_pos;
210 while (ch != (char)0 && (ch < '0' || '9' < ch)) {
214 tok.sep = m_value.Substring(startIndex, length);
219 internal Boolean EOL {
221 return m_pos >= (m_value.Length-1);
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;
230 internal char NextChar {
236 internal char CurrentChar {
238 if (m_pos > -1 && m_pos < m_value.Length) {
239 return m_value[m_pos];
250 // This stores intermediary parsing state for the standard formats
251 struct TimeSpanRawInfo {
252 internal TimeSpanFormat.FormatLiterals PositiveInvariant {
254 return TimeSpanFormat.PositiveInvariantFormatLiterals;
257 internal TimeSpanFormat.FormatLiterals NegativeInvariant {
259 return TimeSpanFormat.NegativeInvariantFormatLiterals;
263 internal TimeSpanFormat.FormatLiterals PositiveLocalized {
266 m_posLoc = new TimeSpanFormat.FormatLiterals();
267 m_posLoc.Init(m_fullPosPattern, false);
273 internal TimeSpanFormat.FormatLiterals NegativeLocalized {
276 m_negLoc = new TimeSpanFormat.FormatLiterals();
277 m_negLoc.Init(m_fullNegPattern, false);
284 internal Boolean FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
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];
294 internal Boolean PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
297 && pattern.Start == literals[0]
298 && pattern.HourMinuteSep == literals[1]
299 && pattern.AppCompatLiteral == literals[2]
300 && pattern.End == literals[3];
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];
313 // D (no hours, minutes, seconds, or fractions)
314 internal Boolean FullDMatch(TimeSpanFormat.FormatLiterals pattern) {
317 && pattern.Start == literals[0]
318 && pattern.End == literals[1];
320 // HM (no days, seconds, or fractions)
321 internal Boolean FullHMMatch(TimeSpanFormat.FormatLiterals pattern) {
324 && pattern.Start == literals[0]
325 && pattern.HourMinuteSep == literals[1]
326 && pattern.End == literals[2];
328 // DHM (no seconds or fraction)
329 internal Boolean FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) {
332 && pattern.Start == literals[0]
333 && pattern.DayHourSep == literals[1]
334 && pattern.HourMinuteSep == literals[2]
335 && pattern.End == literals[3];
338 // HMS (no days or fraction)
339 internal Boolean FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
342 && pattern.Start == literals[0]
343 && pattern.HourMinuteSep == literals[1]
344 && pattern.MinuteSecondSep == literals[2]
345 && pattern.End == literals[3];
347 // DHMS (no fraction)
348 internal Boolean FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
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];
358 internal Boolean FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) {
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];
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
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;
382 private const int MaxTokens = 11;
383 private const int MaxLiteralTokens = 6;
384 private const int MaxNumericTokens = 5;
386 internal void Init(DateTimeFormatInfo dtfi) {
387 Contract.Assert(dtfi != null);
389 lastSeenTTT = TTT.None;
394 literals = new String[MaxLiteralTokens];
395 numbers = new TimeSpanToken[MaxNumericTokens];
397 m_fullPosPattern = dtfi.FullTimeSpanPositivePattern;
398 m_fullNegPattern = dtfi.FullTimeSpanNegativePattern;
399 m_posLocInit = false;
400 m_negLocInit = false;
403 internal Boolean ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result) {
404 if (tok.ttt == TTT.NumOverflow) {
405 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge", null);
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);
416 if (!AddSep(tok.sep, ref result)) return false;
419 if (tokenCount == 0) {
420 if (!AddSep(String.Empty, ref result)) return false;
422 if (!AddNum(tok, ref result)) return false;
428 lastSeenTTT = tok.ttt;
429 Contract.Assert(tokenCount == (SepCount + NumCount), "tokenCount == (SepCount + NumCount)");
433 private bool AddSep(String sep, ref TimeSpanResult result) {
434 if (SepCount >= MaxLiteralTokens || tokenCount >= MaxTokens) {
435 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
438 literals[SepCount++] = sep;
442 private bool AddNum(TimeSpanToken num, ref TimeSpanResult result) {
443 if (NumCount >= MaxNumericTokens || tokenCount >= MaxTokens) {
444 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
447 numbers[NumCount++] = num;
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;
458 internal ParseFailureKind m_failure;
459 internal string m_failureMessageID;
460 internal object m_failureMessageFormatArgument;
461 internal string m_failureArgumentName;
463 internal void Init(TimeSpanThrowStyle canThrow) {
464 parsedTimeSpan = default(TimeSpan);
465 throwStyle = canThrow;
467 internal void SetFailure(ParseFailureKind failure, string failureMessageID) {
468 SetFailure(failure, failureMessageID, null, null);
470 internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
471 SetFailure(failure, failureMessageID, failureMessageFormatArgument, null);
473 internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument,
474 string failureArgumentName) {
476 m_failureMessageID = failureMessageID;
477 m_failureMessageFormatArgument = failureMessageFormatArgument;
478 m_failureArgumentName = failureArgumentName;
479 if (throwStyle != TimeSpanThrowStyle.None) {
480 throw GetTimeSpanParseException();
484 internal Exception GetTimeSpanParseException() {
486 case ParseFailureKind.ArgumentNull:
487 return new ArgumentNullException(m_failureArgumentName, Environment.GetResourceString(m_failureMessageID));
489 case ParseFailureKind.FormatWithParameter:
490 return new FormatException(Environment.GetResourceString(m_failureMessageID, m_failureMessageFormatArgument));
492 case ParseFailureKind.Format:
493 return new FormatException(Environment.GetResourceString(m_failureMessageID));
495 case ParseFailureKind.Overflow:
496 return new OverflowException(Environment.GetResourceString(m_failureMessageID));
499 Contract.Assert(false, "Unknown TimeSpanParseFailure: " + m_failure);
500 return new FormatException(Environment.GetResourceString("Format_InvalidString"));
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)) {
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) {
521 // Normalize the fraction component
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;
531 long lowerLimit = TimeSpan.TicksPerTenthSecond;
532 if (fraction.zeroes > 0) {
533 long divisor = (long)Math.Pow(10, fraction.zeroes);
534 lowerLimit = lowerLimit / divisor;
536 while (f < lowerLimit) {
540 result = ((long)ticks * TimeSpan.TicksPerMillisecond) + f;
541 if (positive && result < 0) {
550 // ---- SECTION: internal static methods called by System.TimeSpan ---------*
552 // [Try]Parse, [Try]ParseExact, and [Try]ParseExactMultiple
554 // Actions: Main methods called from TimeSpan.Parse
556 internal static TimeSpan Parse(String input, IFormatProvider formatProvider) {
557 TimeSpanResult parseResult = new TimeSpanResult();
558 parseResult.Init(TimeSpanThrowStyle.All);
560 if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
561 return parseResult.parsedTimeSpan;
564 throw parseResult.GetTimeSpanParseException();
567 internal static Boolean TryParse(String input, IFormatProvider formatProvider, out TimeSpan result) {
568 TimeSpanResult parseResult = new TimeSpanResult();
569 parseResult.Init(TimeSpanThrowStyle.None);
571 if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
572 result = parseResult.parsedTimeSpan;
576 result = default(TimeSpan);
580 internal static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles) {
581 TimeSpanResult parseResult = new TimeSpanResult();
582 parseResult.Init(TimeSpanThrowStyle.All);
584 if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
585 return parseResult.parsedTimeSpan;
588 throw parseResult.GetTimeSpanParseException();
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);
595 if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
596 result = parseResult.parsedTimeSpan;
600 result = default(TimeSpan);
604 internal static TimeSpan ParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) {
605 TimeSpanResult parseResult = new TimeSpanResult();
606 parseResult.Init(TimeSpanThrowStyle.All);
608 if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
609 return parseResult.parsedTimeSpan;
612 throw parseResult.GetTimeSpanParseException();
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);
619 if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
620 result = parseResult.parsedTimeSpan;
624 result = default(TimeSpan);
631 // ---- SECTION: private static methods that do the actual work ---------*
632 #region TryParseTimeSpan
636 // Actions: Common private Parse method called by both Parse and TryParse
638 private static Boolean TryParseTimeSpan(String input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result) {
640 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
644 input = input.Trim();
645 if (input == String.Empty) {
646 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
650 TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
651 tokenizer.Init(input);
653 TimeSpanRawInfo raw = new TimeSpanRawInfo();
654 raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
656 TimeSpanToken tok = tokenizer.GetNextToken();
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");
665 tok = tokenizer.GetNextToken();
667 if (!tokenizer.EOL) {
668 // embedded nulls in the input string
669 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
672 if (!ProcessTerminalState(ref raw, style, ref result)) {
673 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
682 // ProcessTerminalState
684 // Actions: Validate the terminal state of a standard format parse.
685 // Sets result.parsedTimeSpan on success.
687 // Calculates the resultant TimeSpan from the TimeSpanRawInfo
689 // try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
690 // 1) Verify Start matches
691 // 2) Verify End matches
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();
701 tok.sep = String.Empty;
702 if (!raw.ProcessToken(ref tok, ref result)) {
703 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
708 switch (raw.NumCount) {
710 return ProcessTerminal_D(ref raw, style, ref result);
712 return ProcessTerminal_HM(ref raw, style, ref result);
714 return ProcessTerminal_HM_S_D(ref raw, style, ref result);
716 return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
718 return ProcessTerminal_DHMSF(ref raw, style, ref result);
720 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
726 // ProcessTerminal_DHMSF
728 // Actions: Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.
729 // Sets result.parsedTimeSpan on success.
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");
737 bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
738 bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
740 bool positive = false;
744 if (raw.FullMatch(raw.PositiveInvariant)) {
748 if (!match && raw.FullMatch(raw.NegativeInvariant)) {
754 if (!match && raw.FullMatch(raw.PositiveLocalized)) {
758 if (!match && raw.FullMatch(raw.NegativeLocalized)) {
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");
772 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
776 result.parsedTimeSpan._ticks = ticks;
780 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
785 // ProcessTerminal_HMS_F_D
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.
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");
796 bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
797 bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
800 bool positive = false;
802 bool overflow = false;
805 if (raw.FullHMSFMatch(raw.PositiveInvariant)) {
807 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
808 overflow = overflow || !match;
810 if (!match && raw.FullDHMSMatch(raw.PositiveInvariant)) {
812 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
813 overflow = overflow || !match;
815 if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant)) {
817 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
818 overflow = overflow || !match;
820 if (!match && raw.FullHMSFMatch(raw.NegativeInvariant)) {
822 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
823 overflow = overflow || !match;
825 if (!match && raw.FullDHMSMatch(raw.NegativeInvariant)) {
827 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
828 overflow = overflow || !match;
830 if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant)) {
832 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
833 overflow = overflow || !match;
837 if (!match && raw.FullHMSFMatch(raw.PositiveLocalized)) {
839 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
840 overflow = overflow || !match;
842 if (!match && raw.FullDHMSMatch(raw.PositiveLocalized)) {
844 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
845 overflow = overflow || !match;
847 if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized)) {
849 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
850 overflow = overflow || !match;
852 if (!match && raw.FullHMSFMatch(raw.NegativeLocalized)) {
854 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
855 overflow = overflow || !match;
857 if (!match && raw.FullDHMSMatch(raw.NegativeLocalized)) {
859 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
860 overflow = overflow || !match;
862 if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized)) {
864 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
865 overflow = overflow || !match;
873 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
877 result.parsedTimeSpan._ticks = ticks;
882 // we found at least one literal pattern match but the numbers just didn't fit
883 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
887 // we couldn't find a thing
888 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
894 // ProcessTerminal_HM_S_D
896 // Actions: Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case
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");
904 bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
905 bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
907 bool positive = false;
909 bool overflow = false;
914 if (raw.FullHMSMatch(raw.PositiveInvariant)) {
916 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
917 overflow = overflow || !match;
919 if (!match && raw.FullDHMMatch(raw.PositiveInvariant)) {
921 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
922 overflow = overflow || !match;
924 if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant)) {
926 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
927 overflow = overflow || !match;
929 if (!match && raw.FullHMSMatch(raw.NegativeInvariant)) {
931 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
932 overflow = overflow || !match;
934 if (!match && raw.FullDHMMatch(raw.NegativeInvariant)) {
936 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
937 overflow = overflow || !match;
939 if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant)) {
941 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
942 overflow = overflow || !match;
946 if (!match && raw.FullHMSMatch(raw.PositiveLocalized)) {
948 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
949 overflow = overflow || !match;
951 if (!match && raw.FullDHMMatch(raw.PositiveLocalized)) {
953 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
954 overflow = overflow || !match;
956 if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized)) {
958 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
959 overflow = overflow || !match;
961 if (!match && raw.FullHMSMatch(raw.NegativeLocalized)) {
963 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
964 overflow = overflow || !match;
966 if (!match && raw.FullDHMMatch(raw.NegativeLocalized)) {
968 match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
969 overflow = overflow || !match;
971 if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized)) {
973 match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
974 overflow = overflow || !match;
982 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
986 result.parsedTimeSpan._ticks = ticks;
991 // we found at least one literal pattern match but the numbers just didn't fit
992 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
996 // we couldn't find a thing
997 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1003 // ProcessTerminal_HM
1005 // Actions: Validate the 2-number "Hours:Minutes" terminal case
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");
1013 bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
1014 bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
1016 bool positive = false;
1020 if (raw.FullHMMatch(raw.PositiveInvariant)) {
1024 if (!match && raw.FullHMMatch(raw.NegativeInvariant)) {
1030 if (!match && raw.FullHMMatch(raw.PositiveLocalized)) {
1034 if (!match && raw.FullHMMatch(raw.NegativeLocalized)) {
1042 if (!TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, zero, out ticks)) {
1043 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1049 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1053 result.parsedTimeSpan._ticks = ticks;
1057 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1063 // ProcessTerminal_D
1065 // Actions: Validate the 1-number "Days" terminal case
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");
1073 bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
1074 bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
1076 bool positive = false;
1080 if (raw.FullDMatch(raw.PositiveInvariant)) {
1084 if (!match && raw.FullDMatch(raw.NegativeInvariant)) {
1090 if (!match && raw.FullDMatch(raw.PositiveLocalized)) {
1094 if (!match && raw.FullDMatch(raw.NegativeLocalized)) {
1102 if (!TryTimeToTicks(positive, raw.numbers[0], zero, zero, zero, zero, out ticks)) {
1103 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1109 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1113 result.parsedTimeSpan._ticks = ticks;
1117 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1122 #region TryParseExactTimeSpan
1124 // TryParseExactTimeSpan
1126 // Actions: Common private ParseExact method called by both ParseExact and TryParseExact
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");
1133 if (format == null) {
1134 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format");
1137 if (format.Length == 0) {
1138 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1142 if (format.Length == 1) {
1143 TimeSpanStandardStyles style = TimeSpanStandardStyles.None;
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);
1149 else if (format[0] == 'g') {
1150 style = TimeSpanStandardStyles.Localized;
1152 else if (format[0] == 'G') {
1153 style = TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull;
1156 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1159 return TryParseTimeSpan(input, style, formatProvider, ref result);
1162 return TryParseByFormat(input, format, styles, ref result);
1168 // Actions: Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.
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");
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'
1188 TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
1189 tokenizer.Init(input, -1);
1191 while (i < format.Length) {
1192 char ch = format[i];
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");
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");
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");
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");
1228 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
1229 if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF) {
1230 result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1233 ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
1237 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
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");
1247 StringBuilder enquotedString = new StringBuilder();
1248 if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen)) {
1249 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch);
1252 if (!ParseExactLiteral(ref tokenizer, enquotedString)) {
1253 result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
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
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");
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.
1278 nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
1279 if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar) {
1283 // This means that '\' is at the end of the format string or the literal match failed.
1284 result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1289 result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
1296 if (!tokenizer.EOL) {
1297 // the custom format didn't consume the entire input
1298 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
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),
1310 if (!positive) ticks = -ticks;
1311 result.parsedTimeSpan._ticks = ticks;
1315 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1321 private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result) {
1324 int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
1325 return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
1327 private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result) {
1331 int tokenLength = 0;
1332 while (tokenLength < maxDigitLength) {
1333 char ch = tokenizer.NextChar;
1334 if (ch < '0' || ch > '9') {
1335 tokenizer.BackOne();
1338 result = result * 10 + (ch - '0');
1339 if (result == 0) zeroes++;
1342 return (tokenLength >= minDigitLength);
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)
1353 #region TryParseTimeSpanConstant
1355 // TryParseTimeSpanConstant
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.
1360 private static Boolean TryParseTimeSpanConstant(String input, ref TimeSpanResult result) {
1361 return (new StringParser().TryParse(input, ref result));
1364 private struct StringParser {
1370 internal void NextChar() {
1371 if (pos < len) pos++;
1372 ch = pos < len? str[pos]: (char) 0;
1375 internal char NextNonDigit() {
1379 if (ch < '0' || ch > '9') return ch;
1385 internal bool TryParse(String input, ref TimeSpanResult result) {
1386 result.parsedTimeSpan._ticks = 0;
1388 if (input == null) {
1389 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
1397 bool negative = false;
1403 if (NextNonDigit() == ':') {
1404 if (!ParseTime(out time, ref result)) {
1410 if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result)) {
1413 time = days * TimeSpan.TicksPerDay;
1417 if (!ParseTime(out remainingTime, ref result)) {
1420 time += remainingTime;
1427 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1433 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1439 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1442 result.parsedTimeSpan._ticks = time;
1446 internal bool ParseInt(int max, out int i, ref TimeSpanResult result) {
1449 while (ch >= '0' && ch <= '9') {
1450 if ((i & 0xF0000000) != 0) {
1451 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1454 i = i * 10 + ch - '0';
1456 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1462 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1466 result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
1472 internal bool ParseTime(out long time, ref TimeSpanResult result) {
1475 if (!ParseInt(23, out unit, ref result)) {
1478 time = unit * TimeSpan.TicksPerHour;
1480 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1484 if (!ParseInt(59, out unit, ref result)) {
1487 time += unit * TimeSpan.TicksPerMinute;
1490 // allow seconds with the leading zero
1492 if (!ParseInt(59, out unit, ref result)) {
1495 time += unit * TimeSpan.TicksPerSecond;
1499 int f = (int)TimeSpan.TicksPerSecond;
1500 while (f > 1 && ch >= '0' && ch <= '9') {
1502 time += (ch - '0') * f;
1510 internal void SkipBlanks() {
1511 while (ch == ' ' || ch == '\t') NextChar();
1516 #region TryParseExactMultipleTimeSpan
1518 // TryParseExactMultipleTimeSpan
1520 // Actions: Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple
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");
1527 if (formats == null) {
1528 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats");
1532 if (input.Length == 0) {
1533 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
1537 if (formats.Length == 0) {
1538 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
1543 // Do a loop through the provided formats and see if we can parse succesfully in
1544 // one of the formats.
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");
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);
1556 if(TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult)) {
1557 result.parsedTimeSpan = innerResult.parsedTimeSpan;
1562 result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");