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