Merge pull request #900 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / corlib / System / TimeSpan.cs
index dd7c230d845f5d8f405e57374e6f9a29ad1d0673..7762dfc9177d180adbe5d7ec43d4a55ef72d1ce7 100644 (file)
@@ -5,10 +5,12 @@
 //   Duco Fijma (duco@lorentz.xs4all.nl)
 //   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
 //   Sebastien Pouliot  <sebastien@ximian.com>
+//   Marek Safar (marek.safar@gmail.com)
 //
 // (C) 2001 Duco Fijma
 // (C) 2004 Andreas Nahr
 // Copyright (C) 2004 Novell (http://www.novell.com)
+// Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 //
 
 using System.Text;
+using System.Threading;
+using System.Globalization;
 
 namespace System
 {
        [Serializable]
-       public struct TimeSpan : IComparable
-#if NET_2_0
-               , IComparable<TimeSpan>, IEquatable <TimeSpan>
+       [System.Runtime.InteropServices.ComVisible (true)]
+       public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
+#if NET_4_0
+                                , IFormattable
 #endif
        {
+#if MONOTOUCH
+               static TimeSpan () {
+                       if (MonoTouchAOTHelper.FalseFlag) {
+                               var comparer = new System.Collections.Generic.GenericComparer <TimeSpan> ();
+                               var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <TimeSpan> ();
+                       }
+               }
+#endif
                public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
                public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
                public static readonly TimeSpan Zero = new TimeSpan (0L);
@@ -52,27 +65,27 @@ namespace System
 
                private long _ticks;
 
-               public TimeSpan (long value)
+               public TimeSpan (long ticks)
                {
-                       _ticks = value;
+                       _ticks = ticks;
                }
 
                public TimeSpan (int hours, int minutes, int seconds)
                {
-                       _ticks = CalculateTicks (0, hours, minutes, seconds, 0);
+                       CalculateTicks (0, hours, minutes, seconds, 0, true, out _ticks);
                }
 
                public TimeSpan (int days, int hours, int minutes, int seconds)
                {
-                       _ticks = CalculateTicks (days, hours, minutes, seconds, 0);
+                       CalculateTicks (days, hours, minutes, seconds, 0, true, out _ticks);
                }
 
                public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
                {
-                       _ticks = CalculateTicks (days, hours, minutes, seconds, milliseconds);
+                       CalculateTicks (days, hours, minutes, seconds, milliseconds, true, out _ticks);
                }
 
-               internal static long CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds)
+               internal static bool CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds, bool throwExc, out long result)
                {
                        // there's no overflow checks for hours, minutes, ...
                        // so big hours/minutes values can overflow at some point and change expected values
@@ -81,6 +94,8 @@ namespace System
                        long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
                        t *= 10000;
 
+                       result = 0;
+
                        bool overflow = false;
                        // days is problematic because it can overflow but that overflow can be 
                        // "legal" (i.e. temporary) (e.g. if other parameters are negative) or 
@@ -114,10 +129,14 @@ namespace System
                                }
                        }
 
-                       if (overflow)
-                               throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
+                       if (overflow) {
+                               if (throwExc)
+                                       throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
+                               return false;
+                       }
 
-                       return t;
+                       result = t;
+                       return true;
                }
 
                public int Days {
@@ -219,17 +238,15 @@ namespace System
                        return Compare (this, (TimeSpan) value);
                }
 
-#if NET_2_0
                public int CompareTo (TimeSpan value)
                {
                        return Compare (this, value);
                }
 
-               public bool Equals (TimeSpan value)
+               public bool Equals (TimeSpan obj)
                {
-                       return value._ticks == _ticks;
+                       return obj._ticks == _ticks;
                }
-#endif
 
                public TimeSpan Duration ()
                {
@@ -294,7 +311,7 @@ namespace System
                                value = (value * (tickMultiplicator / TicksPerMillisecond));
 
                                checked {
-                                       long val = (long) Math.Round(value);
+                                       long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
                                        return new TimeSpan (val * TicksPerMillisecond);
                                }
                        }
@@ -327,20 +344,144 @@ namespace System
                                throw new ArgumentNullException ("s");
                        }
 
+                       TimeSpan result;
                        Parser p = new Parser (s);
-                       return p.Execute ();
+                       p.Execute (false, out result);
+                       return result;
                }
 
