//
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);
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
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
}
}
- 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 {
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 ()
{
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)
{
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") // 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)
{
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 {
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
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);
}
}
}
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;
int seconds;
long ticks;
+ result = TimeSpan.Zero;
+
// documented as...
// Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
// ... but not entirely true as an lonely
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 {
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;
}
}
}