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