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