2010-03-19 Carlos Alberto Cortez <calberto.cortez@gmail.com>
[mono.git] / mcs / class / corlib / System / TimeSpan.cs
index 1aa9473f465abc49ea71ab06a570e91fea074c64..a0a84906cda92e0c42a41693881c17dd6d407334 100644 (file)
 //
 
 using System.Text;
+using System.Threading;
+using System.Globalization;
 
 namespace System
 {
        [Serializable]
-       public struct TimeSpan :
-#if NET_2_0
-               IComparable, IComparable<TimeSpan>
-#else
-               IComparable
+       [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);
@@ -54,27 +63,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
@@ -83,6 +92,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 
@@ -116,10 +127,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 {
@@ -221,17 +236,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 ()
                {
@@ -329,9 +342,46 @@ namespace System
                                throw new ArgumentNullException ("s");
                        }
 
+                       TimeSpan result;
+                       Parser p = new Parser (s);
+                       p.Execute (false, out result);
+                       return result;
+               }
+
+               public static bool TryParse (string s, out TimeSpan result)
+               {
+                       if (s == null) {
+                               result = TimeSpan.Zero;
+                               return false;
+                       }
+
                        Parser p = new Parser (s);
-                       return p.Execute ();
+                       return p.Execute (true, out result);
+               }
+
+#if NET_4_0
+               public static TimeSpan Parse (string s, IFormatProvider formatProvider)
+               {
+                       if (s == null)
+                               throw new ArgumentNullException ("s");
+
+                       TimeSpan result;
+                       Parser p = new Parser (s, formatProvider);
+                       p.Execute (false, out result);
+                       return result;
+               }
+
+               public static bool TryParse (string s, IFormatProvider formatProvider, out TimeSpan result)
+               {
+                       if (s == null || s.Length == 0) {
+                               result = TimeSpan.Zero;
+                               return false;
+                       }
+
+                       Parser p = new Parser (s, formatProvider);
+                       return p.Execute (true, out result);
                }
+#endif
 
                public TimeSpan Subtract (TimeSpan ts)
                {
@@ -361,20 +411,88 @@ namespace System
                                sb.Append ('.');
                        }
 
-                       sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Hours), 2, 4));
+                       sb.Append (Math.Abs (Hours).ToString ("D2"));
                        sb.Append (':');
-                       sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Minutes), 2, 4));
+                       sb.Append (Math.Abs (Minutes).ToString ("D2"));
                        sb.Append (':');
-                       sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Seconds), 2, 4));
+                       sb.Append (Math.Abs (Seconds).ToString ("D2"));
 
                        int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
                        if (fractional != 0) {
                                sb.Append ('.');
-                               sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (fractional), 7, 4));
+                               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") // Default version
+                               return ToString ();
+
+                       if (format != "g" && format != "G")
+                               throw new FormatException ("The format is not recognized.");
+
+                       NumberFormatInfo number_info = null;
+                       if (formatProvider != null)
+                               number_info = (NumberFormatInfo)formatProvider.GetFormat (typeof (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 ();
                }
+#endif
 
                public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
                {
@@ -426,19 +544,49 @@ 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;
 
                        public Parser (string src)
                        {
                                _src = src;
                                _length = _src.Length;
+#if NET_4_0
+                               number_format = GetNumberFormatInfo (null);
+#endif
+                       }
+
+#if NET_4_0
+                       NumberFormatInfo number_format;
+
+                       public Parser (string src, IFormatProvider formatProvider) :
+                               this (src)
+                       {
+                               number_format = GetNumberFormatInfo (formatProvider);
                        }
+
+                       NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
+                       {
+                               NumberFormatInfo format = null;
+                               if (formatProvider != null)
+                                       format = (NumberFormatInfo) formatProvider.GetFormat (typeof (NumberFormatInfo));
+                               if (format == null)
+                                       format = Thread.CurrentThread.CurrentCulture.NumberFormat;
+
+                               return format;
+                       }
+#endif
        
                        public bool AtEnd {
                                get {
@@ -484,21 +632,23 @@ namespace System
                                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);
 
-                               return res;
+                               return (int)res;
                        }
 
                        // Parse optional dot
@@ -514,14 +664,36 @@ namespace System
                                return false;
                        }       
 
-                       // Parse optional (LAMESPEC) colon
-                       private void ParseOptColon ()
+#if NET_4_0
+                       // Just as ParseOptDot, but for decimal separator
+                       private bool ParseOptDecimalSeparator ()
+                       {
+                               if (AtEnd)
+                                       return false;
+
+                               // we need to provide compatibility with old versions using '.'
+                               if (_src [_cur] == '.') {
+                                       _cur++;
+                                       return true;
+                               }
+
+                               string decimal_separator = number_format.NumberDecimalSeparator;
+                               if (String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
+                                       _cur += decimal_separator.Length;
+                                       return true;
+                               }
+
+                               return false;
+                       }
+#endif
+
+                       private void ParseColon (bool optional)
                        {
                                if (!AtEnd) {
                                        if (_src[_cur] == ':')
                                                _cur++;
-                                       else 
-                                               formatError = true;
+                                       else if (!optional)
+                                               SetParseError (ParseError.Format);
                                }
                        }
 
@@ -540,12 +712,52 @@ namespace System
                                }
 
                                if (!digitseen)
