5 // Duco Fijma (duco@lorentz.xs4all.nl)
6 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
7 // Sebastien Pouliot <sebastien@ximian.com>
10 // (C) 2004 Andreas Nahr
11 // Copyright (C) 2004 Novell (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Threading;
35 using System.Globalization;
40 [System.Runtime.InteropServices.ComVisible (true)]
41 public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
48 if (MonoTouchAOTHelper.FalseFlag) {
49 var comparer = new System.Collections.Generic.GenericComparer <TimeSpan> ();
50 var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <TimeSpan> ();
54 public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
55 public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
56 public static readonly TimeSpan Zero = new TimeSpan (0L);
58 public const long TicksPerDay = 864000000000L;
59 public const long TicksPerHour = 36000000000L;
60 public const long TicksPerMillisecond = 10000L;
61 public const long TicksPerMinute = 600000000L;
62 public const long TicksPerSecond = 10000000L;
66 public TimeSpan (long ticks)
71 public TimeSpan (int hours, int minutes, int seconds)
73 CalculateTicks (0, hours, minutes, seconds, 0, true, out _ticks);
76 public TimeSpan (int days, int hours, int minutes, int seconds)
78 CalculateTicks (days, hours, minutes, seconds, 0, true, out _ticks);
81 public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
83 CalculateTicks (days, hours, minutes, seconds, milliseconds, true, out _ticks);
86 internal static bool CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds, bool throwExc, out long result)
88 // there's no overflow checks for hours, minutes, ...
89 // so big hours/minutes values can overflow at some point and change expected values
90 int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
91 int minsec = (minutes * 60);
92 long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
97 bool overflow = false;
98 // days is problematic because it can overflow but that overflow can be
99 // "legal" (i.e. temporary) (e.g. if other parameters are negative) or
100 // illegal (e.g. sign change).
102 long td = TicksPerDay * days;
106 // positive days -> total ticks should be lower
107 overflow = (ticks > t);
111 // positive + positive != negative result
116 long td = TicksPerDay * days;
119 // negative + negative != positive result
125 // negative days -> total ticks should be lower
126 overflow = (t > ticks);
132 throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
142 return (int) (_ticks / TicksPerDay);
148 return (int) (_ticks % TicksPerDay / TicksPerHour);
152 public int Milliseconds {
154 return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
160 return (int) (_ticks % TicksPerHour / TicksPerMinute);
166 return (int) (_ticks % TicksPerMinute / TicksPerSecond);
176 public double TotalDays {
178 return (double) _ticks / TicksPerDay;
182 public double TotalHours {
184 return (double) _ticks / TicksPerHour;
188 public double TotalMilliseconds {
190 return (double) _ticks / TicksPerMillisecond;
194 public double TotalMinutes {
196 return (double) _ticks / TicksPerMinute;
200 public double TotalSeconds {
202 return (double) _ticks / TicksPerSecond;
206 public TimeSpan Add (TimeSpan ts)
210 return new TimeSpan (_ticks + ts.Ticks);
213 catch (OverflowException) {
214 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
218 public static int Compare (TimeSpan t1, TimeSpan t2)
220 if (t1._ticks < t2._ticks)
222 if (t1._ticks > t2._ticks)
227 public int CompareTo (object value)
232 if (!(value is TimeSpan)) {
233 throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
236 return Compare (this, (TimeSpan) value);
239 public int CompareTo (TimeSpan value)
241 return Compare (this, value);
244 public bool Equals (TimeSpan obj)
246 return obj._ticks == _ticks;
249 public TimeSpan Duration ()
253 return new TimeSpan (Math.Abs (_ticks));
256 catch (OverflowException) {
257 throw new OverflowException (Locale.GetText (
258 "This TimeSpan value is MinValue so you cannot get the duration."));
262 public override bool Equals (object value)
264 if (!(value is TimeSpan))
267 return _ticks == ((TimeSpan) value)._ticks;
270 public static bool Equals (TimeSpan t1, TimeSpan t2)
272 return t1._ticks == t2._ticks;
275 public static TimeSpan FromDays (double value)
277 return From (value, TicksPerDay);
280 public static TimeSpan FromHours (double value)
282 return From (value, TicksPerHour);
285 public static TimeSpan FromMinutes (double value)
287 return From (value, TicksPerMinute);
290 public static TimeSpan FromSeconds (double value)
292 return From (value, TicksPerSecond);
295 public static TimeSpan FromMilliseconds (double value)
297 return From (value, TicksPerMillisecond);
300 private static TimeSpan From (double value, long tickMultiplicator)
302 if (Double.IsNaN (value))
303 throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
304 if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
305 (value < MinValue.Ticks) || (value > MaxValue.Ticks))
306 throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
309 value = (value * (tickMultiplicator / TicksPerMillisecond));
312 long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
313 return new TimeSpan (val * TicksPerMillisecond);
316 catch (OverflowException) {
317 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
321 public static TimeSpan FromTicks (long value)
323 return new TimeSpan (value);
326 public override int GetHashCode ()
328 return _ticks.GetHashCode ();
331 public TimeSpan Negate ()
333 if (_ticks == MinValue._ticks)
334 throw new OverflowException (Locale.GetText (
335 "This TimeSpan value is MinValue and cannot be negated."));
336 return new TimeSpan (-_ticks);
339 public static TimeSpan Parse (string s)
342 throw new ArgumentNullException ("s");
346 Parser p = new Parser (s);
347 p.Execute (false, out result);
351 public static bool TryParse (string s, out TimeSpan result)
354 result = TimeSpan.Zero;
358 Parser p = new Parser (s);
359 return p.Execute (true, out result);
363 public static TimeSpan Parse (string input, IFormatProvider formatProvider)
366 throw new ArgumentNullException ("input");
369 Parser p = new Parser (input, formatProvider);
370 p.Execute (false, out result);
374 public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result)
376 if (string.IsNullOrEmpty (input)) {
377 result = TimeSpan.Zero;
381 Parser p = new Parser (input, formatProvider);
382 return p.Execute (true, out result);
385 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider)
388 throw new ArgumentNullException ("format");
390 return ParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None);
393 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles)
396 throw new ArgumentNullException ("format");
398 return ParseExact (input, new string [] { format }, formatProvider, styles);
401 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider)
403 return ParseExact (input, formats, formatProvider, TimeSpanStyles.None);
406 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
409 throw new ArgumentNullException ("input");
411 throw new ArgumentNullException ("formats");
413 // All the errors found during the parsing process are reported as FormatException.
415 if (!TryParseExact (input, formats, formatProvider, styles, out result))
416 throw new FormatException ("Invalid format.");
421 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, out TimeSpan result)
423 return TryParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None, out result);
426 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles,
429 return TryParseExact (input, new string [] { format }, formatProvider, styles, out result);
432 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, out TimeSpan result)
434 return TryParseExact (input, formats, formatProvider, TimeSpanStyles.None, out result);
437 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles,
440 result = TimeSpan.Zero;
442 if (input == null || formats == null || formats.Length == 0)
445 Parser p = new Parser (input, formatProvider);
448 foreach (string format in formats) {
449 if (format == null || format.Length == 0)
450 return false; // wrong format, return immediately.
454 p.AllMembersRequired = false;
455 p.CultureSensitive = true;
456 p.UseColonAsDaySeparator = true;
459 p.AllMembersRequired = true;
460 p.CultureSensitive = true;
461 p.UseColonAsDaySeparator = true;
464 p.AllMembersRequired = false;
465 p.CultureSensitive = false;
466 p.UseColonAsDaySeparator = false;
469 // Single letter formats other than the defined ones are not accepted.
470 if (format.Length == 1)
473 if (p.ExecuteWithFormat (format, styles, true, out result))
478 if (p.Execute (true, out result))
486 public TimeSpan Subtract (TimeSpan ts)
490 return new TimeSpan (_ticks - ts.Ticks);
493 catch (OverflowException) {
494 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
498 public override string ToString ()
500 StringBuilder sb = new StringBuilder (14);
505 // We need to take absolute values of all components.
506 // Can't handle negative timespans by negating the TimeSpan
507 // as a whole. This would lead to an overflow for the
508 // degenerate case "TimeSpan.MinValue.ToString()".
510 sb.Append (Math.Abs (Days));
514 sb.Append (Math.Abs (Hours).ToString ("D2"));
516 sb.Append (Math.Abs (Minutes).ToString ("D2"));
518 sb.Append (Math.Abs (Seconds).ToString ("D2"));
520 int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
521 if (fractional != 0) {
523 sb.Append (fractional.ToString ("D7"));
526 return sb.ToString ();
530 public string ToString (string format)
532 return ToString (format, null);
535 public string ToString (string format, IFormatProvider formatProvider)
537 if (format == null || format.Length == 0 || format == "c" ||
538 format == "t" || format == "T") // Default version
541 if (format != "g" && format != "G")
542 return ToStringCustom (format); // custom formats ignore culture/formatProvider
544 NumberFormatInfo number_info = null;
545 if (formatProvider != null)
546 number_info = (NumberFormatInfo)formatProvider.GetFormat (typeof (NumberFormatInfo));
547 if (number_info == null)
548 number_info = Thread.CurrentThread.CurrentCulture.NumberFormat;
550 string decimal_separator = number_info.NumberDecimalSeparator;
551 int days, hours, minutes, seconds, milliseconds, fractional;
553 days = Math.Abs (Days);
554 hours = Math.Abs (Hours);
555 minutes = Math.Abs (Minutes);
556 seconds = Math.Abs (Seconds);
557 milliseconds = Math.Abs (Milliseconds);
558 fractional = (int) Math.Abs (_ticks % TicksPerSecond);
560 // Set Capacity depending on whether it's long or shot format
561 StringBuilder sb = new StringBuilder (format == "g" ? 16 : 32);
566 case "g": // short version
568 sb.Append (days.ToString ());
571 sb.Append (hours.ToString ());
573 sb.Append (minutes.ToString ("D2"));
575 sb.Append (seconds.ToString ("D2"));
576 if (milliseconds != 0) {
577 sb.Append (decimal_separator);
578 sb.Append (milliseconds.ToString ("D3"));
581 case "G": // long version
582 sb.Append (days.ToString ("D1"));
584 sb.Append (hours.ToString ("D2"));
586 sb.Append (minutes.ToString ("D2"));
588 sb.Append (seconds.ToString ("D2"));
589 sb.Append (decimal_separator);
590 sb.Append (fractional.ToString ("D7"));
594 return sb.ToString ();
597 string ToStringCustom (string format)
599 // Single char formats are not accepted.
600 if (format.Length < 2)
601 throw new FormatException ("The format is not recognized.");
603 FormatParser parser = new FormatParser (format);
604 FormatElement element;
607 StringBuilder sb = new StringBuilder (format.Length + 1);
613 element = parser.GetNextElement ();
614 switch (element.Type) {
615 case FormatElementType.Days:
616 value = Math.Abs (Days);
617 sb.Append (value.ToString ("D" + element.IntValue));
619 case FormatElementType.Hours:
620 value = Math.Abs (Hours);
621 sb.Append (value.ToString ("D" + element.IntValue));
623 case FormatElementType.Minutes:
624 value = Math.Abs (Minutes);
625 sb.Append (value.ToString ("D" + element.IntValue));
627 case FormatElementType.Seconds:
628 value = Math.Abs (Seconds);
629 sb.Append (value.ToString ("D" + element.IntValue));
631 case FormatElementType.Ticks:
632 value = Math.Abs (Milliseconds);
633 sb.Append (value.ToString ("D" + element.IntValue));
635 case FormatElementType.TicksUppercase:
636 value = Math.Abs (Milliseconds);
638 int threshold = (int)Math.Pow (10, element.IntValue);
639 while (value >= threshold)
641 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.");
655 return sb.ToString ();
659 public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
664 public static bool operator == (TimeSpan t1, TimeSpan t2)
666 return t1._ticks == t2._ticks;
669 public static bool operator > (TimeSpan t1, TimeSpan t2)
671 return t1._ticks > t2._ticks;
674 public static bool operator >= (TimeSpan t1, TimeSpan t2)
676 return t1._ticks >= t2._ticks;
679 public static bool operator != (TimeSpan t1, TimeSpan t2)
681 return t1._ticks != t2._ticks;
684 public static bool operator < (TimeSpan t1, TimeSpan t2)
686 return t1._ticks < t2._ticks;
689 public static bool operator <= (TimeSpan t1, TimeSpan t2)
691 return t1._ticks <= t2._ticks;
694 public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
696 return t1.Subtract (t2);
699 public static TimeSpan operator - (TimeSpan t)
704 public static TimeSpan operator + (TimeSpan t)
715 // Class Parser implements parser for TimeSpan.Parse
719 private int _cur = 0;
721 ParseError parse_error;
724 NumberFormatInfo number_format;
725 int parsed_numbers_count;
726 bool parsed_days_separator;
728 public bool Exact; // no fallback, strict pattern.
729 public bool AllMembersRequired;
730 public bool CultureSensitive = true;
731 public bool UseColonAsDaySeparator = true;
734 public Parser (string src)
737 _length = _src.Length;
739 number_format = GetNumberFormatInfo (null);
744 // Reset state data, so we can execute another parse over the input.
748 parse_error = ParseError.None;
749 parsed_ticks = parsed_days_separator = false;
750 parsed_numbers_count = 0;
753 public Parser (string src, IFormatProvider formatProvider) :
756 number_format = GetNumberFormatInfo (formatProvider);
759 NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
761 NumberFormatInfo format = null;
762 if (formatProvider != null)
763 format = (NumberFormatInfo) formatProvider.GetFormat (typeof (NumberFormatInfo));
765 format = Thread.CurrentThread.CurrentCulture.NumberFormat;
773 return _cur >= _length;
777 // All "Parse" functions throw a FormatException on syntax error.
778 // Their return value is semantic value of the item parsed.
780 // Range checking is spread over three different places:
781 // 1) When parsing "int" values, an exception is thrown immediately
782 // when the value parsed exceeds the maximum value for an int.
783 // 2) An explicit check is built in that checks for hours > 23 and
784 // for minutes and seconds > 59.
785 // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
786 // or < MinValue is left to the TimeSpan constructor called.
788 // Parse zero or more whitespace chars.
789 private void ParseWhiteSpace ()
791 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
796 // Parse optional sign character.
797 private bool ParseSign ()
801 if (!AtEnd && _src[_cur] == '-') {
810 // Used for custom formats parsing, where we may need to declare how
811 // many digits we expect, as well as the maximum allowed.
812 private int ParseIntExact (int digit_count, int max_digit_count)
817 // We can have more than one preceding zero here.
818 while (!AtEnd && Char.IsDigit (_src, _cur)) {
819 res = res * 10 + _src [_cur] - '0';
820 if (res > Int32.MaxValue) {
821 SetParseError (ParseError.Format);
828 // digit_count = 1 means we can use up to maximum count,
829 if (count == 0 || (digit_count > 1 && digit_count != count) ||
830 count > max_digit_count)
831 SetParseError (ParseError.Format);
837 // Parse simple int value
838 private int ParseInt (bool optional)
840 if (optional && AtEnd)
846 while (!AtEnd && Char.IsDigit (_src, _cur)) {
847 res = res * 10 + _src[_cur] - '0';
848 if (res > Int32.MaxValue) {
849 SetParseError (ParseError.Overflow);
856 if (!optional && (count == 0))
857 SetParseError (ParseError.Format);
860 parsed_numbers_count++;
867 // This behaves pretty much like ParseOptDot, but we need to have it
868 // as a separated routine for both days and decimal separators.
869 private bool ParseOptDaysSeparator ()
874 if (_src[_cur] == '.') {
876 parsed_days_separator = true;
882 // Just as ParseOptDot, but for decimal separator
883 private bool ParseOptDecimalSeparator ()
888 // we may need to provide compatibility with old versions using '.'
889 // for culture insensitve and non exact formats.
890 if (!Exact || !CultureSensitive)
891 if (_src [_cur] == '.') {
896 string decimal_separator = number_format.NumberDecimalSeparator;
897 if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
898 _cur += decimal_separator.Length;
905 private bool ParseLiteral (string value)
907 if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) {
908 _cur += value.Length;
915 private bool ParseChar (char c)
917 if (!AtEnd && _src [_cur] == c) {
925 // Parse optional dot
926 private bool ParseOptDot ()
931 if (_src[_cur] == '.') {
938 private void ParseColon (bool optional)
941 if (_src[_cur] == ':')
944 SetParseError (ParseError.Format);
948 // Parse [1..7] digits, representing fractional seconds (ticks)
949 // In 4.0 more than 7 digits will cause an OverflowException
950 private long ParseTicks ()
954 bool digitseen = false;
956 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
957 res = res + (_src[_cur] - '0') * mag;
964 SetParseError (ParseError.Format);
966 else if (!AtEnd && Char.IsDigit (_src, _cur))
967 SetParseError (ParseError.Overflow);
976 // Used by custom formats parsing
977 // digits_count = 0 for digits up to max_digits_count (optional), and other value to
978 // force a precise number of digits.
979 private long ParseTicksExact (int digits_count, int max_digits_count)
985 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
986 res = res + (_src [_cur] - '0') * mag;
992 if ((digits_count > 0 && count != digits_count) ||
993 count > max_digits_count)
994 SetParseError (ParseError.Format);
1000 void SetParseError (ParseError error)
1002 // We preserve the very first error.
1003 if (parse_error != ParseError.None)
1006 parse_error = error;
1010 bool CheckParseSuccess (bool tryParse)
1012 bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
1015 // We always report the first error, but for 2.0 we need to give a higher
1016 // precence to per-element overflow (as opposed to int32 overflow).
1018 if (parse_error == ParseError.Overflow) {
1020 if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
1024 throw new OverflowException (
1025 Locale.GetText ("Invalid time data."));
1028 if (parse_error == ParseError.Format) {
1031 throw new FormatException (
1032 Locale.GetText ("Invalid format for TimeSpan.Parse."));
1039 // We are using a different parse approach in 4.0, due to some changes in the behaviour
1040 // of the parse routines.
1041 // The input string is documented as:
1042 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1044 // There are some special cases as part of 4.0, however:
1045 // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss'
1046 // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value
1047 // exceeding the valid range for hours: 0-23.
1048 // 3. The decimal separator can be retrieved from the current culture, as well as keeping support
1049 // for the '.' value as part of keeping compatibility.
1051 // So we take the approach to parse, if possible, 4 integers, and depending on both how many were
1052 // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds.
1054 public bool Execute (bool tryParse, out TimeSpan result)
1057 int value1, value2, value3, value4;
1058 int days, hours, minutes, seconds;
1061 result = TimeSpan.Zero;
1062 value1 = value2 = value3 = value4 = 0;
1063 days = hours = minutes = seconds = 0;
1068 sign = ParseSign ();
1070 // Parse 4 integers, making only the first one non-optional.
1071 value1 = ParseInt (false);
1072 if (!ParseOptDaysSeparator ()) // Parse either day separator or colon
1075 value2 = ParseInt (true);
1076 value3 = value4 = 0;
1079 value3 = ParseInt (true);
1081 value4 = ParseInt (true);
1084 // We know the precise separator for ticks, so there's no need to guess.
1085 if (ParseOptDecimalSeparator ())
1086 ticks = ParseTicks ();
1091 SetParseError (ParseError.Format);
1094 // In Exact mode we cannot allow both ':' and '.' as day separator.
1095 if (UseColonAsDaySeparator && parsed_days_separator ||
1096 AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks))
1097 SetParseError (ParseError.Format);
1099 switch (parsed_numbers_count) {
1103 case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm'
1104 if (parsed_days_separator)
1105 SetParseError (ParseError.Format);
1111 case 3: // Assign the first value to days if we parsed a day separator or the value
1112 // is not in the valid range for hours.
1113 if (parsed_days_separator || value1 > 23) {
1123 case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss'
1124 if (!UseColonAsDaySeparator && !parsed_days_separator)
1125 SetParseError (ParseError.Format);
1135 if (hours > 23 || minutes > 59 || seconds > 59)
1136 SetParseError (ParseError.Overflow);
1138 if (!CheckParseSuccess (tryParse))
1142 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1146 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1147 } catch (OverflowException) {
1153 result = new TimeSpan (t);
1157 public bool Execute (bool tryParse, out TimeSpan result)
1166 result = TimeSpan.Zero;
1169 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1170 // ... but not entirely true as an lonely
1171 // integer will be parsed as a number of days
1173 sign = ParseSign ();
1174 days = ParseInt (false);
1175 if (ParseOptDot ()) {
1176 hours = ParseInt (true);
1184 minutes = ParseInt (true);
1188 seconds = ParseInt (true);
1191 if ( ParseOptDot () ) {
1192 ticks = ParseTicks ();
1200 SetParseError (ParseError.Format);
1202 if (!CheckParseSuccess (hours, minutes, seconds, tryParse))
1206 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1210 t = checked ((sign) ? (-t - ticks) : (t + ticks));
1211 } catch (OverflowException) {
1217 result = new TimeSpan (t);
1223 public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result)
1225 int days, hours, minutes, seconds;
1227 FormatElement format_element;
1229 days = hours = minutes = seconds = -1;
1231 result = TimeSpan.Zero;
1234 FormatParser format_parser = new FormatParser (format);
1237 // We need to continue even if AtEnd == true, since we could have
1238 // a optional second element.
1239 if (parse_error != ParseError.None)
1241 if (format_parser.AtEnd)
1244 format_element = format_parser.GetNextElement ();
1245 switch (format_element.Type) {
1246 case FormatElementType.Days:
1248 goto case FormatElementType.Error;
1249 days = ParseIntExact (format_element.IntValue, 8);
1251 case FormatElementType.Hours:
1253 goto case FormatElementType.Error;
1254 hours = ParseIntExact (format_element.IntValue, 2);
1256 case FormatElementType.Minutes:
1258 goto case FormatElementType.Error;
1259 minutes = ParseIntExact (format_element.IntValue, 2);
1261 case FormatElementType.Seconds:
1263 goto case FormatElementType.Error;
1264 seconds = ParseIntExact (format_element.IntValue, 2);
1266 case FormatElementType.Ticks:
1268 goto case FormatElementType.Error;
1269 ticks = ParseTicksExact (format_element.IntValue,
1270 format_element.IntValue);
1272 case FormatElementType.TicksUppercase:
1273 // Similar to Milliseconds, but optional and the
1274 // number of F defines the max length, not the required one.
1276 goto case FormatElementType.Error;
1277 ticks = ParseTicksExact (0, format_element.IntValue);
1279 case FormatElementType.Literal:
1280 if (!ParseLiteral (format_element.StringValue))
1281 SetParseError (ParseError.Format);
1283 case FormatElementType.EscapedChar:
1284 if (!ParseChar (format_element.CharValue))
1285 SetParseError (ParseError.Format);
1287 case FormatElementType.Error:
1288 SetParseError (ParseError.Format);
1304 if (!AtEnd || !format_parser.AtEnd)
1305 SetParseError (ParseError.Format);
1306 if (hours > 23 || minutes > 59 || seconds > 59)
1307 SetParseError (ParseError.Format);
1309 if (!CheckParseSuccess (tryParse))
1313 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1317 t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
1318 } catch (OverflowException) {
1324 result = new TimeSpan (t);
1330 enum FormatElementType
1337 TicksUppercase, // 'F'
1344 struct FormatElement
1346 public FormatElement (FormatElementType type)
1349 CharValue = (char)0;
1354 public FormatElementType Type;
1355 public char CharValue; // Used by EscapedChar
1356 public string StringValue; // Used by Literal
1357 public int IntValue; // Used by numerical elements.
1365 public FormatParser (string format)
1367 this.format = format;
1372 return cur >= format.Length;
1376 public FormatElement GetNextElement ()
1378 FormatElement element = new FormatElement ();
1381 return new FormatElement (FormatElementType.End);
1384 switch (format [cur]) {
1386 count = ParseChar ('d');
1388 return new FormatElement (FormatElementType.Error);
1389 element.Type = FormatElementType.Days;
1390 element.IntValue = count;
1393 count = ParseChar ('h');
1395 return new FormatElement (FormatElementType.Error);
1396 element.Type = FormatElementType.Hours;
1397 element.IntValue = count;
1400 count = ParseChar ('m');
1402 return new FormatElement (FormatElementType.Error);
1403 element.Type = FormatElementType.Minutes;
1404 element.IntValue = count;
1407 count = ParseChar ('s');
1409 return new FormatElement (FormatElementType.Error);
1410 element.Type = FormatElementType.Seconds;
1411 element.IntValue = count;
1414 count = ParseChar ('f');
1416 return new FormatElement (FormatElementType.Error);
1417 element.Type = FormatElementType.Ticks;
1418 element.IntValue = count;
1421 count = ParseChar ('F');
1423 return new FormatElement (FormatElementType.Error);
1424 element.Type = FormatElementType.TicksUppercase;
1425 element.IntValue = count;
1430 return new FormatElement (FormatElementType.Error);
1431 if (format [cur] == 'd')
1433 else if (format [cur] == 'h')
1435 else if (format [cur] == 'm')
1437 else if (format [cur] == 's')
1439 else if (format [cur] == 'f')
1441 else if (format [cur] == 'F')
1444 return new FormatElement (FormatElementType.Error);
1446 string literal = ParseLiteral ();
1447 if (literal == null)
1448 return new FormatElement (FormatElementType.Error);
1449 element.Type = FormatElementType.Literal;
1450 element.StringValue = literal;
1453 char escaped_char = ParseEscapedChar ();
1454 if ((int)escaped_char == 0)
1455 return new FormatElement (FormatElementType.Error);
1456 element.Type = FormatElementType.EscapedChar;
1457 element.CharValue = escaped_char;
1460 return new FormatElement (FormatElementType.Error);
1466 int ParseChar (char c)
1470 while (!AtEnd && format [cur] == c) {
1478 char ParseEscapedChar ()
1480 if (AtEnd || format [cur] != '\\')
1487 return format [cur++];
1490 string ParseLiteral ()
1495 if (AtEnd || format [cur] != '\'')
1499 while (!AtEnd && format [cur] != '\'') {
1504 if (!AtEnd && format [cur] == '\'') {
1506 return format.Substring (start, count);