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