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