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