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