5 // Duco Fijma (duco@lorentz.xs4all.nl)
6 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
7 // Sebastien Pouliot <sebastien@ximian.com>
8 // Marek Safar (marek.safar@gmail.com)
10 // (C) 2001 Duco Fijma
11 // (C) 2004 Andreas Nahr
12 // Copyright (C) 2004 Novell (http://www.novell.com)
13 // Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Threading;
37 using System.Globalization;
42 [System.Runtime.InteropServices.ComVisible (true)]
43 public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
50 if (MonoTouchAOTHelper.FalseFlag) {
51 var comparer = new System.Collections.Generic.GenericComparer <TimeSpan> ();
52 var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <TimeSpan> ();
56 public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
57 public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
58 public static readonly TimeSpan Zero = new TimeSpan (0L);
60 public const long TicksPerDay = 864000000000L;
61 public const long TicksPerHour = 36000000000L;
62 public const long TicksPerMillisecond = 10000L;
63 public const long TicksPerMinute = 600000000L;
64 public const long TicksPerSecond = 10000000L;
68 public TimeSpan (long ticks)
73 public TimeSpan (int hours, int minutes, int seconds)
75 CalculateTicks (0, hours, minutes, seconds, 0, true, out _ticks);
78 public TimeSpan (int days, int hours, int minutes, int seconds)
80 CalculateTicks (days, hours, minutes, seconds, 0, true, out _ticks);
83 public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
85 CalculateTicks (days, hours, minutes, seconds, milliseconds, true, out _ticks);
88 internal static bool CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds, bool throwExc, out long result)
90 // there's no overflow checks for hours, minutes, ...
91 // so big hours/minutes values can overflow at some point and change expected values
92 int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
93 int minsec = (minutes * 60);
94 long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
99 bool overflow = false;
100 // days is problematic because it can overflow but that overflow can be
101 // "legal" (i.e. temporary) (e.g. if other parameters are negative) or
102 // illegal (e.g. sign change).
104 long td = TicksPerDay * days;
108 // positive days -> total ticks should be lower
109 overflow = (ticks > t);
113 // positive + positive != negative result
118 long td = TicksPerDay * days;
121 // negative + negative != positive result
127 // negative days -> total ticks should be lower
128 overflow = (t > ticks);
134 throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
144 return (int) (_ticks / TicksPerDay);
150 return (int) (_ticks % TicksPerDay / TicksPerHour);
154 public int Milliseconds {
156 return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
162 return (int) (_ticks % TicksPerHour / TicksPerMinute);
168 return (int) (_ticks % TicksPerMinute / TicksPerSecond);
178 public double TotalDays {
180 return (double) _ticks / TicksPerDay;
184 public double TotalHours {
186 return (double) _ticks / TicksPerHour;
190 public double TotalMilliseconds {
192 return (double) _ticks / TicksPerMillisecond;
196 public double TotalMinutes {
198 return (double) _ticks / TicksPerMinute;
202 public double TotalSeconds {
204 return (double) _ticks / TicksPerSecond;
208 public TimeSpan Add (TimeSpan ts)
212 return new TimeSpan (_ticks + ts.Ticks);
215 catch (OverflowException) {
216 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
220 public static int Compare (TimeSpan t1, TimeSpan t2)
222 if (t1._ticks < t2._ticks)
224 if (t1._ticks > t2._ticks)
229 public int CompareTo (object value)
234 if (!(value is TimeSpan)) {
235 throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
238 return Compare (this, (TimeSpan) value);
241 public int CompareTo (TimeSpan value)
243 return Compare (this, value);
246 public bool Equals (TimeSpan obj)
248 return obj._ticks == _ticks;
251 public TimeSpan Duration ()
255 return new TimeSpan (Math.Abs (_ticks));
258 catch (OverflowException) {
259 throw new OverflowException (Locale.GetText (
260 "This TimeSpan value is MinValue so you cannot get the duration."));
264 public override bool Equals (object value)
266 if (!(value is TimeSpan))
269 return _ticks == ((TimeSpan) value)._ticks;
272 public static bool Equals (TimeSpan t1, TimeSpan t2)
274 return t1._ticks == t2._ticks;
277 public static TimeSpan FromDays (double value)
279 return From (value, TicksPerDay);
282 public static TimeSpan FromHours (double value)
284 return From (value, TicksPerHour);
287 public static TimeSpan FromMinutes (double value)
289 return From (value, TicksPerMinute);
292 public static TimeSpan FromSeconds (double value)
294 return From (value, TicksPerSecond);
297 public static TimeSpan FromMilliseconds (double value)
299 return From (value, TicksPerMillisecond);
302 private static TimeSpan From (double value, long tickMultiplicator)
304 if (Double.IsNaN (value))
305 throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
306 if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
307 (value < MinValue.Ticks) || (value > MaxValue.Ticks))
308 throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
311 value = (value * (tickMultiplicator / TicksPerMillisecond));
314 long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
315 return new TimeSpan (val * TicksPerMillisecond);
318 catch (OverflowException) {
319 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
323 public static TimeSpan FromTicks (long value)
325 return new TimeSpan (value);
328 public override int GetHashCode ()
330 return _ticks.GetHashCode ();
333 public TimeSpan Negate ()
335 if (_ticks == MinValue._ticks)
336 throw new OverflowException (Locale.GetText (
337 "This TimeSpan value is MinValue and cannot be negated."));
338 return new TimeSpan (-_ticks);
341 public static TimeSpan Parse (string s)
344 throw new ArgumentNullException ("s");
348 Parser p = new Parser (s);
349 p.Execute (false, out result);
353 public static bool TryParse (string s, out TimeSpan result)
356 result = TimeSpan.Zero;
360 Parser p = new Parser (s);
361 return p.Execute (true, out result);
365 public static TimeSpan Parse (string input, IFormatProvider formatProvider)
368 throw new ArgumentNullException ("input");
371 Parser p = new Parser (input, formatProvider);
372 p.Execute (false, out result);
376 public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result)
378 if (string.IsNullOrEmpty (input)) {
379 result = TimeSpan.Zero;
383 Parser p = new Parser (input, formatProvider);
384 return p.Execute (true, out result);
387 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider)
390 throw new ArgumentNullException ("format");
392 return ParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None);
395 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles)
398 throw new ArgumentNullException ("format");
400 return ParseExact (input, new string [] { format }, formatProvider, styles);
403 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider)
405 return ParseExact (input, formats, formatProvider, TimeSpanStyles.None);
408 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
411 throw new ArgumentNullException ("input");
413 throw new ArgumentNullException ("formats");
415 // All the errors found during the parsing process are reported as FormatException.
417 if (!TryParseExact (input, formats, formatProvider, styles, out result))
418 throw new FormatException ("Invalid format.");
423 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, out TimeSpan result)
425 return TryParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None, out result);
428 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles,
431 return TryParseExact (input, new string [] { format }, formatProvider, styles, out result);
434 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, out TimeSpan result)
436 return TryParseExact (input, formats, formatProvider, TimeSpanStyles.None, out result);
439 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles,
442 result = TimeSpan.Zero;
444 if (input == null || formats == null || formats.Length == 0)
447 Parser p = new Parser (input, formatProvider);
450 foreach (string format in formats) {
451 if (format == null || format.Length == 0)
452 return false; // wrong format, return immediately.
456 p.AllMembersRequired = false;
457 p.CultureSensitive = true;
458 p.UseColonAsDaySeparator = true;
461 p.AllMembersRequired = true;
462 p.CultureSensitive = true;
463 p.UseColonAsDaySeparator = true;
466 p.AllMembersRequired = false;
467 p.CultureSensitive = false;
468 p.UseColonAsDaySeparator = false;
471 // Single letter formats other than the defined ones are not accepted.
472 if (format.Length == 1)
475 if (p.ExecuteWithFormat (format, styles, true, out result))
480 if (p.Execute (true, out result))
488 public TimeSpan Subtract (TimeSpan ts)
492 return new TimeSpan (_ticks - ts.Ticks);
495 catch (OverflowException) {
496 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
500 public override string ToString ()
502 StringBuilder sb = new StringBuilder (14);
507 // We need to take absolute values of all components.
508 // Can't handle negative timespans by negating the TimeSpan
509 // as a whole. This would lead to an overflow for the
510 // degenerate case "TimeSpan.MinValue.ToString()".
512 sb.Append (Math.Abs (Days));
516 sb.Append (Math.Abs (Hours).ToString ("D2"));
518 sb.Append (Math.Abs (Minutes).ToString ("D2"));
520 sb.Append (Math.Abs (Seconds).ToString ("D2"));
522 int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
523 if (fractional != 0) {
525 sb.Append (fractional.ToString ("D7"));
528 return sb.ToString ();
532 public string ToString (string format)
534 return ToString (format, null);
537 public string ToString (string format, IFormatProvider formatProvider)
539 if (format == null || format.Length == 0 || format == "c" ||
540 format == "t" || format == "T") // Default version
543 if (format != "g" && format != "G")
544 return ToStringCustom (format); // custom formats ignore culture/formatProvider
546 NumberFormatInfo number_info = null;
547 if (formatProvider != null)
548 number_info = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
549 if (number_info == null)
550 number_info = Thread.CurrentThread.CurrentCulture.NumberFormat;
552 string decimal_separator = number_info.NumberDecimalSeparator;
553 int days, hours, minutes, seconds, milliseconds, fractional;
555 days = Math.Abs (Days);
556 hours = Math.Abs (Hours);
557 minutes = Math.Abs (Minutes);
558 seconds = Math.Abs (Seconds);
559 milliseconds = Math.Abs (Milliseconds);
560 fractional = (int) Math.Abs (_ticks % TicksPerSecond);
562 // Set Capacity depending on whether it's long or shot format
563 StringBuilder sb = new StringBuilder (format == "g" ? 16 : 32);
568 case "g": // short version
570 sb.Append (days.ToString ());
573 sb.Append (hours.ToString ());
575 sb.Append (minutes.ToString ("D2"));
577 sb.Append (seconds.ToString ("D2"));
578 if (milliseconds != 0) {
579 sb.Append (decimal_separator);
580 sb.Append (milliseconds.ToString ("D3"));
583 case "G": // long version
584 sb.Append (days.ToString ("D1"));
586 sb.Append (hours.ToString ("D2"));
588 sb.Append (minutes.ToString ("D2"));
590 sb.Append (seconds.ToString ("D2"));
591 sb.Append (decimal_separator);
592 sb.Append (fractional.ToString ("D7"));
596 return sb.ToString ();
599 string ToStringCustom (string format)
601 // Single char formats are not accepted.
602 if (format.Length < 2)
603 throw new FormatException ("The format is not recognized.");
605 FormatParser parser = new FormatParser (format);
606 FormatElement element;
609 StringBuilder sb = new StringBuilder (format.Length + 1);
615 element = parser.GetNextElement ();
616 switch (element.Type) {
617 case FormatElementType.Days:
618 value = Math.Abs (Days);
620 case FormatElementType.Hours:
621 value = Math.Abs (Hours);
623 case FormatElementType.Minutes:
624 value = Math.Abs (Minutes);
626 case FormatElementType.Seconds:
627 value = Math.Abs (Seconds);
629 case FormatElementType.Ticks:
630 case FormatElementType.TicksUppercase:
632 // TODO: Unify with datetime ticks formatting
634 value = (int)(_ticks % TicksPerSecond);
636 if (element.Type == FormatElementType.Ticks)
642 int total_length = element.IntValue;
643 const int max_length = 7;
644 int digits = max_length;
645 for (var dv = (int)Math.Pow (10, max_length - 1); dv > value; dv /= 10, --digits)
649 // Skip only leading zeros in F format
651 if (element.Type == FormatElementType.TicksUppercase && max_length - digits >= total_length)
658 for (; leading < total_length && leading < max_length - digits; ++leading) {
662 if (total_length == leading)
666 // Remove trailing zeros
668 if (element.Type == FormatElementType.TicksUppercase) {
669 while (value % 10 == 0)
673 var max_value = (int)Math.Pow (10, total_length - leading);
674 while (value >= max_value)
677 sb.Append (value.ToString (CultureInfo.InvariantCulture));
679 case FormatElementType.EscapedChar:
680 sb.Append (element.CharValue);
682 case FormatElementType.Literal:
683 sb.Append (element.StringValue);
686 throw new FormatException ("The format is not recognized.");
689 sb.Append (value.ToString ("D" + element.IntValue.ToString ()));
692 return sb.ToString ();
696 public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
701 public static bool operator == (TimeSpan t1, TimeSpan t2)
703 return t1._ticks == t2._ticks;
706 public static bool operator > (TimeSpan t1, TimeSpan t2)
708 return t1._ticks > t2._ticks;
711 public static bool operator >= (TimeSpan t1, TimeSpan t2)
713 return t1._ticks >= t2._ticks;
716 public static bool operator != (TimeSpan t1, TimeSpan t2)
718 return t1._ticks != t2._ticks;
721 public static bool operator < (TimeSpan t1, TimeSpan t2)
723 return t1._ticks < t2._ticks;
726 public static bool operator <= (TimeSpan t1, TimeSpan t2)
728 return t1._ticks <= t2._ticks;
731 public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
733 return t1.Subtract (t2);
736 public static TimeSpan operator - (TimeSpan t)
741 public static TimeSpan operator + (TimeSpan t)
752 // Class Parser implements parser for TimeSpan.Parse
756 private int _cur = 0;
758 ParseError parse_error;
761 NumberFormatInfo number_format;
762 int parsed_numbers_count;
763 bool parsed_days_separator;
765 public bool Exact; // no fallback, strict pattern.
766 public bool AllMembersRequired;
767 public bool CultureSensitive = true;
768 public bool UseColonAsDaySeparator = true;
771 public Parser (string src)
774 _length = _src.Length;
776 number_format = GetNumberFormatInfo (null);
781 // Reset state data, so we can execute another parse over the input.
785 parse_error = ParseError.None;
786 parsed_ticks = parsed_days_separator = false;
787 parsed_numbers_count = 0;
790 public Parser (string src, IFormatProvider formatProvider) :
793 number_format = GetNumberFormatInfo (formatProvider);
796 static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
798 NumberFormatInfo format = null;
799 if (formatProvider != null)
800 format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
802 format = Thread.CurrentThread.CurrentCulture.NumberFormat;
810 return _cur >= _length;
814 // All "Parse" functions throw a FormatException on syntax error.
815 // Their return value is semantic value of the item parsed.
817 // Range checking is spread over three different places:
818 // 1) When parsing "int" values, an exception is thrown immediately
819 // when the value parsed exceeds the maximum value for an int.
820 // 2) An explicit check is built in that checks for hours > 23 and
821 // for minutes and seconds > 59.
822 // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
823 // or < MinValue is left to the TimeSpan constructor called.
825 // Parse zero or more whitespace chars.
826 private void ParseWhiteSpace ()
828 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
833 // Parse optional sign character.
834 private bool ParseSign ()
838 if (!AtEnd && _src[_cur] == '-') {
847 // Used for custom formats parsing, where we may need to declare how
848 // many digits we expect, as well as the maximum allowed.
849 private int ParseIntExact (int digit_count, int max_digit_count)
854 // We can have more than one preceding zero here.
855 while (!AtEnd && Char.IsDigit (_src, _cur)) {
856 res = res * 10 + _src [_cur] - '0';
857 if (res > Int32.MaxValue) {
858 SetParseError (ParseError.Format);
865 // digit_count = 1 means we can use up to maximum count,
866 if (count == 0 || (digit_count > 1 && digit_count != count) ||
867 count > max_digit_count)
868 SetParseError (ParseError.Format);
874 // Parse simple int value
875 private int ParseInt (bool optional)
877 if (optional && AtEnd)
883 while (!AtEnd && Char.IsDigit (_src, _cur)) {
884 res = res * 10 + _src[_cur] - '0';
885 if (res > Int32.MaxValue) {
886 SetParseError (ParseError.Overflow);
893 if (!optional && (count == 0))
894 SetParseError (ParseError.Format);
897 parsed_numbers_count++;
904 // This behaves pretty much like ParseOptDot, but we need to have it
905 // as a separated routine for both days and decimal separators.
906 private bool ParseOptDaysSeparator ()
911 if (_src[_cur] == '.') {
913 parsed_days_separator = true;
919 // Just as ParseOptDot, but for decimal separator
920 private bool ParseOptDecimalSeparator ()
925 // we may need to provide compatibility with old versions using '.'
926 // for culture insensitve and non exact formats.
927 if (!Exact || !CultureSensitive)
928 if (_src [_cur] == '.') {
933 string decimal_separator = number_format.NumberDecimalSeparator;
934 if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
935 _cur += decimal_separator.Length;
942 private bool ParseLiteral (string value)
944 if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) {
945 _cur += value.Length;
952 private bool ParseChar (char c)
954 if (!AtEnd && _src [_cur] == c) {
962 // Parse optional dot
963 private bool ParseOptDot ()
968 if (_src[_cur] == '.') {
975 private void ParseColon (bool optional)
978 if (_src[_cur] == ':')
981 SetParseError (ParseError.Format);
985 // Parse [1..7] digits, representing fractional seconds (ticks)
986 // In 4.0 more than 7 digits will cause an OverflowException
987 private long ParseTicks ()
991 bool digitseen = false;
993 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
994 res = res + (_src[_cur] - '0') * mag;
1001 SetParseError (ParseError.Format);
1003 else if (!AtEnd && Char.IsDigit (_src, _cur))
1004 SetParseError (ParseError.Overflow);
1006 parsed_ticks = true;
1013 // Used by custom formats parsing
1014 // digits_count = 0 for digits up to max_digits_count (optional), and other value to
1015 // force a precise number of digits.
1016 private long ParseTicksExact (int digits_count, int max_digits_count)
1022 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
1023 res = res + (_src [_cur] - '0') * mag;
1029 if ((digits_count > 0 && count != digits_count) ||
1030 count > max_digits_count)
1031 SetParseError (ParseError.Format);
1037 void SetParseError (ParseError error)
1039 // We preserve the very first error.
1040 if (parse_error != ParseError.None)
1043 parse_error = error;
1047 bool CheckParseSuccess (bool tryParse)
1049 bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
1052 // We always report the first error, but for 2.0 we need to give a higher
1053 // precence to per-element overflow (as opposed to int32 overflow).
1055 if (parse_error == ParseError.Overflow) {
1057 if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
1061 throw new OverflowException (
1062 Locale.GetText ("Invalid time data."));
1065 if (parse_error == ParseError.Format) {
1068 throw new FormatException (
1069 Locale.GetText ("Invalid format for TimeSpan.Parse."));
1076 // We are using a different parse approach in 4.0, due to some changes in the behaviour
1077 // of the parse routines.
1078 // The input string is documented as:
1079 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1081 // There are some special cases as part of 4.0, however:
1082 // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss'
1083 // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value
1084 // exceeding the valid range for hours: 0-23.
1085 // 3. The decimal separator can be retrieved from the current culture, as well as keeping support
1086 // for the '.' value as part of keeping compatibility.
1088 // So we take the approach to parse, if possible, 4 integers, and depending on both how many were
1089 // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds.
1091 public bool Execute (bool tryParse, out TimeSpan result)
1094 int value1, value2, value3, value4;
1095 int days, hours, minutes, seconds;
1098 result = TimeSpan.Zero;
1099 value1 = value2 = value3 = value4 = 0;
1100 days = hours = minutes = seconds = 0;
1105 sign = ParseSign ();
1107 // Parse 4 integers, making only the first one non-optional.
1108 value1 = ParseInt (false);
1109 if (!ParseOptDaysSeparator ()) // Parse either day separator or colon
1112 value2 = ParseInt (true);
1113 value3 = value4 = 0;
1116 value3 = ParseInt (true);
1118 value4 = ParseInt (true);
1121 // We know the precise separator for ticks, so there's no need to guess.
1122 if (ParseOptDecimalSeparator ())
1123 ticks = ParseTicks ();
1128 SetParseError (ParseError.Format);
1131 // In Exact mode we cannot allow both ':' and '.' as day separator.
1132 if (UseColonAsDaySeparator && parsed_days_separator ||
1133 AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks))
1134 SetParseError (ParseError.Format);
1136 switch (parsed_numbers_count) {
1140 case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm'
1141 if (parsed_days_separator)
1142 SetParseError (ParseError.Format);
1148 case 3: // Assign the first value to days if we parsed a day separator or the value
1149 // is not in the valid range for hours.
1150 if (parsed_days_separator || value1 > 23) {
1160 case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss'
1161 if (!UseColonAsDaySeparator && !parsed_days_separator)
1162 SetParseError (ParseError.Format);
1172 if (hours > 23 || minutes > 59 || seconds > 59)
1173 SetParseError (ParseError.Overflow);
1175 if (!CheckParseSuccess (tryParse))
1179 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1183 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1184 } catch (OverflowException) {
1190 result = new TimeSpan (t);
1194 public bool Execute (bool tryParse, out TimeSpan result)
1203 result = TimeSpan.Zero;
1206 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1207 // ... but not entirely true as an lonely
1208 // integer will be parsed as a number of days
1210 sign = ParseSign ();
1211 days = ParseInt (false);
1212 if (ParseOptDot ()) {
1213 hours = ParseInt (true);
1221 minutes = ParseInt (true);
1225 seconds = ParseInt (true);
1228 if ( ParseOptDot () ) {
1229 ticks = ParseTicks ();
1237 SetParseError (ParseError.Format);
1239 if (!CheckParseSuccess (hours, minutes, seconds, tryParse))
1243 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1247 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1248 } catch (OverflowException) {
1254 result = new TimeSpan (t);
1260 public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result)
1262 int days, hours, minutes, seconds;
1264 FormatElement format_element;
1266 days = hours = minutes = seconds = -1;
1268 result = TimeSpan.Zero;
1271 FormatParser format_parser = new FormatParser (format);
1274 // We need to continue even if AtEnd == true, since we could have
1275 // a optional second element.
1276 if (parse_error != ParseError.None)
1278 if (format_parser.AtEnd)
1281 format_element = format_parser.GetNextElement ();
1282 switch (format_element.Type) {
1283 case FormatElementType.Days:
1285 goto case FormatElementType.Error;
1286 days = ParseIntExact (format_element.IntValue, 8);
1288 case FormatElementType.Hours:
1290 goto case FormatElementType.Error;
1291 hours = ParseIntExact (format_element.IntValue, 2);
1293 case FormatElementType.Minutes:
1295 goto case FormatElementType.Error;
1296 minutes = ParseIntExact (format_element.IntValue, 2);
1298 case FormatElementType.Seconds:
1300 goto case FormatElementType.Error;
1301 seconds = ParseIntExact (format_element.IntValue, 2);
1303 case FormatElementType.Ticks:
1305 goto case FormatElementType.Error;
1306 ticks = ParseTicksExact (format_element.IntValue,
1307 format_element.IntValue);
1309 case FormatElementType.TicksUppercase:
1310 // Similar to Milliseconds, but optional and the
1311 // number of F defines the max length, not the required one.
1313 goto case FormatElementType.Error;
1314 ticks = ParseTicksExact (0, format_element.IntValue);
1316 case FormatElementType.Literal:
1317 if (!ParseLiteral (format_element.StringValue))
1318 SetParseError (ParseError.Format);
1320 case FormatElementType.EscapedChar:
1321 if (!ParseChar (format_element.CharValue))
1322 SetParseError (ParseError.Format);
1324 case FormatElementType.Error:
1325 SetParseError (ParseError.Format);
1341 if (!AtEnd || !format_parser.AtEnd)
1342 SetParseError (ParseError.Format);
1343 if (hours > 23 || minutes > 59 || seconds > 59)
1344 SetParseError (ParseError.Format);
1346 if (!CheckParseSuccess (tryParse))
1350 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1354 t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
1355 } catch (OverflowException) {
1361 result = new TimeSpan (t);
1367 enum FormatElementType
1374 TicksUppercase, // 'F'
1381 struct FormatElement
1383 public FormatElement (FormatElementType type)
1386 CharValue = (char)0;
1391 public FormatElementType Type;
1392 public char CharValue; // Used by EscapedChar
1393 public string StringValue; // Used by Literal
1394 public int IntValue; // Used by numerical elements.
1402 public FormatParser (string format)
1404 this.format = format;
1409 return cur >= format.Length;
1413 public FormatElement GetNextElement ()
1415 FormatElement element = new FormatElement ();
1418 return new FormatElement (FormatElementType.End);
1421 switch (format [cur]) {
1423 count = ParseChar ('d');
1425 return new FormatElement (FormatElementType.Error);
1426 element.Type = FormatElementType.Days;
1427 element.IntValue = count;
1430 count = ParseChar ('h');
1432 return new FormatElement (FormatElementType.Error);
1433 element.Type = FormatElementType.Hours;
1434 element.IntValue = count;
1437 count = ParseChar ('m');
1439 return new FormatElement (FormatElementType.Error);
1440 element.Type = FormatElementType.Minutes;
1441 element.IntValue = count;
1444 count = ParseChar ('s');
1446 return new FormatElement (FormatElementType.Error);
1447 element.Type = FormatElementType.Seconds;
1448 element.IntValue = count;
1451 count = ParseChar ('f');
1453 return new FormatElement (FormatElementType.Error);
1454 element.Type = FormatElementType.Ticks;
1455 element.IntValue = count;
1458 count = ParseChar ('F');
1460 return new FormatElement (FormatElementType.Error);
1461 element.Type = FormatElementType.TicksUppercase;
1462 element.IntValue = count;
1467 return new FormatElement (FormatElementType.Error);
1468 if (format [cur] == 'd')
1470 else if (format [cur] == 'h')
1472 else if (format [cur] == 'm')
1474 else if (format [cur] == 's')
1476 else if (format [cur] == 'f')
1478 else if (format [cur] == 'F')
1481 return new FormatElement (FormatElementType.Error);
1483 string literal = ParseLiteral ();
1484 if (literal == null)
1485 return new FormatElement (FormatElementType.Error);
1486 element.Type = FormatElementType.Literal;
1487 element.StringValue = literal;
1490 char escaped_char = ParseEscapedChar ();
1491 if ((int)escaped_char == 0)
1492 return new FormatElement (FormatElementType.Error);
1493 element.Type = FormatElementType.EscapedChar;
1494 element.CharValue = escaped_char;
1497 return new FormatElement (FormatElementType.Error);
1503 int ParseChar (char c)
1507 while (!AtEnd && format [cur] == c) {
1515 char ParseEscapedChar ()
1517 if (AtEnd || format [cur] != '\\')
1524 return format [cur++];
1527 string ParseLiteral ()
1532 if (AtEnd || format [cur] != '\'')
1536 while (!AtEnd && format [cur] != '\'') {
1541 if (!AtEnd && format [cur] == '\'') {
1543 return format.Substring (start, count);