-#if NET_2_0
                public static bool TryParse (string s, out TimeSpan result)
                {
-                       try {
-                               result = Parse (s);
-                               return true;
-                       } catch {
+                       if (s == null) {
                                result = TimeSpan.Zero;
                                return false;
                        }
+
+                       Parser p = new Parser (s);
+                       return p.Execute (true, out result);
+               }
+
+#if NET_4_0
+               public static TimeSpan Parse (string input, IFormatProvider formatProvider)
+               {
+                       if (input == null)
+                               throw new ArgumentNullException ("input");
+
+                       TimeSpan result;
+                       Parser p = new Parser (input, formatProvider);
+                       p.Execute (false, out result);
+                       return result;
+               }
+
+               public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result)
+               {
+                       if (string.IsNullOrEmpty (input)) {
+                               result = TimeSpan.Zero;
+                               return false;
+                       }
+
+                       Parser p = new Parser (input, formatProvider);
+                       return p.Execute (true, out result);
+               }
+
+               public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider)
+               {
+                       if (format == null)
+                               throw new ArgumentNullException ("format");
+
+                       return ParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None);
+               }
+
+               public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles)
+               {
+                       if (format == null)
+                               throw new ArgumentNullException ("format");
+
+                       return ParseExact (input, new string [] { format }, formatProvider, styles);
+               }
+
+               public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider)
+               {
+                       return ParseExact (input, formats, formatProvider, TimeSpanStyles.None);
+               }
+
+               public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
+               {
+                       if (input == null)
+                               throw new ArgumentNullException ("input");
+                       if (formats == null)
+                               throw new ArgumentNullException ("formats");
+
+                       // All the errors found during the parsing process are reported as FormatException.
+                       TimeSpan result;
+                       if (!TryParseExact (input, formats, formatProvider, styles, out result))
+                               throw new FormatException ("Invalid format.");
+
+                       return result;
+               }
+
+               public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, out TimeSpan result)
+               {
+                       return TryParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None, out result);
+               }
+
+               public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles,
+                               out TimeSpan result)
+               {
+                       return TryParseExact (input, new string [] { format }, formatProvider, styles, out result);
+               }
+
+               public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, out TimeSpan result)
+               {
+                       return TryParseExact (input, formats, formatProvider, TimeSpanStyles.None, out result);
+               }
+
+               public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles,
+                       out TimeSpan result)
+               {
+                       result = TimeSpan.Zero;
+
+                       if (input == null || formats == null || formats.Length == 0)
+                               return false;
+
+                       Parser p = new Parser (input, formatProvider);
+                       p.Exact = true;
+
+                       foreach (string format in formats) {
+                               if (format == null || format.Length == 0)
+                                       return false; // wrong format, return immediately.
+
+                               switch (format) {
+                                       case "g":
+                                               p.AllMembersRequired = false;
+                                               p.CultureSensitive = true;
+                                               p.UseColonAsDaySeparator = true;
+                                               break;
+                                       case "G":
+                                               p.AllMembersRequired = true;
+                                               p.CultureSensitive = true;
+                                               p.UseColonAsDaySeparator = true;
+                                               break;
+                                       case "c":
+                                               p.AllMembersRequired = false;
+                                               p.CultureSensitive = false;
+                                               p.UseColonAsDaySeparator = false;
+                                               break;
+                                       default:
+                                               // Single letter formats other than the defined ones are not accepted.
+                                               if (format.Length == 1)
+                                                       return false;
+                                               // custom format
+                                               if (p.ExecuteWithFormat (format, styles, true, out result))
+                                                       return true;
+                                               continue;
+                               }
+
+                               if (p.Execute (true, out result))
+                                       return true;
+                       }
+
+                       return false;
                }
 #endif
 
@@ -372,22 +513,151 @@ namespace System
                                sb.Append ('.');
                        }
 
-                       System.Globalization.NumberFormatInfo nfi = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
-                       sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Hours)), 2, nfi));
+                       sb.Append (Math.Abs (Hours).ToString ("D2"));
                        sb.Append (':');
-                       sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Minutes)), 2, nfi));
+                       sb.Append (Math.Abs (Minutes).ToString ("D2"));
                        sb.Append (':');
-                       sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Seconds)), 2, nfi));
+                       sb.Append (Math.Abs (Seconds).ToString ("D2"));
 
                        int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
                        if (fractional != 0) {
                                sb.Append ('.');
-                               sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore (fractional), 7, nfi));
+                               sb.Append (fractional.ToString ("D7"));
                        }
 
                        return sb.ToString ();
                }
 
