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:
631 value = Math.Abs (Milliseconds);
633 if (element.Type == FormatElementType.Ticks)
639 int threshold = (int)Math.Pow (10, element.IntValue);
640 while (value >= threshold)
642 sb.Append (value.ToString ());
644 case FormatElementType.EscapedChar:
645 sb.Append (element.CharValue);
647 case FormatElementType.Literal:
648 sb.Append (element.StringValue);
651 throw new FormatException ("The format is not recognized.");
654 sb.Append (value.ToString ("D" + element.IntValue.ToString ()));
657 return sb.ToString ();
661 public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
666 public static bool operator == (TimeSpan t1, TimeSpan t2)
668 return t1._ticks == t2._ticks;
671 public static bool operator > (TimeSpan t1, TimeSpan t2)
673 return t1._ticks > t2._ticks;
676 public static bool operator >= (TimeSpan t1, TimeSpan t2)
678 return t1._ticks >= t2._ticks;
681 public static bool operator != (TimeSpan t1, TimeSpan t2)
683 return t1._ticks != t2._ticks;
686 public static bool operator < (TimeSpan t1, TimeSpan t2)
688 return t1._ticks < t2._ticks;
691 public static bool operator <= (TimeSpan t1, TimeSpan t2)
693 return t1._ticks <= t2._ticks;
696 public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
698 return t1.Subtract (t2);
701 public static TimeSpan operator - (TimeSpan t)
706 public static TimeSpan operator + (TimeSpan t)
717 // Class Parser implements parser for TimeSpan.Parse
721 private int _cur = 0;
723 ParseError parse_error;
726 NumberFormatInfo number_format;
727 int parsed_numbers_count;
728 bool parsed_days_separator;
730 public bool Exact; // no fallback, strict pattern.
731 public bool AllMembersRequired;
732 public bool CultureSensitive = true;
733 public bool UseColonAsDaySeparator = true;
736 public Parser (string src)
739 _length = _src.Length;
741 number_format = GetNumberFormatInfo (null);
746 // Reset state data, so we can execute another parse over the input.
750 parse_error = ParseError.None;
751 parsed_ticks = parsed_days_separator = false;
752 parsed_numbers_count = 0;
755 public Parser (string src, IFormatProvider formatProvider) :
758 number_format = GetNumberFormatInfo (formatProvider);
761 static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
763 NumberFormatInfo format = null;
764 if (formatProvider != null)
765 format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
767 format = Thread.CurrentThread.CurrentCulture.NumberFormat;
775 return _cur >= _length;
779 // All "Parse" functions throw a FormatException on syntax error.
780 // Their return value is semantic value of the item parsed.
782 // Range checking is spread over three different places:
783 // 1) When parsing "int" values, an exception is thrown immediately
784 // when the value parsed exceeds the maximum value for an int.
785 // 2) An explicit check is built in that checks for hours > 23 and
786 // for minutes and seconds > 59.
787 // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
788 // or < MinValue is left to the TimeSpan constructor called.
790 // Parse zero or more whitespace chars.
791 private void ParseWhiteSpace ()
793 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
798 // Parse optional sign character.
799 private bool ParseSign ()
803 if (!AtEnd && _src[_cur] == '-') {
812 // Used for custom formats parsing, where we may need to declare how
813 // many digits we expect, as well as the maximum allowed.
814 private int ParseIntExact (int digit_count, int max_digit_count)
819 // We can have more than one preceding zero here.
820 while (!AtEnd && Char.IsDigit (_src, _cur)) {
821 res = res * 10 + _src [_cur] - '0';
822 if (res > Int32.MaxValue) {
823 SetParseError (ParseError.Format);
830 // digit_count = 1 means we can use up to maximum count,
831 if (count == 0 || (digit_count > 1 && digit_count != count) ||
832 count > max_digit_count)
833 SetParseError (ParseError.Format);
839 // Parse simple int value
840 private int ParseInt (bool optional)
842 if (optional && AtEnd)
848 while (!AtEnd && Char.IsDigit (_src, _cur)) {
849 res = res * 10 + _src[_cur] - '0';
850 if (res > Int32.MaxValue) {
851 SetParseError (ParseError.Overflow);
858 if (!optional && (count == 0))
859 SetParseError (ParseError.Format);
862 parsed_numbers_count++;
869 // This behaves pretty much like ParseOptDot, but we need to have it
870 // as a separated routine for both days and decimal separators.
871 private bool ParseOptDaysSeparator ()
876 if (_src[_cur] == '.') {
878 parsed_days_separator = true;
884 // Just as ParseOptDot, but for decimal separator
885 private bool ParseOptDecimalSeparator ()
890 // we may need to provide compatibility with old versions using '.'
891 // for culture insensitve and non exact formats.
892 if (!Exact || !CultureSensitive)
893 if (_src [_cur] == '.') {
898 string decimal_separator = number_format.NumberDecimalSeparator;
899 if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
900 _cur += decimal_separator.Length;
907 private bool ParseLiteral (string value)
909 if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) {
910 _cur += value.Length;
917 private bool ParseChar (char c)
919 if (!AtEnd && _src [_cur] == c) {
927 // Parse optional dot
928 private bool ParseOptDot ()
933 if (_src[_cur] == '.') {
940 private void ParseColon (bool optional)
943 if (_src[_cur] == ':')
946 SetParseError (ParseError.Format);
950 // Parse [1..7] digits, representing fractional seconds (ticks)
951 // In 4.0 more than 7 digits will cause an OverflowException
952 private long ParseTicks ()
956 bool digitseen = false;
958 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
959 res = res + (_src[_cur] - '0') * mag;
966 SetParseError (ParseError.Format);
968 else if (!AtEnd && Char.IsDigit (_src, _cur))
969 SetParseError (ParseError.Overflow);
978 // Used by custom formats parsing
979 // digits_count = 0 for digits up to max_digits_count (optional), and other value to
980 // force a precise number of digits.
981 private long ParseTicksExact (int digits_count, int max_digits_count)
987 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
988 res = res + (_src [_cur] - '0') * mag;
994 if ((digits_count > 0 && count != digits_count) ||
995 count > max_digits_count)
996 SetParseError (ParseError.Format);
1002 void SetParseError (ParseError error)
1004 // We preserve the very first error.
1005 if (parse_error != ParseError.None)
1008 parse_error = error;
1012 bool CheckParseSuccess (bool tryParse)
1014 bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
1017 // We always report the first error, but for 2.0 we need to give a higher
1018 // precence to per-element overflow (as opposed to int32 overflow).
1020 if (parse_error == ParseError.Overflow) {
1022 if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
1026 throw new OverflowException (
1027 Locale.GetText ("Invalid time data."));
1030 if (parse_error == ParseError.Format) {
1033 throw new FormatException (
1034 Locale.GetText ("Invalid format for TimeSpan.Parse."));
1041 // We are using a different parse approach in 4.0, due to some changes in the behaviour
1042 // of the parse routines.
1043 // The input string is documented as:
1044 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1046 // There are some special cases as part of 4.0, however:
1047 // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss'
1048 // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value
1049 // exceeding the valid range for hours: 0-23.
1050 // 3. The decimal separator can be retrieved from the current culture, as well as keeping support
1051 // for the '.' value as part of keeping compatibility.
1053 // So we take the approach to parse, if possible, 4 integers, and depending on both how many were
1054 // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds.
1056 public bool Execute (bool tryParse, out TimeSpan result)
1059 int value1, value2, value3, value4;
1060 int days, hours, minutes, seconds;
1063 result = TimeSpan.Zero;
1064 value1 = value2 = value3 = value4 = 0;
1065 days = hours = minutes = seconds = 0;
1070 sign = ParseSign ();
1072 // Parse 4 integers, making only the first one non-optional.
1073 value1 = ParseInt (false);
1074 if (!ParseOptDaysSeparator ()) // Parse either day separator or colon
1077 value2 = ParseInt (true);
1078 value3 = value4 = 0;
1081 value3 = ParseInt (true);
1083 value4 = ParseInt (true);
1086 // We know the precise separator for ticks, so there's no need to guess.
1087 if (ParseOptDecimalSeparator ())
1088 ticks = ParseTicks ();
1093 SetParseError (ParseError.Format);
1096 // In Exact mode we cannot allow both ':' and '.' as day separator.
1097 if (UseColonAsDaySeparator && parsed_days_separator ||
1098 AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks))
1099 SetParseError (ParseError.Format);
1101 switch (parsed_numbers_count) {
1105 case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm'
1106 if (parsed_days_separator)
1107 SetParseError (ParseError.Format);
1113 case 3: // Assign the first value to days if we parsed a day separator or the value
1114 // is not in the valid range for hours.
1115 if (parsed_days_separator || value1 > 23) {
1125 case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss'
1126 if (!UseColonAsDaySeparator && !parsed_days_separator)
1127 SetParseError (ParseError.Format);
1137 if (hours > 23 || minutes > 59 || seconds > 59)
1138 SetParseError (ParseError.Overflow);
1140 if (!CheckParseSuccess (tryParse))
1144 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1148 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1149 } catch (OverflowException) {
1155 result = new TimeSpan (t);
1159 public bool Execute (bool tryParse, out TimeSpan result)
1168 result = TimeSpan.Zero;
1171 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1172 // ... but not entirely true as an lonely
1173 // integer will be parsed as a number of days
1175 sign = ParseSign ();
1176 days = ParseInt (false);
1177 if (ParseOptDot ()) {
1178 hours = ParseInt (true);
1186 minutes = ParseInt (true);
1190 seconds = ParseInt (true);
1193 if ( ParseOptDot () ) {
1194 ticks = ParseTicks ();
1202 SetParseError (ParseError.Format);
1204 if (!CheckParseSuccess (hours, minutes, seconds, tryParse))
1208 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1212 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1213 } catch (OverflowException) {
1219 result = new TimeSpan (t);
1225 public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result)
1227 int days, hours, minutes, seconds;
1229 FormatElement format_element;
1231 days = hours = minutes = seconds = -1;
1233 result = TimeSpan.Zero;
1236 FormatParser format_parser = new FormatParser (format);
1239 // We need to continue even if AtEnd == true, since we could have
1240 // a optional second element.
1241 if (parse_error != ParseError.None)
1243 if (format_parser.AtEnd)
1246 format_element = format_parser.GetNextElement ();
1247 switch (format_element.Type) {
1248 case FormatElementType.Days:
1250 goto case FormatElementType.Error;
1251 days = ParseIntExact (format_element.IntValue, 8);
1253 case FormatElementType.Hours:
1255 goto case FormatElementType.Error;
1256 hours = ParseIntExact (format_element.IntValue, 2);
1258 case FormatElementType.Minutes:
1260 goto case FormatElementType.Error;
1261 minutes = ParseIntExact (format_element.IntValue, 2);
1263 case FormatElementType.Seconds:
1265 goto case FormatElementType.Error;
1266 seconds = ParseIntExact (format_element.IntValue, 2);
1268 case FormatElementType.Ticks:
1270 goto case FormatElementType.Error;
1271 ticks = ParseTicksExact (format_element.IntValue,
1272 format_element.IntValue);
1274 case FormatElementType.TicksUppercase:
1275 // Similar to Milliseconds, but optional and the
1276 // number of F defines the max length, not the required one.
1278 goto case FormatElementType.Error;
1279 ticks = ParseTicksExact (0, format_element.IntValue);
1281 case FormatElementType.Literal:
1282 if (!ParseLiteral (format_element.StringValue))
1283 SetParseError (ParseError.Format);
1285 case FormatElementType.EscapedChar:
1286 if (!ParseChar (format_element.CharValue))
1287 SetParseError (ParseError.Format);
1289 case FormatElementType.Error:
1290 SetParseError (ParseError.Format);
1306 if (!AtEnd || !format_parser.AtEnd)
1307 SetParseError (ParseError.Format);
1308 if (hours > 23 || minutes > 59 || seconds > 59)
1309 SetParseError (ParseError.Format);
1311 if (!CheckParseSuccess (tryParse))
1315 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1319 t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
1320 } catch (OverflowException) {
1326 result = new TimeSpan (t);
1332 enum FormatElementType
1339 TicksUppercase, // 'F'
1346 struct FormatElement
1348 public FormatElement (FormatElementType type)
1351 CharValue = (char)0;
1356 public FormatElementType Type;
1357 public char CharValue; // Used by EscapedChar
1358 public string StringValue; // Used by Literal
1359 public int IntValue; // Used by numerical elements.
1367 public FormatParser (string format)
1369 this.format = format;
1374 return cur >= format.Length;
1378 public FormatElement GetNextElement ()
1380 FormatElement element = new FormatElement ();
1383 return new FormatElement (FormatElementType.End);
1386 switch (format [cur]) {
1388 count = ParseChar ('d');
1390 return new FormatElement (FormatElementType.Error);
1391 element.Type = FormatElementType.Days;
1392 element.IntValue = count;
1395 count = ParseChar ('h');
1397 return new FormatElement (FormatElementType.Error);
1398 element.Type = FormatElementType.Hours;
1399 element.IntValue = count;
1402 count = ParseChar ('m');
1404 return new FormatElement (FormatElementType.Error);
1405 element.Type = FormatElementType.Minutes;
1406 element.IntValue = count;
1409 count = ParseChar ('s');
1411 return new FormatElement (FormatElementType.Error);
1412 element.Type = FormatElementType.Seconds;
1413 element.IntValue = count;
1416 count = ParseChar ('f');
1418 return new FormatElement (FormatElementType.Error);
1419 element.Type = FormatElementType.Ticks;
1420 element.IntValue = count;
1423 count = ParseChar ('F');
1425 return new FormatElement (FormatElementType.Error);
1426 element.Type = FormatElementType.TicksUppercase;
1427 element.IntValue = count;
1432 return new FormatElement (FormatElementType.Error);
1433 if (format [cur] == 'd')
1435 else if (format [cur] == 'h')
1437 else if (format [cur] == 'm')
1439 else if (format [cur] == 's')
1441 else if (format [cur] == 'f')
1443 else if (format [cur] == 'F')
1446 return new FormatElement (FormatElementType.Error);
1448 string literal = ParseLiteral ();
1449 if (literal == null)
1450 return new FormatElement (FormatElementType.Error);
1451 element.Type = FormatElementType.Literal;
1452 element.StringValue = literal;
1455 char escaped_char = ParseEscapedChar ();
1456 if ((int)escaped_char == 0)
1457 return new FormatElement (FormatElementType.Error);
1458 element.Type = FormatElementType.EscapedChar;
1459 element.CharValue = escaped_char;
1462 return new FormatElement (FormatElementType.Error);
1468 int ParseChar (char c)
1472 while (!AtEnd && format [cur] == c) {
1480 char ParseEscapedChar ()
1482 if (AtEnd || format [cur] != '\\')
1489 return format [cur++];
1492 string ParseLiteral ()
1497 if (AtEnd || format [cur] != '\'')
1501 while (!AtEnd && format [cur] != '\'') {
1506 if (!AtEnd && format [cur] == '\'') {
1508 return format.Substring (start, count);