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