+#if NET_4_0
+               public string ToString (string format)
+               {
+                       return ToString (format, null);
+               }
+
+               public string ToString (string format, IFormatProvider formatProvider)
+               {
+                       if (format == null || format.Length == 0 || format == "c" ||
+                                       format == "t" || format == "T") // Default version
+                               return ToString ();
+
+                       if (format != "g" && format != "G")
+                               return ToStringCustom (format); // custom formats ignore culture/formatProvider
+
+                       NumberFormatInfo number_info = null;
+                       if (formatProvider != null)
+                               number_info = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
+                       if (number_info == null)
+                               number_info = Thread.CurrentThread.CurrentCulture.NumberFormat;
+
+                       string decimal_separator = number_info.NumberDecimalSeparator;
+                       int days, hours, minutes, seconds, milliseconds, fractional;
+
+                       days = Math.Abs (Days);
+                       hours = Math.Abs (Hours);
+                       minutes = Math.Abs (Minutes);
+                       seconds = Math.Abs (Seconds);
+                       milliseconds = Math.Abs (Milliseconds);
+                       fractional = (int) Math.Abs (_ticks % TicksPerSecond);
+
+                       // Set Capacity depending on whether it's long or shot format
+                       StringBuilder sb = new StringBuilder (format == "g" ? 16 : 32);
+                       if (_ticks < 0)
+                               sb.Append ('-');
+
+                       switch (format) {
+                               case "g": // short version
+                                       if (days != 0) {
+                                               sb.Append (days.ToString ());
+                                               sb.Append (':');
+                                       }
+                                       sb.Append (hours.ToString ());
+                                       sb.Append (':');
+                                       sb.Append (minutes.ToString ("D2"));
+                                       sb.Append (':');
+                                       sb.Append (seconds.ToString ("D2"));
+                                       if (milliseconds != 0) {
+                                               sb.Append (decimal_separator);
+                                               sb.Append (milliseconds.ToString ("D3"));
+                                       }
+                                       break;
+                               case "G": // long version
+                                       sb.Append (days.ToString ("D1"));
+                                       sb.Append (':');
+                                       sb.Append (hours.ToString ("D2"));
+                                       sb.Append (':');
+                                       sb.Append (minutes.ToString ("D2"));
+                                       sb.Append (':');
+                                       sb.Append (seconds.ToString ("D2"));
+                                       sb.Append (decimal_separator);
+                                       sb.Append (fractional.ToString ("D7"));
+                                       break;
+                       }
+
+                       return sb.ToString ();
+               }
+
+               string ToStringCustom (string format)
+               {
+                       // Single char formats are not accepted.
+                       if (format.Length < 2)
+                               throw new FormatException ("The format is not recognized.");
+
+                       FormatParser parser = new FormatParser (format);
+                       FormatElement element;
+                       int value;
+
+                       StringBuilder sb = new StringBuilder (format.Length + 1);
+
+                       while (true) {
+                               if (parser.AtEnd)
+                                       break;
+
+                               element = parser.GetNextElement ();
+                               switch (element.Type) {
+                                       case FormatElementType.Days:
+                                               value = Math.Abs (Days);
+                                               break;
+                                       case FormatElementType.Hours:
+                                               value = Math.Abs (Hours);
+                                               break;
+                                       case FormatElementType.Minutes:
+                                               value = Math.Abs (Minutes);
+                                               break;
+                                       case FormatElementType.Seconds:
+                                               value = Math.Abs (Seconds);
+                                               break;
+                                       case FormatElementType.Ticks:
+                                       case FormatElementType.TicksUppercase:
+                                               value = Math.Abs (Milliseconds);
+                                               if (value == 0) {
+                                                       if (element.Type == FormatElementType.Ticks)
+                                                               break;
+
+                                                       continue;
+                                               }
+
+                                               int threshold = (int)Math.Pow (10, element.IntValue);
+                                               while (value >= threshold)
+                                                       value /= 10;
+                                               sb.Append (value.ToString ());
+                                               continue;
+                                       case FormatElementType.EscapedChar:
+                                               sb.Append (element.CharValue);
+                                               continue;
+                                       case FormatElementType.Literal:
+                                               sb.Append (element.StringValue);
+                                               continue;
+                                       default:
+                                               throw new FormatException ("The format is not recognized.");
+                               }
+
+                               sb.Append (value.ToString ("D" + element.IntValue.ToString ()));
+                       }
+
+                       return sb.ToString ();
+               }
+#endif
+
                public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
                {
                        return t1.Add (t2);
@@ -438,19 +708,67 @@ namespace System
                        return t;
                }
 
