2004-06-02 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / corlib / System / TimeSpan.cs
1 //
2 // System.TimeSpan.cs
3 //
4 // Authors:
5 //   Duco Fijma (duco@lorentz.xs4all.nl)
6 //   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
7 //   Sebastien Pouliot  <sebastien@ximian.com>
8 //
9 // (C) 2001 Duco Fijma
10 // (C) 2004 Andreas Nahr
11 // Copyright (C) 2004 Novell (http://www.novell.com)
12 //
13
14 using System.Text;
15
16 namespace System
17 {
18         [Serializable]
19         public struct TimeSpan : IComparable
20         {
21                 public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
22                 public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
23                 public static readonly TimeSpan Zero = new TimeSpan (0L);
24
25                 public const long TicksPerDay = 864000000000L;
26                 public const long TicksPerHour = 36000000000L;
27                 public const long TicksPerMillisecond = 10000L;
28                 public const long TicksPerMinute = 600000000L;
29                 public const long TicksPerSecond = 10000000L;
30
31                 private long _ticks;
32
33                 public TimeSpan (long value)
34                 {
35                         _ticks = value;
36                 }
37
38                 public TimeSpan (int hours, int minutes, int seconds)
39                 {
40                         _ticks = CalculateTicks (0, hours, minutes, seconds, 0);
41                 }
42
43                 public TimeSpan (int days, int hours, int minutes, int seconds)
44                 {
45                         _ticks = CalculateTicks (days, hours, minutes, seconds, 0);
46                 }
47
48                 public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
49                 {
50                         _ticks = CalculateTicks (days, hours, minutes, seconds, milliseconds);
51                 }
52
53                 internal static long CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds)
54                 {
55                         bool overflow = false;
56
57                         // this part cannot overflow Int64
58                         long t = checked (TimeSpan.TicksPerHour * hours +
59                                 TimeSpan.TicksPerMinute * minutes +
60                                 TimeSpan.TicksPerSecond * seconds + 
61                                 TimeSpan.TicksPerMillisecond * milliseconds);
62
63                         // days is problematic because it can overflow but that overflow can be 
64                         // "legal" (i.e. temporary) (e.g. if other parameters are negative) or 
65                         // illegal (e.g. sign change).
66                         if (days > 0) {
67                                 long td = TicksPerDay * days;
68                                 if (t < 0) {
69                                         long ticks = t;
70                                         t += td;
71                                         // positive days -> total ticks should be lower
72                                         overflow = (ticks > t);
73                                 }
74                                 else {
75                                         t += td;
76                                         // positive + positive != negative result
77                                         overflow = (t < 0);
78                                 }
79                         }
80                         else if (days < 0) {
81                                 long td = TicksPerDay * days;
82                                 if (t <= 0) {
83                                         t += td;
84                                         // negative + negative != positive result
85                                         overflow = (t > 0);
86                                 }
87                                 else {
88                                         long ticks = t;
89                                         t += td;
90                                         // negative days -> total ticks should be lower
91                                         overflow = (t > ticks);
92                                 }
93                         }
94
95                         if (overflow)
96                                 throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
97
98                         return t;
99                 }
100
101                 public int Days {
102                         get {
103                                 return (int) (_ticks / TicksPerDay);
104                         }
105                 }
106
107                 public int Hours {
108                         get {
109                                 return (int) (_ticks % TicksPerDay / TicksPerHour);
110                         }
111                 }
112
113                 public int Milliseconds {
114                         get {
115                                 return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
116                         }
117                 }
118
119                 public int Minutes {
120                         get {
121                                 return (int) (_ticks % TicksPerHour / TicksPerMinute);
122                         }
123                 }
124
125                 public int Seconds {
126                         get {
127                                 return (int) (_ticks % TicksPerMinute / TicksPerSecond);
128                         }
129                 }
130
131                 public long Ticks {
132                         get {
133                                 return _ticks;
134                         }
135                 }
136
137                 public double TotalDays {
138                         get {
139                                 return (double) _ticks / TicksPerDay;
140                         }
141                 }
142
143                 public double TotalHours {
144                         get {
145                                 return (double) _ticks / TicksPerHour;
146                         }
147                 }
148
149                 public double TotalMilliseconds {
150                         get {
151                                 return (double) _ticks  / TicksPerMillisecond;
152                         }
153                 }
154
155                 public double TotalMinutes {
156                         get {
157                                 return (double) _ticks / TicksPerMinute;
158                         }
159                 }
160
161                 public double TotalSeconds {
162                         get {
163                                 return (double) _ticks / TicksPerSecond;
164                         }
165                 }
166
167                 public TimeSpan Add (TimeSpan ts)
168                 {
169                         try {
170                                 checked {
171                                         return new TimeSpan (_ticks + ts.Ticks);
172                                 }
173                         }
174                         catch (OverflowException) {
175                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
176                         }
177                 }
178
179                 public static int Compare (TimeSpan t1, TimeSpan t2)
180                 {
181                         if (t1._ticks < t2._ticks)
182                                 return -1;
183                         if (t1._ticks > t2._ticks)
184                                 return 1;
185                         return 0;
186                 }
187
188                 public int CompareTo (object value)
189                 {
190                         if (value == null)
191                                 return 1;
192
193                         if (!(value is TimeSpan)) {
194                                 throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
195                         }
196
197                         return Compare (this, (TimeSpan) value);
198                 }
199
200                 public TimeSpan Duration ()
201                 {
202                         try {
203                                 checked {
204                                         return new TimeSpan (Math.Abs (_ticks));
205                                 }
206                         }
207                         catch (OverflowException) {
208                                 throw new OverflowException (Locale.GetText (
209                                         "This TimeSpan value is MinValue so you cannot get the duration."));
210                         }
211                 }
212
213                 public override bool Equals (object value)
214                 {
215                         if (!(value is TimeSpan))
216                                 return false;
217
218                         return _ticks == ((TimeSpan) value)._ticks;
219                 }
220
221                 public static bool Equals (TimeSpan t1, TimeSpan t2)
222                 {
223                         return t1._ticks == t2._ticks;
224                 }
225
226                 public static TimeSpan FromDays (double value)
227                 {
228                         return From (value, TicksPerDay);
229                 }
230
231                 public static TimeSpan FromHours (double value)
232                 {
233                         return From (value, TicksPerHour);
234                 }
235
236                 public static TimeSpan FromMinutes (double value)
237                 {
238                         return From (value, TicksPerMinute);
239                 }
240
241                 public static TimeSpan FromSeconds (double value)
242                 {
243                         return From (value, TicksPerSecond);
244                 }
245
246                 public static TimeSpan FromMilliseconds (double value)
247                 {
248                         return From (value, TicksPerMillisecond);
249                 }
250
251                 private static TimeSpan From (double value, long tickMultiplicator) 
252                 {
253                         if (Double.IsNaN (value))
254                                 throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
255                         if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
256                                 (value < MinValue.Ticks) || (value > MaxValue.Ticks))
257                                 throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
258
259                         try {
260                                 value = (value * (tickMultiplicator / TicksPerMillisecond));
261
262                                 checked {
263                                         long val = (long) Math.Round(value);
264                                         return new TimeSpan (val * TicksPerMillisecond);
265                                 }
266                         }
267                         catch (OverflowException) {
268                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
269                         }
270                 }
271
272                 public static TimeSpan FromTicks (long value)
273                 {
274                         return new TimeSpan (value);
275                 }
276
277                 public override int GetHashCode ()
278                 {
279                         return _ticks.GetHashCode ();
280                 }
281
282                 public TimeSpan Negate ()
283                 {
284                         if (_ticks == MinValue._ticks)
285                                 throw new OverflowException (Locale.GetText (
286                                         "This TimeSpan value is MinValue and cannot be negated."));
287                         return new TimeSpan (-_ticks);
288                 }
289
290                 public static TimeSpan Parse (string s)
291                 {
292                         if (s == null) {
293                                 throw new ArgumentNullException ("s");
294                         }
295
296                         Parser p = new Parser (s);
297                         return p.Execute ();
298                 }
299
300                 public TimeSpan Subtract (TimeSpan ts)
301                 {
302                         try {
303                                 checked {
304                                         return new TimeSpan (_ticks - ts.Ticks);
305                                 }
306                         }
307                         catch (OverflowException) {
308                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
309                         }
310                 }
311
312                 public override string ToString ()
313                 {
314                         StringBuilder sb = new StringBuilder (14);
315                         
316                         if (_ticks < 0)
317                                 sb.Append ('-');
318
319                         // We need to take absolute values of all components.
320                         // Can't handle negative timespans by negating the TimeSpan
321                         // as a whole. This would lead to an overflow for the 
322                         // degenerate case "TimeSpan.MinValue.ToString()".
323                         if (Days != 0) {
324                                 sb.Append (Math.Abs (Days));
325                                 sb.Append ('.');
326                         }
327
328                         sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Hours), 2, 4));
329                         sb.Append (':');
330                         sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Minutes), 2, 4));
331                         sb.Append (':');
332                         sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (Seconds), 2, 4));
333
334                         int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
335                         if (fractional != 0) {
336                                 sb.Append ('.');
337                                 sb.Append (IntegerFormatter.FormatDecimal (Math.Abs (fractional), 7, 4));
338                         }
339
340                         return sb.ToString ();
341                 }
342
343                 public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
344                 {
345                         return t1.Add (t2);
346                 }
347
348                 public static bool operator == (TimeSpan t1, TimeSpan t2)
349                 {
350                         return t1._ticks == t2._ticks;
351                 }
352
353                 public static bool operator > (TimeSpan t1, TimeSpan t2)
354                 {
355                         return t1._ticks > t2._ticks;
356                 }
357
358                 public static bool operator >= (TimeSpan t1, TimeSpan t2)
359                 {
360                         return t1._ticks >= t2._ticks;
361                 }
362
363                 public static bool operator != (TimeSpan t1, TimeSpan t2)
364                 {
365                         return t1._ticks != t2._ticks;
366                 }
367
368                 public static bool operator < (TimeSpan t1, TimeSpan t2)
369                 {
370                         return t1._ticks < t2._ticks;
371                 }
372
373                 public static bool operator <= (TimeSpan t1, TimeSpan t2)
374                 {
375                         return t1._ticks <= t2._ticks;
376                 }
377
378                 public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
379                 {
380                         return t1.Subtract (t2);
381                 }
382
383                 public static TimeSpan operator - (TimeSpan t)
384                 {
385                         return t.Negate ();
386                 }
387
388                 public static TimeSpan operator + (TimeSpan t)
389                 {
390                         return t;
391                 }
392
393                 // Class Parser implements parser for TimeSpan.Parse
394                 private class Parser
395                 {
396                         private string _src;
397                         private int _cur = 0;
398                         private int _length;
399
400                         public Parser (string src)
401                         {
402                                 _src = src;
403                                 _length = _src.Length;
404                         }
405         
406                         public bool AtEnd {
407                                 get {
408                                         return _cur >= _length;
409                                 }
410                         }
411
412                         private void ThrowFormatException ()
413                         {
414                                 throw new FormatException (Locale.GetText ("Invalid format for TimeSpan.Parse."));
415                         }
416
417                         // All "Parse" functions throw a FormatException on syntax error.
418                         // Their return value is semantic value of the item parsed.
419
420                         // Range checking is spread over three different places:
421                         // 1) When parsing "int" values, an exception is thrown immediately
422                         //    when the value parsed exceeds the maximum value for an int.
423                         // 2) An explicit check is built in that checks for hours > 23 and
424                         //    for minutes and seconds > 59.
425                         // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
426                         //    or < MinValue is left to the TimeSpan constructor called.
427
428                         // Parse zero or more whitespace chars.
429                         private void ParseWhiteSpace ()
430                         {
431                                 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
432                                         _cur++;
433                                 }
434                         }
435
436                         // Parse optional sign character.
437                         private bool ParseSign ()
438                         {
439                                 bool res = false;
440
441                                 if (!AtEnd && _src[_cur] == '-') {
442                                         res = true;
443                                         _cur++;
444                                 }
445
446                                 return res;
447                         }
448
449                         // Parse simple int value
450                         private int ParseInt (bool optional)
451                         {
452                                 if (optional && AtEnd)
453                                         return 0;
454
455                                 int res = 0;
456                                 int count = 0;
457
458                                 while (!AtEnd && Char.IsDigit (_src, _cur)) {
459                                         checked {
460                                                 res = res * 10 + _src[_cur] - '0';
461                                         }
462                                         _cur++;
463                                         count++;
464                                 }
465
466                                 if (count == 0)
467                                         ThrowFormatException ();
468
469                                 return res;
470                         }
471
472                         // Parse optional dot
473                         private bool ParseOptDot ()
474                         {
475                                 if (AtEnd)
476                                         return false;
477
478                                 if (_src[_cur] == '.') {
479                                         _cur++;
480                                         return true;
481                                 }
482                                 return false;
483                         }       
484
485                         // Parse optional (LAMESPEC) colon
486                         private void ParseOptColon ()
487                         {
488                                 if (!AtEnd) {
489                                         if (_src[_cur] == ':')
490                                                 _cur++;
491                                         else 
492                                                 ThrowFormatException ();
493                                 }
494                         }
495
496                         // Parse [1..7] digits, representing fractional seconds (ticks)
497                         private long ParseTicks ()
498                         {
499                                 long mag = 1000000;
500                                 long res = 0;
501                                 bool digitseen = false;
502                                 
503                                 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
504                                         res = res + (_src[_cur] - '0') * mag;
505                                         _cur++;
506                                         mag = mag / 10;
507                                         digitseen = true;
508                                 }
509
510                                 if (!digitseen)
511                                         ThrowFormatException ();
512
513                                 return res;
514                         }
515
516                         public TimeSpan Execute ()
517                         {
518                                 bool sign;
519                                 int days;
520                                 int hours = 0;
521                                 int minutes;
522                                 int seconds;
523                                 long ticks;
524
525                                 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
526                                 ParseWhiteSpace ();
527                                 sign = ParseSign ();
528                                 days = ParseInt (false);
529                                 if (ParseOptDot ()) {
530                                         hours = ParseInt (true);
531                                 }
532                                 else if (!AtEnd) {
533                                         hours = days;
534                                         days = 0;
535                                 }
536                                 ParseOptColon();
537                                 minutes = ParseInt (true);
538                                 ParseOptColon ();
539                                 seconds = ParseInt (true);
540                                 if ( ParseOptDot () ) {
541                                         ticks = ParseTicks ();
542                                 }
543                                 else {
544                                         ticks = 0;
545                                 }
546                                 ParseWhiteSpace ();
547         
548                                 if (!AtEnd)
549                                         ThrowFormatException ();
550
551                                 if (hours > 23 || minutes > 59 || seconds > 59) {
552                                         throw new OverflowException (Locale.GetText (
553                                                 "Invalid time data."));
554                                 }
555
556                                 long t = TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0);
557                                 t += ticks;
558                                 if (sign)
559                                         t = -t;
560                                 return new TimeSpan (t);
561                         }
562                 }
563         }
564 }