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