+               enum ParseError {
+                       None,
+                       Format,
+                       Overflow
+               }
+
                // Class Parser implements parser for TimeSpan.Parse
                private class Parser
                {
                        private string _src;
                        private int _cur = 0;
                        private int _length;
-                       private bool formatError;
+                       ParseError parse_error;
+#if NET_4_0
+                       bool parsed_ticks;
+                       NumberFormatInfo number_format;
+                       int parsed_numbers_count;
+                       bool parsed_days_separator;
+
+                       public bool Exact; // no fallback, strict pattern.
+                       public bool AllMembersRequired;
+                       public bool CultureSensitive = true;
+                       public bool UseColonAsDaySeparator = true;
+#endif
 
                        public Parser (string src)
                        {
                                _src = src;
                                _length = _src.Length;
+#if NET_4_0
+                               number_format = GetNumberFormatInfo (null);
+#endif
+                       }
+
+#if NET_4_0
+                       // Reset state data, so we can execute another parse over the input.
+                       void Reset ()
+                       {
+                               _cur = 0;
+                               parse_error = ParseError.None;
+                               parsed_ticks = parsed_days_separator = false;
+                               parsed_numbers_count = 0;
+                       }
+
+                       public Parser (string src, IFormatProvider formatProvider) :
+                               this (src)
+                       {
+                               number_format = GetNumberFormatInfo (formatProvider);
+                       }
+
+                       static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
+                       {
+                               NumberFormatInfo format = null;
+                               if (formatProvider != null)
+                                       format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
+                               if (format == null)
+                                       format = Thread.CurrentThread.CurrentCulture.NumberFormat;
+
+                               return format;
                        }
+#endif
        
                        public bool AtEnd {
                                get {
@@ -490,29 +808,122 @@ namespace System
                                return res;
                        }
 
+#if NET_4_0
+                       // Used for custom formats parsing, where we may need to declare how
+                       // many digits we expect, as well as the maximum allowed.
+                       private int ParseIntExact (int digit_count, int max_digit_count)
+                       {
+                               long res = 0;
+                               int count = 0;
+
+                               // We can have more than one preceding zero here.
+                               while (!AtEnd && Char.IsDigit (_src, _cur)) {
+                                       res = res * 10 + _src [_cur] - '0';
+                                       if (res > Int32.MaxValue) {
+                                               SetParseError (ParseError.Format);
+                                               break;
+                                       }
+                                       _cur++;
+                                       count++;
+                               }
+
+                               // digit_count = 1 means we can use up to maximum count,
+                               if (count == 0 || (digit_count > 1 && digit_count != count) ||
+                                               count > max_digit_count)
+                                       SetParseError (ParseError.Format);
+
+                               return (int)res;
+                       }
+#endif
+
                        // Parse simple int value
                        private int ParseInt (bool optional)
                        {
                                if (optional && AtEnd)
                                        return 0;
 
-                               int res = 0;
+                               long res = 0;
                                int count = 0;
 
                                while (!AtEnd && Char.IsDigit (_src, _cur)) {
-                                       checked {
-                                               res = res * 10 + _src[_cur] - '0';
+                                       res = res * 10 + _src[_cur] - '0';
+                                       if (res > Int32.MaxValue) {
+                                               SetParseError (ParseError.Overflow);
+                                               break;
                                        }
                                        _cur++;
                                        count++;
                                }
 
-                               if (count == 0)
-                                       formatError = true;
+                               if (!optional && (count == 0))
+                                       SetParseError (ParseError.Format);
+#if NET_4_0
+                               if (count > 0)
+                                       parsed_numbers_count++;
+#endif
 
-                               return res;
+                               return (int)res;
                        }
 
+#if NET_4_0
+                       // This behaves pretty much like ParseOptDot, but we need to have it
+                       // as a separated routine for both days and decimal separators.
+                       private bool ParseOptDaysSeparator ()
+                       {
+                               if (AtEnd)
+                                       return false;
+
+                               if (_src[_cur] == '.') {
+                                       _cur++;
+                                       parsed_days_separator = true;
+                                       return true;
+                               }
+                               return false;
+                       }
+
+                       // Just as ParseOptDot, but for decimal separator
+                       private bool ParseOptDecimalSeparator ()
+                       {
+                               if (AtEnd)
+                                       return false;
+
+                               // we may need to provide compatibility with old versions using '.'
+                               // for culture insensitve and non exact formats.
+                               if (!Exact || !CultureSensitive)
+                                       if (_src [_cur] == '.') {
+                                               _cur++;
+                                               return true;
+                                       }
+
+                               string decimal_separator = number_format.NumberDecimalSeparator;
+                               if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
+                                       _cur += decimal_separator.Length;
+                                       return true;
+                               }
+
+                               return false;
+                       }
+
+                       private bool ParseLiteral (string value)
+                       {
+                               if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) {
+                                       _cur += value.Length;
+                                       return true;
+                               }
+
+                               return false;
+                       }
+
+                       private bool ParseChar (char c)
+                       {
+                               if (!AtEnd && _src [_cur] == c) {
+                                       _cur++;
+                                       return true;
+                               }
+
+                               return false;
+                       }
+#endif
                        // Parse optional dot
                        private bool ParseOptDot ()
                        {
@@ -526,18 +937,18 @@ namespace System
                                return false;
                        }       
 
-                       // Parse optional (LAMESPEC) colon
-                       private void ParseOptColon ()
+                       private void ParseColon (bool optional)
                        {
                                if (!AtEnd) {
                                        if (_src[_cur] == ':')
                                                _cur++;
-                                       else 
-                                               formatError = true;
+                                       else if (!optional)
+                                               SetParseError (ParseError.Format);
                                }
                        }
 
                        // Parse [1..7] digits, representing fractional seconds (ticks)
+                       // In 4.0 more than 7 digits will cause an OverflowException
                        private long ParseTicks ()
                        {
                                long mag = 1000000;
@@ -552,12 +963,200 @@ namespace System
                                }
 
                                if (!digitseen)
-                                       formatError = true;
+                                       SetParseError (ParseError.Format);
+#if NET_4_0
+                               else if (!AtEnd && Char.IsDigit (_src, _cur))
+                                       SetParseError (ParseError.Overflow);
+
+                               parsed_ticks = true;
+#endif
+
+                               return res;
+                       }
+
+#if NET_4_0
+                       // Used by custom formats parsing
+                       // digits_count = 0 for digits up to max_digits_count (optional), and other value to
+                       // force a precise number of digits.
+                       private long ParseTicksExact (int digits_count, int max_digits_count)
+                       {
+                               long mag = 1000000;
+                               long res = 0;
+                               int count = 0;
+
+                               while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
+                                       res = res + (_src [_cur] - '0') * mag;
+                                       _cur++;
+                                       count++;
+                                       mag = mag / 10;
+                               }
+
+                               if ((digits_count > 0 && count != digits_count) ||
+                                               count > max_digits_count)
+                                       SetParseError (ParseError.Format);
 
                                return res;
                        }
