2004-06-06 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                         private bool formatError;
400
401                         public Parser (string src)
402                         {
403                                 _src = src;
404                                 _length = _src.Length;
405                         }
406         
407                         public bool AtEnd {
408                                 get {
409                                         return _cur >= _length;
410                                 }
411                         }
412
413                         // All "Parse" functions throw a FormatException on syntax error.
414                         // Their return value is semantic value of the item parsed.
415
416                         // Range checking is spread over three different places:
417                         // 1) When parsing "int" values, an exception is thrown immediately
418                         //    when the value parsed exceeds the maximum value for an int.
419                         // 2) An explicit check is built in that checks for hours > 23 and
420                         //    for minutes and seconds > 59.
421                         // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
422                         //    or < MinValue is left to the TimeSpan constructor called.
423
424                         // Parse zero or more whitespace chars.
425                         private void ParseWhiteSpace ()
426                         {
427                                 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
428                                         _cur++;
429                                 }
430                         }
431
432                         // Parse optional sign character.
433                         private bool ParseSign ()
434                         {
435                                 bool res = false;
436
437                                 if (!AtEnd && _src[_cur] == '-') {
438                                         res = true;
439                                         _cur++;
440                                 }
441
442                                 return res;
443                         }
444
445                         // Parse simple int value
446                         private int ParseInt (bool optional)
447                         {
448                                 if (optional && AtEnd)
449                                         return 0;
450
451                                 int res = 0;
452                                 int count = 0;
453
454                                 while (!AtEnd && Char.IsDigit (_src, _cur)) {
455                                         checked {
456                                                 res = res * 10 + _src[_cur] - '0';
457                                         }
458                                         _cur++;
459                                         count++;
460                                 }
461
462                                 if (count == 0)
463                                         formatError = true;
464
465                                 return res;
466                         }
467
468                         // Parse optional dot
469                         private bool ParseOptDot ()
470                         {
471                                 if (AtEnd)
472                                         return false;
473
474                                 if (_src[_cur] == '.') {
475                                         _cur++;
476                                         return true;
477                                 }
478                                 return false;
479                         }       
480
481                         // Parse optional (LAMESPEC) colon
482                         private void ParseOptColon ()
483                         {
484                                 if (!AtEnd) {
485                                         if (_src[_cur] == ':')
486                                                 _cur++;
487                                         else 
488                                                 formatError = true;
489                                 }
490                         }
491
492                         // Parse [1..7] digits, representing fractional seconds (ticks)
493                         private long ParseTicks ()
494                         {
495                                 long mag = 1000000;
496                                 long res = 0;
497                                 bool digitseen = false;
498                                 
499                                 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
500                                         res = res + (_src[_cur] - '0') * mag;
501                                         _cur++;
502                                         mag = mag / 10;
503                                         digitseen = true;
504                                 }
505
506                                 if (!digitseen)
507                                         formatError = true;
508
509                                 return res;
510                         }
511
512                         public TimeSpan Execute ()
513                         {
514                                 bool sign;
515                                 int days;
516                                 int hours = 0;
517                                 int minutes;
518                                 int seconds;
519                                 long ticks;
520
521                                 // documented as...
522                                 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
523                                 // ... but not entirely true as an lonely 
524                                 // integer will be parsed as a number of days
525                                 ParseWhiteSpace ();
526                                 sign = ParseSign ();
527                                 days = ParseInt (false);
528                                 if (ParseOptDot ()) {
529                                         hours = ParseInt (true);
530                                 }
531                                 else if (!AtEnd) {
532                                         hours = days;
533                                         days = 0;
534                                 }
535                                 ParseOptColon();
536                                 minutes = ParseInt (true);
537                                 ParseOptColon ();
538                                 seconds = ParseInt (true);
539                                 if ( ParseOptDot () ) {
540                                         ticks = ParseTicks ();
541                                 }
542                                 else {
543                                         ticks = 0;
544                                 }
545                                 ParseWhiteSpace ();
546         
547                                 if (!AtEnd)
548                                         formatError = true;
549
550                                 // Overflow has presceance over FormatException
551                                 if (hours > 23 || minutes > 59 || seconds > 59) {
552                                         throw new OverflowException (
553                                                 Locale.GetText ("Invalid time data."));
554                                 }
555                                 else if (formatError) {
556                                         throw new FormatException (
557                                                 Locale.GetText ("Invalid format for TimeSpan.Parse."));
558                                 }
559
560                                 long t = TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0);
561                                 t += ticks;
562                                 if (sign)
563                                         t = -t;
564                                 return new TimeSpan (t);
565                         }
566                 }
567         }
568 }