-                                       formatError = true;
+                                       SetParseError (ParseError.Format);
 
                                return res;
                        }
 
-                       public TimeSpan Execute ()
+                       void SetParseError (ParseError error)
+                       {
+                               // We preserve the very first error.
+                               if (parse_error != ParseError.None)
+                                       return;
+
+                               parse_error = error;
+                       }
+
+                       bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
+                       {
+                               // FormatException has precedence over OverflowException starting with 4.0
+                               // so put the block before/after properly.
+#if NET_4_0
+                               if (parse_error == ParseError.Format) {
+                                       if (tryParse)
+                                               return false;
+                                       throw new FormatException (
+                                               Locale.GetText ("Invalid format for TimeSpan.Parse."));
+                               }
+#endif
+                               if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
+                                       if (tryParse)
+                                               return false;
+                                       throw new OverflowException (
+                                               Locale.GetText ("Invalid time data."));
+                               }
+#if !NET_4_0
+                               // Respect the Overflow precedence for 2.0, putting the format check last.
+                               if (parse_error == ParseError.Format) {
+                                       if (tryParse)
+                                               return false;
+                                       throw new FormatException (
+                                               Locale.GetText ("Invalid format for TimeSpan.Parse."));
+                               }
+#endif
+
+                               return true;
+                       }
+
+                       public bool Execute (bool tryParse, out TimeSpan result)
                        {
                                bool sign;
                                int days;
@@ -554,6 +766,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 
@@ -564,15 +778,27 @@ namespace System
                                if (ParseOptDot ()) {
                                        hours = ParseInt (true);
                                }
+#if NET_4_0
+                               // if the value that was going to be used as 'hours' exceeds the range,
+                               // .net keeps it as days, even if there's a colon instead of a dot ahead
+                               else if (days > 23) {
+                                       ParseColon (false);
+                                       hours = ParseInt (true);
+                               }
+#endif
                                else if (!AtEnd) {
                                        hours = days;
                                        days = 0;
                                }
-                               ParseOptColon();
+                               ParseColon(false);
                                minutes = ParseInt (true);
-                               ParseOptColon ();
+                               ParseColon (true);
                                seconds = ParseInt (true);
+#if NET_4_0
+                               if ( ParseOptDecimalSeparator () ) {
+#else
                                if ( ParseOptDot () ) {
+#endif
                                        ticks = ParseTicks ();
                                }
                                else {
@@ -581,21 +807,25 @@ 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."));
-                               }
-                               else if (formatError) {
-                                       throw new FormatException (
-                                               Locale.GetText ("Invalid format for TimeSpan.Parse."));
+                               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;
                                }
 
-                               long t = TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0);
-                               t = checked ((sign) ? (-t - ticks) : (t + ticks));
-                               return new TimeSpan (t);
+                               result = new TimeSpan (t);
+                               return true;
                        }
                }
        }