+#endif
 
-                       public TimeSpan Execute ()
+                       void SetParseError (ParseError error)
+                       {
+                               // We preserve the very first error.
+                               if (parse_error != ParseError.None)
+                                       return;
+
+                               parse_error = error;
+                       }
+
+#if NET_4_0
+                       bool CheckParseSuccess (bool tryParse)
+#else
+                       bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
+#endif
+                       {
+                               // We always report the first error, but for 2.0 we need to give a higher
+                               // precence to per-element overflow (as opposed to int32 overflow).
+#if NET_4_0
+                               if (parse_error == ParseError.Overflow) {
+#else
+                               if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
+#endif
+                                       if (tryParse)
+                                               return false;
+                                       throw new OverflowException (
+                                               Locale.GetText ("Invalid time data."));
+                               }
+
+                               if (parse_error == ParseError.Format) {
+                                       if (tryParse)
+                                               return false;
+                                       throw new FormatException (
+                                               Locale.GetText ("Invalid format for TimeSpan.Parse."));
+                               }
+
+                               return true;
+                       }
+
+#if NET_4_0
+                       // We are using a different parse approach in 4.0, due to some changes in the behaviour
+                       // of the parse routines.
+                       // The input string is documented as:
+                       //      Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
+                       //
+                       // There are some special cases as part of 4.0, however:
+                       // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss'
+                       // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value
+                       // exceeding the valid range for hours: 0-23.
+                       // 3. The decimal separator can be retrieved from the current culture, as well as keeping support
+                       // for the '.' value as part of keeping compatibility.
+                       //
+                       // So we take the approach to parse, if possible, 4 integers, and depending on both how many were
+                       // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds.
+                       //
+                       public bool Execute (bool tryParse, out TimeSpan result)
+                       {
+                               bool sign;
+                               int value1, value2, value3, value4;
+                               int days, hours, minutes, seconds;
+                               long ticks = 0;
+
+                               result = TimeSpan.Zero;
+                               value1 = value2 = value3 = value4 = 0;
+                               days = hours = minutes = seconds = 0;
+
+                               Reset ();
+
+                               ParseWhiteSpace ();
+                               sign = ParseSign ();
+
+                               // Parse 4 integers, making only the first one non-optional.
+                               value1 = ParseInt (false);
+                               if (!ParseOptDaysSeparator ()) // Parse either day separator or colon
+                                       ParseColon (false);
+                               int p = _cur;
+                               value2 = ParseInt (true);
+                               value3 = value4 = 0;
+                               if (p < _cur) {
+                                       ParseColon (true);
+                                       value3 = ParseInt (true);
+                                       ParseColon (true);
+                                       value4 = ParseInt (true);
+                               }
+
+                               // We know the precise separator for ticks, so there's no need to guess.
+                               if (ParseOptDecimalSeparator ())
+                                       ticks = ParseTicks ();
+
+                               ParseWhiteSpace ();
+
+                               if (!AtEnd)
+                                       SetParseError (ParseError.Format);
+
+                               if (Exact)
+                                       // In Exact mode we cannot allow both ':' and '.' as day separator.
+                                       if (UseColonAsDaySeparator && parsed_days_separator ||
+                                               AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks))
+                                               SetParseError (ParseError.Format);
+
+                               switch (parsed_numbers_count) {
+                                       case 1:
+                                               days = value1;
+                                               break;
+                                       case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm'
+                                               if (parsed_days_separator)
+                                                       SetParseError (ParseError.Format);
+                                               else {
+                                                       hours = value1;
+                                                       minutes = value2;
+                                               }
+                                               break;
+                                       case 3: // Assign the first value to days if we parsed a day separator or the value
+                                               // is not in the valid range for hours.
+                                               if (parsed_days_separator || value1 > 23) {
+                                                       days = value1;
+                                                       hours = value2;
+                                                       minutes = value3;
+                                               } else {
+                                                       hours = value1;
+                                                       minutes = value2;
+                                                       seconds = value3;
+                                               }
+                                               break;
+                                       case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss'
+                                               if (!UseColonAsDaySeparator && !parsed_days_separator)
+                                                       SetParseError (ParseError.Format);
+                                               else {
+                                                       days = value1;
+                                                       hours = value2;
+                                                       minutes = value3;
+                                                       seconds = value4;
+                                               }
+                                               break;
+                               }
+
+                               if (hours > 23 || minutes > 59 || seconds > 59)
+                                       SetParseError (ParseError.Overflow);
+
+                               if (!CheckParseSuccess (tryParse))
+                                       return false;
+
+                               long t;
+                               if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
+                                       return false;
+
+                               try {
+                                       t = checked ((sign) ? (-t - ticks) : (t + ticks));
+                               } catch (OverflowException) {
+                                       if (tryParse)
+                                               return false;
+                                       throw;
+                               }
+
+                               result = new TimeSpan (t);
+                               return true;
+                       }
+#else
+                       public bool Execute (bool tryParse, out TimeSpan result)
                        {
                                bool sign;
                                int days;
@@ -566,6 +1165,8 @@ namespace System
                                int seconds;
                                long ticks;
 
+                               result = TimeSpan.Zero;
+
                                // documented as...
                                // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
                                // ... but not entirely true as an lonely 
@@ -580,10 +1181,15 @@ namespace System
                                        hours = days;
                                        days = 0;
                                }
