//
// System.TimeSpan.cs
//
-// author:
+// Authors:
// Duco Fijma (duco@lorentz.xs4all.nl)
+// Andreas Nahr (ClassDevelopment@A-SoftTech.com)
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2001 Duco Fijma
+// (C) 2004 Andreas Nahr
+// Copyright (C) 2004 Novell (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// (C) 2001 Duco Fijma
-
-namespace System {
-
- public struct TimeSpan : IComparable {
- private long ticks;
-
- // Ctors
-
- public TimeSpan (long value) { ticks = value; }
- public TimeSpan (int hours, int minutes, int seconds)
- : this(0, hours, minutes, seconds, 0) {}
- public TimeSpan (int days, int hours, int minutes, int seconds)
- : this(days, hours, minutes, seconds, 0) {}
- public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
- {
- ticks = TicksPerDay * days +
- TicksPerHour * hours +
- TicksPerMinute * minutes +
- TicksPerSecond * seconds +
- TicksPerMillisecond * milliseconds;
- }
-
- // Fields
+using System.Text;
+
+namespace System
+{
+ [Serializable]
+#if NET_2_0
+ [System.Runtime.InteropServices.ComVisible (true)]
+#endif
+ public struct TimeSpan : IComparable
+#if NET_2_0
+ , IComparable<TimeSpan>, IEquatable <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);
+
public const long TicksPerDay = 864000000000L;
public const long TicksPerHour = 36000000000L;
public const long TicksPerMillisecond = 10000L;
public const long TicksPerMinute = 600000000L;
public const long TicksPerSecond = 10000000L;
- public static readonly TimeSpan Zero = new TimeSpan (0L);
- // Properties
+ private long _ticks;
- public int Days
+ public TimeSpan (long value)
{
- get {
- return (int) TotalDays;
- }
+ _ticks = value;
+ }
+
+ public TimeSpan (int hours, int minutes, int seconds)
+ {
+ _ticks = CalculateTicks (0, hours, minutes, seconds, 0);
+ }
+
+ public TimeSpan (int days, int hours, int minutes, int seconds)
+ {
+ _ticks = CalculateTicks (days, hours, minutes, seconds, 0);
}
- public int Hours
+ public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
{
+ _ticks = CalculateTicks (days, hours, minutes, seconds, milliseconds);
+ }
+
+ internal static long CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds)
+ {
+ // there's no overflow checks for hours, minutes, ...
+ // so big hours/minutes values can overflow at some point and change expected values
+ int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
+ int minsec = (minutes * 60);
+ long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
+ t *= 10000;
+
+ 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
+ // illegal (e.g. sign change).
+ if (days > 0) {
+ long td = TicksPerDay * days;
+ if (t < 0) {
+ long ticks = t;
+ t += td;
+ // positive days -> total ticks should be lower
+ overflow = (ticks > t);
+ }
+ else {
+ t += td;
+ // positive + positive != negative result
+ overflow = (t < 0);
+ }
+ }
+ else if (days < 0) {
+ long td = TicksPerDay * days;
+ if (t <= 0) {
+ t += td;
+ // negative + negative != positive result
+ overflow = (t > 0);
+ }
+ else {
+ long ticks = t;
+ t += td;
+ // negative days -> total ticks should be lower
+ overflow = (t > ticks);
+ }
+ }
+
+ if (overflow)
+ throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
+
+ return t;
+ }
+
+ public int Days {
get {
- return (int) (ticks % TicksPerDay / TicksPerHour);
+ return (int) (_ticks / TicksPerDay);
}
}
- public int Milliseconds
- {
- get
- {
- return (int) (ticks % TicksPerSecond / TicksPerMillisecond);
+ public int Hours {
+ get {
+ return (int) (_ticks % TicksPerDay / TicksPerHour);
}
}
- public int Minutes
- {
- get
- {
- return (int) (ticks % TicksPerHour / TicksPerMinute);
+ public int Milliseconds {
+ get {
+ return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
}
}
- public int Seconds
- {
- get
- {
- return (int) (ticks % TicksPerMinute / TicksPerSecond);
+ public int Minutes {
+ get {
+ return (int) (_ticks % TicksPerHour / TicksPerMinute);
}
}
- public long Ticks
- {
- get
- {
- return ticks;
+ public int Seconds {
+ get {
+ return (int) (_ticks % TicksPerMinute / TicksPerSecond);
}
}
- public double TotalDays
- {
- get
- {
- return (double) ticks / TicksPerDay;
+ public long Ticks {
+ get {
+ return _ticks;
}
}
- public double TotalHours
- {
- get
- {
- return (double) ticks / TicksPerHour;
+ public double TotalDays {
+ get {
+ return (double) _ticks / TicksPerDay;
}
}
- public double TotalMilliseconds
- {
- get
- {
- return (double) ticks / TicksPerMillisecond;
+ public double TotalHours {
+ get {
+ return (double) _ticks / TicksPerHour;
}
}
- public double TotalMinutes
- {
+ public double TotalMilliseconds {
get {
- return (double) ticks / TicksPerMinute;
+ return (double) _ticks / TicksPerMillisecond;
}
}
- public double TotalSeconds
- {
+ public double TotalMinutes {
get {
- return (double) ticks / TicksPerSecond;
+ return (double) _ticks / TicksPerMinute;
}
}
- // Methods
+ public double TotalSeconds {
+ get {
+ return (double) _ticks / TicksPerSecond;
+ }
+ }
public TimeSpan Add (TimeSpan ts)
{
- checked {
- return new TimeSpan (ticks+ts.Ticks);
+ try {
+ checked {
+ return new TimeSpan (_ticks + ts.Ticks);
+ }
+ }
+ catch (OverflowException) {
+ throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
}
}
public static int Compare (TimeSpan t1, TimeSpan t2)
{
- if (t1.ticks < t2.ticks) {
+ if (t1._ticks < t2._ticks)
return -1;
- }
- else if (t1.ticks > t2.ticks) {
+ if (t1._ticks > t2._ticks)
return 1;
- }
- else {
- return 0;
- }
+ return 0;
}
public int CompareTo (object value)
{
-
- if (value == null ) {
+ if (value == null)
return 1;
- }
if (!(value is TimeSpan)) {
- throw new ArgumentException("Argument of System.TimeSpan.CompareTo should be a TimeSpan");
+ throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
}
-
- return Compare(this, (TimeSpan) value);
+
+ return Compare (this, (TimeSpan) value);
+ }
+
+#if NET_2_0
+ public int CompareTo (TimeSpan value)
+ {
+ return Compare (this, value);
+ }
+
+ public bool Equals (TimeSpan value)
+ {
+ return value._ticks == _ticks;
}
+#endif
- // Overflow issue for Duration is like for Negate.
public TimeSpan Duration ()
{
- checked {
- return new TimeSpan (Math.Abs (ticks));
+ try {
+ checked {
+ return new TimeSpan (Math.Abs (_ticks));
+ }
+ }
+ catch (OverflowException) {
+ throw new OverflowException (Locale.GetText (
+ "This TimeSpan value is MinValue so you cannot get the duration."));
}
}
- // TODO: Consider implementing this version
- // in terms of Equals(TimeSPan, TimeSpan)
public override bool Equals (object value)
{
- if (value == null || !(value is TimeSpan)) {
+ if (!(value is TimeSpan))
return false;
- }
- return ticks == ((TimeSpan) (value)).ticks;
+
+ return _ticks == ((TimeSpan) value)._ticks;
}
- public static new bool Equals(TimeSpan t1, TimeSpan t2)
+ public static bool Equals (TimeSpan t1, TimeSpan t2)
{
- return t1.Equals (t2);
+ return t1._ticks == t2._ticks;
}
- // Implementing FromDays -> FromHours -> FromMinutes -> FromSeconds ->
- // FromMilliseconds as done here is probably not the most efficient
- // way.
-
public static TimeSpan FromDays (double value)
{
- if (Double.IsNaN(value) || Double.IsNegativeInfinity(value)) {
- return MinValue;
- }
-
- if (Double.IsPositiveInfinity(value)) {
- return MaxValue;
- }
-
- return new TimeSpan((int) value,0,0,0,0) + FromHours ((value - ((int) value)) * 24);
+ return From (value, TicksPerDay);
}
public static TimeSpan FromHours (double value)
{
- if (Double.IsNaN(value) || Double.IsNegativeInfinity(value)) {
- return MinValue;
- }
-
- if (Double.IsPositiveInfinity(value)) {
- return MaxValue;
- }
-
- return new TimeSpan ((int) value,0,0) + FromMinutes ((value - ((int) value)) * 60);
+ return From (value, TicksPerHour);
}
- public static TimeSpan FromMinutes(double value)
+ public static TimeSpan FromMinutes (double value)
{
- if (Double.IsNaN(value) || Double.IsNegativeInfinity(value)) {
- return MinValue;
- }
-
- if (Double.IsPositiveInfinity(value)) {
- return MaxValue;
- }
+ return From (value, TicksPerMinute);
+ }
- return new TimeSpan (0,(int) value,0) + FromSeconds((value - ((int) value)) * 60);
+ public static TimeSpan FromSeconds (double value)
+ {
+ return From (value, TicksPerSecond);
}
- public static TimeSpan FromSeconds(double value)
+ public static TimeSpan FromMilliseconds (double value)
{
- if (Double.IsNaN(value) || Double.IsNegativeInfinity(value)) {
- return MinValue;
- }
+ return From (value, TicksPerMillisecond);
+ }
- if (Double.IsPositiveInfinity(value)) {
- return MaxValue;
- }
+ private static TimeSpan From (double value, long tickMultiplicator)
+ {
+ if (Double.IsNaN (value))
+ throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
+ if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
+ (value < MinValue.Ticks) || (value > MaxValue.Ticks))
+ throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
- return new TimeSpan (0,0,0,(int) value,((int) ((value - ((int) value)) * 1000)));
+ try {
+ value = (value * (tickMultiplicator / TicksPerMillisecond));
+ checked {
+ long val = (long) Math.Round(value);
+ return new TimeSpan (val * TicksPerMillisecond);
+ }
+ }
+ catch (OverflowException) {
+ throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
+ }
}
public static TimeSpan FromTicks (long value)
return new TimeSpan (value);
}
- public override int GetHashCode()
+ public override int GetHashCode ()
{
- return ticks.GetHashCode();
+ return _ticks.GetHashCode ();
}
- // TODO: It makes sense that Negate can throw an overflow
- // exception (if negating MinValue). Is this specified
- // somewhere?
- public TimeSpan Negate()
+ public TimeSpan Negate ()
{
- checked {
- return new TimeSpan(- ticks);
- }
+ if (_ticks == MinValue._ticks)
+ throw new OverflowException (Locale.GetText (
+ "This TimeSpan value is MinValue and cannot be negated."));
+ return new TimeSpan (-_ticks);
}
- // TODO: implement
- public static TimeSpan Parse(string s)
+ public static TimeSpan Parse (string s)
{
- return Zero;
+ if (s == null) {
+ throw new ArgumentNullException ("s");
+ }
+
+ Parser p = new Parser (s);
+ return p.Execute ();
}
- public TimeSpan Subtract(TimeSpan ts)
+#if NET_2_0
+ public static bool TryParse (string s, out TimeSpan result)
{
- checked {
- return new TimeSpan(ticks - ts.Ticks);
+ try {
+ result = Parse (s);
+ return true;
+ } catch {
+ result = TimeSpan.Zero;
+ return false;
}
}
+#endif
- public override string ToString()
+ public TimeSpan Subtract (TimeSpan ts)
{
- string res = "";
-
- if (ticks < 0) {
- res += "-";
+ try {
+ checked {
+ return new TimeSpan (_ticks - ts.Ticks);
+ }
+ }
+ catch (OverflowException) {
+ throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
}
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder (14);
+
+ if (_ticks < 0)
+ sb.Append ('-');
// We need to take absolute values of all components.
// Can't handle negative timespans by negating the TimeSpan
// as a whole. This would lead to an overflow for the
// degenerate case "TimeSpan.MinValue.ToString()".
-
if (Days != 0) {
- res += Math.Abs(Days) + "." ;
+ sb.Append (Math.Abs (Days));
+ sb.Append ('.');
}
- res += string.Format("{0:00}:{1:00}:{2:00}", Math.Abs(Hours), Math.Abs(Minutes), Math.Abs(Seconds));
+ System.Globalization.NumberFormatInfo nfi = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
+ sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Hours)), 2, nfi));
+ sb.Append (':');
+ sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Minutes)), 2, nfi));
+ sb.Append (':');
+ sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore ((int)Math.Abs (Seconds)), 2, nfi));
- int fractional = (int) Math.Abs(ticks % TicksPerSecond);
+ int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
if (fractional != 0) {
- res += string.Format(".{0:0000000}", fractional);
+ sb.Append ('.');
+ sb.Append (NumberFormatter.FormatDecimal (new NumberFormatter.NumberStore (fractional), 7, nfi));
}
-
- return res;
+
+ return sb.ToString ();
}
- public static TimeSpan operator +(TimeSpan t1, TimeSpan t2)
+ public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
{
- return t1.Add(t2);
+ return t1.Add (t2);
}
- public static bool operator ==(TimeSpan t1, TimeSpan t2)
+ public static bool operator == (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) == 0;
+ return t1._ticks == t2._ticks;
}
- public static bool operator >(TimeSpan t1, TimeSpan t2)
+ public static bool operator > (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) == 1;
+ return t1._ticks > t2._ticks;
}
- public static bool operator >=(TimeSpan t1, TimeSpan t2)
+ public static bool operator >= (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) != -1;
+ return t1._ticks >= t2._ticks;
}
- public static bool operator !=(TimeSpan t1, TimeSpan t2)
+ public static bool operator != (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) != 0;
+ return t1._ticks != t2._ticks;
}
- public static bool operator <(TimeSpan t1, TimeSpan t2)
+ public static bool operator < (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) == -1;
+ return t1._ticks < t2._ticks;
}
- public static bool operator <=(TimeSpan t1, TimeSpan t2)
+ public static bool operator <= (TimeSpan t1, TimeSpan t2)
{
- return Compare(t1, t2) != 1;
+ return t1._ticks <= t2._ticks;
}
- public static TimeSpan operator -(TimeSpan t1, TimeSpan t2)
+ public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
{
- return t1.Subtract(t2);
+ return t1.Subtract (t2);
}
- public static TimeSpan operator -(TimeSpan t)
+ public static TimeSpan operator - (TimeSpan t)
{
- return t.Negate();
+ return t.Negate ();
}
- public static TimeSpan operator +(TimeSpan t)
+ public static TimeSpan operator + (TimeSpan t)
{
return t;
}
+
+ // Class Parser implements parser for TimeSpan.Parse
+ private class Parser
+ {
+ private string _src;
+ private int _cur = 0;
+ private int _length;
+ private bool formatError;
+
+ public Parser (string src)
+ {
+ _src = src;
+ _length = _src.Length;
+ }
+
+ public bool AtEnd {
+ get {
+ return _cur >= _length;
+ }
+ }
+
+ // All "Parse" functions throw a FormatException on syntax error.
+ // Their return value is semantic value of the item parsed.
+
+ // Range checking is spread over three different places:
+ // 1) When parsing "int" values, an exception is thrown immediately
+ // when the value parsed exceeds the maximum value for an int.
+ // 2) An explicit check is built in that checks for hours > 23 and
+ // for minutes and seconds > 59.
+ // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
+ // or < MinValue is left to the TimeSpan constructor called.
+
+ // Parse zero or more whitespace chars.
+ private void ParseWhiteSpace ()
+ {
+ while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
+ _cur++;
+ }
+ }
+
+ // Parse optional sign character.
+ private bool ParseSign ()
+ {
+ bool res = false;
+
+ if (!AtEnd && _src[_cur] == '-') {
+ res = true;
+ _cur++;
+ }
+
+ return res;
+ }
+
+ // Parse simple int value
+ private int ParseInt (bool optional)
+ {
+ if (optional && AtEnd)
+ return 0;
+
+ int res = 0;
+ int count = 0;
+
+ while (!AtEnd && Char.IsDigit (_src, _cur)) {
+ checked {
+ res = res * 10 + _src[_cur] - '0';
+ }
+ _cur++;
+ count++;
+ }
+
+ if (count == 0)
+ formatError = true;
+
+ return res;
+ }
+
+ // Parse optional dot
+ private bool ParseOptDot ()
+ {
+ if (AtEnd)
+ return false;
+
+ if (_src[_cur] == '.') {
+ _cur++;
+ return true;
+ }
+ return false;
+ }
+
+ // Parse optional (LAMESPEC) colon
+ private void ParseOptColon ()
+ {
+ if (!AtEnd) {
+ if (_src[_cur] == ':')
+ _cur++;
+ else
+ formatError = true;
+ }
+ }
+
+ // Parse [1..7] digits, representing fractional seconds (ticks)
+ private long ParseTicks ()
+ {
+ long mag = 1000000;
+ long res = 0;
+ bool digitseen = false;
+
+ while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
+ res = res + (_src[_cur] - '0') * mag;
+ _cur++;
+ mag = mag / 10;
+ digitseen = true;
+ }
+
+ if (!digitseen)
+ formatError = true;
+
+ return res;
+ }
+
+ public TimeSpan Execute ()
+ {
+ bool sign;
+ int days;
+ int hours = 0;
+ int minutes;
+ int seconds;
+ long ticks;
+
+ // documented as...
+ // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
+ // ... but not entirely true as an lonely
+ // integer will be parsed as a number of days
+ ParseWhiteSpace ();
+ sign = ParseSign ();
+ days = ParseInt (false);
+ if (ParseOptDot ()) {
+ hours = ParseInt (true);
+ }
+ else if (!AtEnd) {
+ hours = days;
+ days = 0;
+ }
+ ParseOptColon();
+ minutes = ParseInt (true);
+ ParseOptColon ();
+ seconds = ParseInt (true);
+ if ( ParseOptDot () ) {
+ ticks = ParseTicks ();
+ }
+ else {
+ ticks = 0;
+ }
+ ParseWhiteSpace ();
+
+ if (!AtEnd)
+ formatError = true;
+
+ // 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."));
+ }
+
+ long t = TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0);
+ t = checked ((sign) ? (-t - ticks) : (t + ticks));
+ return new TimeSpan (t);
+ }
+ }
}
}
-