-                               ParseOptColon();
+                               ParseColon(false);
+                               int p = _cur;
                                minutes = ParseInt (true);
-                               ParseOptColon ();
-                               seconds = ParseInt (true);
+                               seconds = 0;
+                               if (p < _cur) {
+                                       ParseColon (true);
+                                       seconds = ParseInt (true);
+                               }
+
                                if ( ParseOptDot () ) {
                                        ticks = ParseTicks ();
                                }
@@ -593,22 +1199,319 @@ namespace System
                                ParseWhiteSpace ();
        
                                if (!AtEnd)
-                                       formatError = true;
+                                       SetParseError (ParseError.Format);
 
-                               // Overflow has presceance over FormatException
-                               if (hours > 23 || minutes > 59 || seconds > 59) {
-                                       throw new OverflowException (
-                                               Locale.GetText ("Invalid time data."));
+                               if (!CheckParseSuccess (hours, minutes, seconds, tryParse))
+                                       return false;
+
+                               long t;
+                               if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
+                                       return false;
+
+                               try {
+                                       t = checked ((sign) ? (-t - ticks) : (t + ticks));
+                               } catch (OverflowException) {
+                                       if (tryParse)
+                                               return false;
+                                       throw;
                                }
-                               else if (formatError) {
-                                       throw new FormatException (
-                                               Locale.GetText ("Invalid format for TimeSpan.Parse."));
+
+                               result = new TimeSpan (t);
+                               return true;
+                       }
+#endif
+
+#if NET_4_0
+                       public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result)
+                       {
+                               int days, hours, minutes, seconds;
+                               long ticks;
+                               FormatElement format_element;
+
+                               days = hours = minutes = seconds = -1;
+                               ticks = -1;
+                               result = TimeSpan.Zero;
+                               Reset ();
+
+                               FormatParser format_parser = new FormatParser (format);
+
+                               for (;;) {
+                                       // We need to continue even if AtEnd == true, since we could have
+                                       // a optional second element.
+                                       if (parse_error != ParseError.None)
+                                               break;
+                                       if (format_parser.AtEnd)
+                                               break;
+
+                                       format_element = format_parser.GetNextElement ();
+                                       switch (format_element.Type) {
+                                               case FormatElementType.Days:
+                                                       if (days != -1)
+                                                               goto case FormatElementType.Error;
+                                                       days = ParseIntExact (format_element.IntValue, 8);
+                                                       break;
+                                               case FormatElementType.Hours:
+                                                       if (hours != -1)
+                                                               goto case FormatElementType.Error;
+                                                       hours = ParseIntExact (format_element.IntValue, 2);
+                                                       break;
+                                               case FormatElementType.Minutes:
+                                                       if (minutes != -1)
+                                                               goto case FormatElementType.Error;
+                                                       minutes = ParseIntExact (format_element.IntValue, 2);
+                                                       break;
+                                               case FormatElementType.Seconds:
+                                                       if (seconds != -1)
+                                                               goto case FormatElementType.Error;
+                                                       seconds = ParseIntExact (format_element.IntValue, 2);
+                                                       break;
+                                               case FormatElementType.Ticks:
+                                                       if (ticks != -1)
+                                                               goto case FormatElementType.Error;
+                                                       ticks = ParseTicksExact (format_element.IntValue,
+                                                                       format_element.IntValue);
+                                                       break;
+                                               case FormatElementType.TicksUppercase:
+                                                       // Similar to Milliseconds, but optional and the
+                                                       // number of F defines the max length, not the required one.
+                                                       if (ticks != -1)
+                                                               goto case FormatElementType.Error;
+                                                       ticks = ParseTicksExact (0, format_element.IntValue);
+                                                       break;
+                                               case FormatElementType.Literal:
+                                                       if (!ParseLiteral (format_element.StringValue))
+                                                               SetParseError (ParseError.Format);
+                                                       break;
+                                               case FormatElementType.EscapedChar:
+                                                       if (!ParseChar (format_element.CharValue))
+                                                               SetParseError (ParseError.Format);
+                                                       break;
+                                               case FormatElementType.Error:
+                                                       SetParseError (ParseError.Format);
+                                                       break;
+                                       }
+                               }
+
+                               if (days == -1)
+                                       days = 0;
+                               if (hours == -1)
+                                       hours = 0;
+                               if (minutes == -1)
+                                       minutes = 0;
+                               if (seconds == -1)
+                                       seconds = 0;
+                               if (ticks == -1)
+                                       ticks = 0;
+
+                               if (!AtEnd || !format_parser.AtEnd)
+                                       SetParseError (ParseError.Format);
+                               if (hours > 23 || minutes > 59 || seconds > 59)
+                                       SetParseError (ParseError.Format);
+
+                               if (!CheckParseSuccess (tryParse))
+                                       return false;
+
+                               long t;
+                               if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
+                                       return false;
+
+                               try {
+                                       t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
+                               } catch (OverflowException) {
+                                       if (tryParse)
+                                               return false;
+                                       throw;
+                               }
+
+                               result = new TimeSpan (t);
+                               return true;
+                       }
+#endif
+               }
+#if NET_4_0
+               enum FormatElementType 
+               {
+                       Days,
+                       Hours,
+                       Minutes,
+                       Seconds,
+                       Ticks, // 'f'
+                       TicksUppercase, // 'F'
+                       Literal,
+                       EscapedChar,
+                       Error,
+                       End
+               }
+
+               struct FormatElement
+               {
+                       public FormatElement (FormatElementType type)
+                       {
+                               Type = type;
+                               CharValue = (char)0;
+                               IntValue = 0;
+                               StringValue = null;
+                       }
+
+                       public FormatElementType Type;
+                       public char CharValue; // Used by EscapedChar
+                       public string StringValue; // Used by Literal
+                       public int IntValue; // Used by numerical elements.
+               }
+
+               class FormatParser 
+               {
+                       int cur;
+                       string format;
+
+                       public FormatParser (string format)
+                       {
+                               this.format = format;
+                       }
+
+                       public bool AtEnd {
+                               get {
+                                       return cur >= format.Length;
+                               }
+                       }
+
+                       public FormatElement GetNextElement ()
+                       {
+                               FormatElement element = new FormatElement ();
+
+                               if (AtEnd)
+                                       return new FormatElement (FormatElementType.End);
+
+                               int count = 0;
+                               switch (format [cur]) {
+                                       case 'd':
+                                               count = ParseChar ('d');
+                                               if (count > 8)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Days;
+                                               element.IntValue = count;
+                                               break;
+                                       case 'h':
+                                               count = ParseChar ('h');
+                                               if (count > 2)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Hours;
+                                               element.IntValue = count;
+                                               break;
+                                       case 'm':
+                                               count = ParseChar ('m');
+                                               if (count > 2)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Minutes;
+                                               element.IntValue = count;
+                                               break;
+                                       case 's':
+                                               count = ParseChar ('s');
+                                               if (count > 2)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Seconds;
+                                               element.IntValue = count;
+                                               break;
+                                       case 'f':
+                                               count = ParseChar ('f');
+                                               if (count > 7)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Ticks;
+                                               element.IntValue = count;
+                                               break;
+                                       case 'F':
+                                               count = ParseChar ('F');
+                                               if (count > 7)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.TicksUppercase;
+                                               element.IntValue = count;
+                                               break;
+                                       case '%':
+                                               cur++;
+                                               if (AtEnd)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               if (format [cur] == 'd')
+                                                       goto case 'd';
+                                               else if (format [cur] == 'h')
+                                                       goto case 'h';
+                                               else if (format [cur] == 'm')
+                                                       goto case 'm';
+                                               else if (format [cur] == 's')
+                                                       goto case 's';
+                                               else if (format [cur] == 'f')
+                                                       goto case 'f';
+                                               else if (format [cur] == 'F')
+                                                       goto case 'F';
+
+                                               return new FormatElement (FormatElementType.Error);
+                                       case '\'':
+                                               string literal = ParseLiteral ();
+                                               if (literal == null)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.Literal;
+                                               element.StringValue = literal;
+                                               break;
+                                       case '\\':
+                                               char escaped_char = ParseEscapedChar ();
+                                               if ((int)escaped_char == 0)
+                                                       return new FormatElement (FormatElementType.Error);
+                                               element.Type = FormatElementType.EscapedChar;
+                                               element.CharValue = escaped_char;
+                                               break;
+                                       default:
+                                               return new FormatElement (FormatElementType.Error);
+                               }
+
+                               return element;
+                       }
+
+                       int ParseChar (char c)
+                       {
+                               int count = 0;
+
+                               while (!AtEnd && format [cur] == c) {
+                                       cur++;
+                                       count++;
                                }
 
-                               long t = TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0);
-                               t = checked ((sign) ? (-t - ticks) : (t + ticks));
-                               return new TimeSpan (t);
+                               return count;
+                       }
+
+                       char ParseEscapedChar ()
+                       {
+                               if (AtEnd || format [cur] != '\\')
+                                       return (char)0;
+
+                               cur++;
+                               if (AtEnd)
+                                       return (char)0;
+
+                               return format [cur++];
+                       }
+
+                       string ParseLiteral ()
+                       {
+                               int start;
+                               int count = 0;
+
+                               if (AtEnd || format [cur] != '\'')
+                                       return null;
+
+                               start = ++cur;
+                               while (!AtEnd && format [cur] != '\'') {
+                                       cur++;
+                                       count++;
+                               }
+
+                               if (!AtEnd && format [cur] == '\'') {
+                                       cur++;
+                                       return format.Substring (start, count);
+                               }
+
+                               return null;
                        }
                }
+#endif
+
        }
 }