Merge pull request #1321 from esdrubal/currentsystemtimezone
[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 //   Marek Safar (marek.safar@gmail.com)
9 //
10 // (C) 2001 Duco Fijma
11 // (C) 2004 Andreas Nahr
12 // Copyright (C) 2004 Novell (http://www.novell.com)
13 // Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System.Text;
36 using System.Threading;
37 using System.Globalization;
38
39 namespace System
40 {
41         [Serializable]
42         [System.Runtime.InteropServices.ComVisible (true)]
43         public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
44 #if NET_4_0
45                                  , IFormattable
46 #endif
47         {
48 #if MONOTOUCH
49                 static TimeSpan () {
50                         if (MonoTouchAOTHelper.FalseFlag) {
51                                 var comparer = new System.Collections.Generic.GenericComparer <TimeSpan> ();
52                                 var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <TimeSpan> ();
53                         }
54                 }
55 #endif
56                 public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
57                 public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
58                 public static readonly TimeSpan Zero = new TimeSpan (0L);
59
60                 public const long TicksPerDay = 864000000000L;
61                 public const long TicksPerHour = 36000000000L;
62                 public const long TicksPerMillisecond = 10000L;
63                 public const long TicksPerMinute = 600000000L;
64                 public const long TicksPerSecond = 10000000L;
65
66                 private long _ticks;
67
68                 public TimeSpan (long ticks)
69                 {
70                         _ticks = ticks;
71                 }
72
73                 public TimeSpan (int hours, int minutes, int seconds)
74                 {
75                         CalculateTicks (0, hours, minutes, seconds, 0, true, out _ticks);
76                 }
77
78                 public TimeSpan (int days, int hours, int minutes, int seconds)
79                 {
80                         CalculateTicks (days, hours, minutes, seconds, 0, true, out _ticks);
81                 }
82
83                 public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
84                 {
85                         CalculateTicks (days, hours, minutes, seconds, milliseconds, true, out _ticks);
86                 }
87
88                 internal static bool CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds, bool throwExc, out long result)
89                 {
90                         // there's no overflow checks for hours, minutes, ...
91                         // so big hours/minutes values can overflow at some point and change expected values
92                         int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
93                         int minsec = (minutes * 60);
94                         long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
95                         t *= 10000;
96
97                         result = 0;
98
99                         bool overflow = false;
100                         // days is problematic because it can overflow but that overflow can be 
101                         // "legal" (i.e. temporary) (e.g. if other parameters are negative) or 
102                         // illegal (e.g. sign change).
103                         if (days > 0) {
104                                 long td = TicksPerDay * days;
105                                 if (t < 0) {
106                                         long ticks = t;
107                                         t += td;
108                                         // positive days -> total ticks should be lower
109                                         overflow = (ticks > t);
110                                 }
111                                 else {
112                                         t += td;
113                                         // positive + positive != negative result
114                                         overflow = (t < 0);
115                                 }
116                         }
117                         else if (days < 0) {
118                                 long td = TicksPerDay * days;
119                                 if (t <= 0) {
120                                         t += td;
121                                         // negative + negative != positive result
122                                         overflow = (t > 0);
123                                 }
124                                 else {
125                                         long ticks = t;
126                                         t += td;
127                                         // negative days -> total ticks should be lower
128                                         overflow = (t > ticks);
129                                 }
130                         }
131
132                         if (overflow) {
133                                 if (throwExc)
134                                         throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
135                                 return false;
136                         }
137
138                         result = t;
139                         return true;
140                 }
141
142                 public int Days {
143                         get {
144                                 return (int) (_ticks / TicksPerDay);
145                         }
146                 }
147
148                 public int Hours {
149                         get {
150                                 return (int) (_ticks % TicksPerDay / TicksPerHour);
151                         }
152                 }
153
154                 public int Milliseconds {
155                         get {
156                                 return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
157                         }
158                 }
159
160                 public int Minutes {
161                         get {
162                                 return (int) (_ticks % TicksPerHour / TicksPerMinute);
163                         }
164                 }
165
166                 public int Seconds {
167                         get {
168                                 return (int) (_ticks % TicksPerMinute / TicksPerSecond);
169                         }
170                 }
171
172                 public long Ticks {
173                         get {
174                                 return _ticks;
175                         }
176                 }
177
178                 public double TotalDays {
179                         get {
180                                 return (double) _ticks / TicksPerDay;
181                         }
182                 }
183
184                 public double TotalHours {
185                         get {
186                                 return (double) _ticks / TicksPerHour;
187                         }
188                 }
189
190                 public double TotalMilliseconds {
191                         get {
192                                 return (double) _ticks  / TicksPerMillisecond;
193                         }
194                 }
195
196                 public double TotalMinutes {
197                         get {
198                                 return (double) _ticks / TicksPerMinute;
199                         }
200                 }
201
202                 public double TotalSeconds {
203                         get {
204                                 return (double) _ticks / TicksPerSecond;
205                         }
206                 }
207
208                 public TimeSpan Add (TimeSpan ts)
209                 {
210                         try {
211                                 checked {
212                                         return new TimeSpan (_ticks + ts.Ticks);
213                                 }
214                         }
215                         catch (OverflowException) {
216                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
217                         }
218                 }
219
220                 public static int Compare (TimeSpan t1, TimeSpan t2)
221                 {
222                         if (t1._ticks < t2._ticks)
223                                 return -1;
224                         if (t1._ticks > t2._ticks)
225                                 return 1;
226                         return 0;
227                 }
228
229                 public int CompareTo (object value)
230                 {
231                         if (value == null)
232                                 return 1;
233
234                         if (!(value is TimeSpan)) {
235                                 throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
236                         }
237
238                         return Compare (this, (TimeSpan) value);
239                 }
240
241                 public int CompareTo (TimeSpan value)
242                 {
243                         return Compare (this, value);
244                 }
245
246                 public bool Equals (TimeSpan obj)
247                 {
248                         return obj._ticks == _ticks;
249                 }
250
251                 public TimeSpan Duration ()
252                 {
253                         try {
254                                 checked {
255                                         return new TimeSpan (Math.Abs (_ticks));
256                                 }
257                         }
258                         catch (OverflowException) {
259                                 throw new OverflowException (Locale.GetText (
260                                         "This TimeSpan value is MinValue so you cannot get the duration."));
261                         }
262                 }
263
264                 public override bool Equals (object value)
265                 {
266                         if (!(value is TimeSpan))
267                                 return false;
268
269                         return _ticks == ((TimeSpan) value)._ticks;
270                 }
271
272                 public static bool Equals (TimeSpan t1, TimeSpan t2)
273                 {
274                         return t1._ticks == t2._ticks;
275                 }
276
277                 public static TimeSpan FromDays (double value)
278                 {
279                         return From (value, TicksPerDay);
280                 }
281
282                 public static TimeSpan FromHours (double value)
283                 {
284                         return From (value, TicksPerHour);
285                 }
286
287                 public static TimeSpan FromMinutes (double value)
288                 {
289                         return From (value, TicksPerMinute);
290                 }
291
292                 public static TimeSpan FromSeconds (double value)
293                 {
294                         return From (value, TicksPerSecond);
295                 }
296
297                 public static TimeSpan FromMilliseconds (double value)
298                 {
299                         return From (value, TicksPerMillisecond);
300                 }
301
302                 private static TimeSpan From (double value, long tickMultiplicator) 
303                 {
304                         if (Double.IsNaN (value))
305                                 throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
306                         if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
307                                 (value < MinValue.Ticks) || (value > MaxValue.Ticks))
308                                 throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
309
310                         try {
311                                 value = (value * (tickMultiplicator / TicksPerMillisecond));
312
313                                 checked {
314                                         long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
315                                         return new TimeSpan (val * TicksPerMillisecond);
316                                 }
317                         }
318                         catch (OverflowException) {
319                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
320                         }
321                 }
322
323                 public static TimeSpan FromTicks (long value)
324                 {
325                         return new TimeSpan (value);
326                 }
327
328                 public override int GetHashCode ()
329                 {
330                         return _ticks.GetHashCode ();
331                 }
332
333                 public TimeSpan Negate ()
334                 {
335                         if (_ticks == MinValue._ticks)
336                                 throw new OverflowException (Locale.GetText (
337                                         "This TimeSpan value is MinValue and cannot be negated."));
338                         return new TimeSpan (-_ticks);
339                 }
340
341                 public static TimeSpan Parse (string s)
342                 {
343                         if (s == null) {
344                                 throw new ArgumentNullException ("s");
345                         }
346
347                         TimeSpan result;
348                         Parser p = new Parser (s);
349                         p.Execute (false, out result);
350                         return result;
351                 }
352
353                 public static bool TryParse (string s, out TimeSpan result)
354                 {
355                         if (s == null) {
356                                 result = TimeSpan.Zero;
357                                 return false;
358                         }
359
360                         Parser p = new Parser (s);
361                         return p.Execute (true, out result);
362                 }
363
364 #if NET_4_0
365                 public static TimeSpan Parse (string input, IFormatProvider formatProvider)
366                 {
367                         if (input == null)
368                                 throw new ArgumentNullException ("input");
369
370                         TimeSpan result;
371                         Parser p = new Parser (input, formatProvider);
372                         p.Execute (false, out result);
373                         return result;
374                 }
375
376                 public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result)
377                 {
378                         if (string.IsNullOrEmpty (input)) {
379                                 result = TimeSpan.Zero;
380                                 return false;
381                         }
382
383                         Parser p = new Parser (input, formatProvider);
384                         return p.Execute (true, out result);
385                 }
386
387                 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider)
388                 {
389                         if (format == null)
390                                 throw new ArgumentNullException ("format");
391
392                         return ParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None);
393                 }
394
395                 public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles)
396                 {
397                         if (format == null)
398                                 throw new ArgumentNullException ("format");
399
400                         return ParseExact (input, new string [] { format }, formatProvider, styles);
401                 }
402
403                 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider)
404                 {
405                         return ParseExact (input, formats, formatProvider, TimeSpanStyles.None);
406                 }
407
408                 public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
409                 {
410                         if (input == null)
411                                 throw new ArgumentNullException ("input");
412                         if (formats == null)
413                                 throw new ArgumentNullException ("formats");
414
415                         // All the errors found during the parsing process are reported as FormatException.
416                         TimeSpan result;
417                         if (!TryParseExact (input, formats, formatProvider, styles, out result))
418                                 throw new FormatException ("Invalid format.");
419
420                         return result;
421                 }
422
423                 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, out TimeSpan result)
424                 {
425                         return TryParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None, out result);
426                 }
427
428                 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles,
429                                 out TimeSpan result)
430                 {
431                         return TryParseExact (input, new string [] { format }, formatProvider, styles, out result);
432                 }
433
434                 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, out TimeSpan result)
435                 {
436                         return TryParseExact (input, formats, formatProvider, TimeSpanStyles.None, out result);
437                 }
438
439                 public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles,
440                         out TimeSpan result)
441                 {
442                         result = TimeSpan.Zero;
443
444                         if (input == null || formats == null || formats.Length == 0)
445                                 return false;
446
447                         Parser p = new Parser (input, formatProvider);
448                         p.Exact = true;
449
450                         foreach (string format in formats) {
451                                 if (format == null || format.Length == 0)
452                                         return false; // wrong format, return immediately.
453
454                                 switch (format) {
455                                         case "g":
456                                                 p.AllMembersRequired = false;
457                                                 p.CultureSensitive = true;
458                                                 p.UseColonAsDaySeparator = true;
459                                                 break;
460                                         case "G":
461                                                 p.AllMembersRequired = true;
462                                                 p.CultureSensitive = true;
463                                                 p.UseColonAsDaySeparator = true;
464                                                 break;
465                                         case "c":
466                                                 p.AllMembersRequired = false;
467                                                 p.CultureSensitive = false;
468                                                 p.UseColonAsDaySeparator = false;
469                                                 break;
470                                         default:
471                                                 // Single letter formats other than the defined ones are not accepted.
472                                                 if (format.Length == 1)
473                                                         return false;
474                                                 // custom format
475                                                 if (p.ExecuteWithFormat (format, styles, true, out result))
476                                                         return true;
477                                                 continue;
478                                 }
479
480                                 if (p.Execute (true, out result))
481                                         return true;
482                         }
483
484                         return false;
485                 }
486 #endif
487
488                 public TimeSpan Subtract (TimeSpan ts)
489                 {
490                         try {
491                                 checked {
492                                         return new TimeSpan (_ticks - ts.Ticks);
493                                 }
494                         }
495                         catch (OverflowException) {
496                                 throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
497                         }
498                 }
499
500                 public override string ToString ()
501                 {
502                         StringBuilder sb = new StringBuilder (14);
503                         
504                         if (_ticks < 0)
505                                 sb.Append ('-');
506
507                         // We need to take absolute values of all components.
508                         // Can't handle negative timespans by negating the TimeSpan
509                         // as a whole. This would lead to an overflow for the 
510                         // degenerate case "TimeSpan.MinValue.ToString()".
511                         if (Days != 0) {
512                                 sb.Append (Math.Abs (Days));
513                                 sb.Append ('.');
514                         }
515
516                         sb.Append (Math.Abs (Hours).ToString ("D2"));
517                         sb.Append (':');
518                         sb.Append (Math.Abs (Minutes).ToString ("D2"));
519                         sb.Append (':');
520                         sb.Append (Math.Abs (Seconds).ToString ("D2"));
521
522                         int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
523                         if (fractional != 0) {
524                                 sb.Append ('.');
525                                 sb.Append (fractional.ToString ("D7"));
526                         }
527
528                         return sb.ToString ();
529                 }
530
531 #if NET_4_0
532                 public string ToString (string format)
533                 {
534                         return ToString (format, null);
535                 }
536
537                 public string ToString (string format, IFormatProvider formatProvider)
538                 {
539                         if (format == null || format.Length == 0 || format == "c" ||
540                                         format == "t" || format == "T") // Default version
541                                 return ToString ();
542
543                         if (format != "g" && format != "G")
544                                 return ToStringCustom (format); // custom formats ignore culture/formatProvider
545
546                         NumberFormatInfo number_info = null;
547                         if (formatProvider != null)
548                                 number_info = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
549                         if (number_info == null)
550                                 number_info = Thread.CurrentThread.CurrentCulture.NumberFormat;
551
552                         string decimal_separator = number_info.NumberDecimalSeparator;
553                         int days, hours, minutes, seconds, milliseconds, fractional;
554
555                         days = Math.Abs (Days);
556                         hours = Math.Abs (Hours);
557                         minutes = Math.Abs (Minutes);
558                         seconds = Math.Abs (Seconds);
559                         milliseconds = Math.Abs (Milliseconds);
560                         fractional = (int) Math.Abs (_ticks % TicksPerSecond);
561
562                         // Set Capacity depending on whether it's long or shot format
563                         StringBuilder sb = new StringBuilder (format == "g" ? 16 : 32);
564                         if (_ticks < 0)
565                                 sb.Append ('-');
566
567                         switch (format) {
568                                 case "g": // short version
569                                         if (days != 0) {
570                                                 sb.Append (days.ToString ());
571                                                 sb.Append (':');
572                                         }
573                                         sb.Append (hours.ToString ());
574                                         sb.Append (':');
575                                         sb.Append (minutes.ToString ("D2"));
576                                         sb.Append (':');
577                                         sb.Append (seconds.ToString ("D2"));
578                                         if (milliseconds != 0) {
579                                                 sb.Append (decimal_separator);
580                                                 sb.Append (milliseconds.ToString ("D3"));
581                                         }
582                                         break;
583                                 case "G": // long version
584                                         sb.Append (days.ToString ("D1"));
585                                         sb.Append (':');
586                                         sb.Append (hours.ToString ("D2"));
587                                         sb.Append (':');
588                                         sb.Append (minutes.ToString ("D2"));
589                                         sb.Append (':');
590                                         sb.Append (seconds.ToString ("D2"));
591                                         sb.Append (decimal_separator);
592                                         sb.Append (fractional.ToString ("D7"));
593                                         break;
594                         }
595
596                         return sb.ToString ();
597                 }
598
599                 string ToStringCustom (string format)
600                 {
601                         // Single char formats are not accepted.
602                         if (format.Length < 2)
603                                 throw new FormatException ("The format is not recognized.");
604
605                         FormatParser parser = new FormatParser (format);
606                         FormatElement element;
607                         int value;
608
609                         StringBuilder sb = new StringBuilder (format.Length + 1);
610
611                         while (true) {
612                                 if (parser.AtEnd)
613                                         break;
614
615                                 element = parser.GetNextElement ();
616                                 switch (element.Type) {
617                                 case FormatElementType.Days:
618                                         value = Math.Abs (Days);
619                                         break;
620                                 case FormatElementType.Hours:
621                                         value = Math.Abs (Hours);
622                                         break;
623                                 case FormatElementType.Minutes:
624                                         value = Math.Abs (Minutes);
625                                         break;
626                                 case FormatElementType.Seconds:
627                                         value = Math.Abs (Seconds);
628                                         break;
629                                 case FormatElementType.Ticks:
630                                 case FormatElementType.TicksUppercase:
631                                         //
632                                         // TODO: Unify with datetime ticks formatting
633                                         //
634                                         value = (int)(_ticks % TicksPerSecond);
635                                         if (value == 0) {
636                                                 if (element.Type == FormatElementType.Ticks)
637                                                         break;
638
639                                                 continue;
640                                         }
641
642                                         int total_length = element.IntValue;
643                                         const int max_length = 7;
644                                         int digits = max_length;
645                                         for (var dv = (int)Math.Pow (10, max_length - 1); dv > value; dv /= 10, --digits)
646                                                 ;
647
648                                         //
649                                         // Skip only leading zeros in F format
650                                         //
651                                         if (element.Type == FormatElementType.TicksUppercase && max_length - digits >= total_length)
652                                                 continue;
653
654                                         //
655                                         // Add leading zeros
656                                         //
657                                         int leading = 0;
658                                         for (; leading < total_length && leading < max_length - digits; ++leading) {
659                                                 sb.Append ("0");
660                                         }
661
662                                         if (total_length == leading)
663                                                 continue;
664
665                                         //
666                                         // Remove trailing zeros
667                                         //
668                                         if (element.Type == FormatElementType.TicksUppercase) {
669                                                 while (value % 10 == 0)
670                                                         value /= 10;
671                                         }
672
673                                         var max_value = (int)Math.Pow (10, total_length - leading);
674                                         while (value >= max_value)
675                                                 value /= 10;
676
677                                         sb.Append (value.ToString (CultureInfo.InvariantCulture));
678                                         continue;
679                                 case FormatElementType.EscapedChar:
680                                         sb.Append (element.CharValue);
681                                         continue;
682                                 case FormatElementType.Literal:
683                                         sb.Append (element.StringValue);
684                                         continue;
685                                 default:
686                                         throw new FormatException ("The format is not recognized.");
687                                 }
688
689                                 sb.Append (value.ToString ("D" + element.IntValue.ToString ()));
690                         }
691
692                         return sb.ToString ();
693                 }
694 #endif
695
696                 public static TimeSpan operator + (TimeSpan t1, TimeSpan t2)
697                 {
698                         return t1.Add (t2);
699                 }
700
701                 public static bool operator == (TimeSpan t1, TimeSpan t2)
702                 {
703                         return t1._ticks == t2._ticks;
704                 }
705
706                 public static bool operator > (TimeSpan t1, TimeSpan t2)
707                 {
708                         return t1._ticks > t2._ticks;
709                 }
710
711                 public static bool operator >= (TimeSpan t1, TimeSpan t2)
712                 {
713                         return t1._ticks >= t2._ticks;
714                 }
715
716                 public static bool operator != (TimeSpan t1, TimeSpan t2)
717                 {
718                         return t1._ticks != t2._ticks;
719                 }
720
721                 public static bool operator < (TimeSpan t1, TimeSpan t2)
722                 {
723                         return t1._ticks < t2._ticks;
724                 }
725
726                 public static bool operator <= (TimeSpan t1, TimeSpan t2)
727                 {
728                         return t1._ticks <= t2._ticks;
729                 }
730
731                 public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
732                 {
733                         return t1.Subtract (t2);
734                 }
735
736                 public static TimeSpan operator - (TimeSpan t)
737                 {
738                         return t.Negate ();
739                 }
740
741                 public static TimeSpan operator + (TimeSpan t)
742                 {
743                         return t;
744                 }
745
746                 enum ParseError {
747                         None,
748                         Format,
749                         Overflow
750                 }
751
752                 // Class Parser implements parser for TimeSpan.Parse
753                 private class Parser
754                 {
755                         private string _src;
756                         private int _cur = 0;
757                         private int _length;
758                         ParseError parse_error;
759 #if NET_4_0
760                         bool parsed_ticks;
761                         NumberFormatInfo number_format;
762                         int parsed_numbers_count;
763                         bool parsed_days_separator;
764
765                         public bool Exact; // no fallback, strict pattern.
766                         public bool AllMembersRequired;
767                         public bool CultureSensitive = true;
768                         public bool UseColonAsDaySeparator = true;
769 #endif
770
771                         public Parser (string src)
772                         {
773                                 _src = src;
774                                 _length = _src.Length;
775 #if NET_4_0
776                                 number_format = GetNumberFormatInfo (null);
777 #endif
778                         }
779
780 #if NET_4_0
781                         // Reset state data, so we can execute another parse over the input.
782                         void Reset ()
783                         {
784                                 _cur = 0;
785                                 parse_error = ParseError.None;
786                                 parsed_ticks = parsed_days_separator = false;
787                                 parsed_numbers_count = 0;
788                         }
789
790                         public Parser (string src, IFormatProvider formatProvider) :
791                                 this (src)
792                         {
793                                 number_format = GetNumberFormatInfo (formatProvider);
794                         }
795
796                         static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
797                         {
798                                 NumberFormatInfo format = null;
799                                 if (formatProvider != null)
800                                         format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
801                                 if (format == null)
802                                         format = Thread.CurrentThread.CurrentCulture.NumberFormat;
803
804                                 return format;
805                         }
806 #endif
807         
808                         public bool AtEnd {
809                                 get {
810                                         return _cur >= _length;
811                                 }
812                         }
813
814                         // All "Parse" functions throw a FormatException on syntax error.
815                         // Their return value is semantic value of the item parsed.
816
817                         // Range checking is spread over three different places:
818                         // 1) When parsing "int" values, an exception is thrown immediately
819                         //    when the value parsed exceeds the maximum value for an int.
820                         // 2) An explicit check is built in that checks for hours > 23 and
821                         //    for minutes and seconds > 59.
822                         // 3) Throwing an exceptions for a final TimeSpan value > MaxValue
823                         //    or < MinValue is left to the TimeSpan constructor called.
824
825                         // Parse zero or more whitespace chars.
826                         private void ParseWhiteSpace ()
827                         {
828                                 while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
829                                         _cur++;
830                                 }
831                         }
832
833                         // Parse optional sign character.
834                         private bool ParseSign ()
835                         {
836                                 bool res = false;
837
838                                 if (!AtEnd && _src[_cur] == '-') {
839                                         res = true;
840                                         _cur++;
841                                 }
842
843                                 return res;
844                         }
845
846 #if NET_4_0
847                         // Used for custom formats parsing, where we may need to declare how
848                         // many digits we expect, as well as the maximum allowed.
849                         private int ParseIntExact (int digit_count, int max_digit_count)
850                         {
851                                 long res = 0;
852                                 int count = 0;
853
854                                 // We can have more than one preceding zero here.
855                                 while (!AtEnd && Char.IsDigit (_src, _cur)) {
856                                         res = res * 10 + _src [_cur] - '0';
857                                         if (res > Int32.MaxValue) {
858                                                 SetParseError (ParseError.Format);
859                                                 break;
860                                         }
861                                         _cur++;
862                                         count++;
863                                 }
864
865                                 // digit_count = 1 means we can use up to maximum count,
866                                 if (count == 0 || (digit_count > 1 && digit_count != count) ||
867                                                 count > max_digit_count)
868                                         SetParseError (ParseError.Format);
869
870                                 return (int)res;
871                         }
872 #endif
873
874                         // Parse simple int value
875                         private int ParseInt (bool optional)
876                         {
877                                 if (optional && AtEnd)
878                                         return 0;
879
880                                 long res = 0;
881                                 int count = 0;
882
883                                 while (!AtEnd && Char.IsDigit (_src, _cur)) {
884                                         res = res * 10 + _src[_cur] - '0';
885                                         if (res > Int32.MaxValue) {
886                                                 SetParseError (ParseError.Overflow);
887                                                 break;
888                                         }
889                                         _cur++;
890                                         count++;
891                                 }
892
893                                 if (!optional && (count == 0))
894                                         SetParseError (ParseError.Format);
895 #if NET_4_0
896                                 if (count > 0)
897                                         parsed_numbers_count++;
898 #endif
899
900                                 return (int)res;
901                         }
902
903 #if NET_4_0
904                         // This behaves pretty much like ParseOptDot, but we need to have it
905                         // as a separated routine for both days and decimal separators.
906                         private bool ParseOptDaysSeparator ()
907                         {
908                                 if (AtEnd)
909                                         return false;
910
911                                 if (_src[_cur] == '.') {
912                                         _cur++;
913                                         parsed_days_separator = true;
914                                         return true;
915                                 }
916                                 return false;
917                         }
918
919                         // Just as ParseOptDot, but for decimal separator
920                         private bool ParseOptDecimalSeparator ()
921                         {
922                                 if (AtEnd)
923                                         return false;
924
925                                 // we may need to provide compatibility with old versions using '.'
926                                 // for culture insensitve and non exact formats.
927                                 if (!Exact || !CultureSensitive)
928                                         if (_src [_cur] == '.') {
929                                                 _cur++;
930                                                 return true;
931                                         }
932
933                                 string decimal_separator = number_format.NumberDecimalSeparator;
934                                 if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) {
935                                         _cur += decimal_separator.Length;
936                                         return true;
937                                 }
938
939                                 return false;
940                         }
941
942                         private bool ParseLiteral (string value)
943                         {
944                                 if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) {
945                                         _cur += value.Length;
946                                         return true;
947                                 }
948
949                                 return false;
950                         }
951
952                         private bool ParseChar (char c)
953                         {
954                                 if (!AtEnd && _src [_cur] == c) {
955                                         _cur++;
956                                         return true;
957                                 }
958
959                                 return false;
960                         }
961 #endif
962                         // Parse optional dot
963                         private bool ParseOptDot ()
964                         {
965                                 if (AtEnd)
966                                         return false;
967
968                                 if (_src[_cur] == '.') {
969                                         _cur++;
970                                         return true;
971                                 }
972                                 return false;
973                         }       
974
975                         private void ParseColon (bool optional)
976                         {
977                                 if (!AtEnd) {
978                                         if (_src[_cur] == ':')
979                                                 _cur++;
980                                         else if (!optional)
981                                                 SetParseError (ParseError.Format);
982                                 }
983                         }
984
985                         // Parse [1..7] digits, representing fractional seconds (ticks)
986                         // In 4.0 more than 7 digits will cause an OverflowException
987                         private long ParseTicks ()
988                         {
989                                 long mag = 1000000;
990                                 long res = 0;
991                                 bool digitseen = false;
992                                 
993                                 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
994                                         res = res + (_src[_cur] - '0') * mag;
995                                         _cur++;
996                                         mag = mag / 10;
997                                         digitseen = true;
998                                 }
999
1000                                 if (!digitseen)
1001                                         SetParseError (ParseError.Format);
1002 #if NET_4_0
1003                                 else if (!AtEnd && Char.IsDigit (_src, _cur))
1004                                         SetParseError (ParseError.Overflow);
1005
1006                                 parsed_ticks = true;
1007 #endif
1008
1009                                 return res;
1010                         }
1011
1012 #if NET_4_0
1013                         // Used by custom formats parsing
1014                         // digits_count = 0 for digits up to max_digits_count (optional), and other value to
1015                         // force a precise number of digits.
1016                         private long ParseTicksExact (int digits_count, int max_digits_count)
1017                         {
1018                                 long mag = 1000000;
1019                                 long res = 0;
1020                                 int count = 0;
1021
1022                                 while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
1023                                         res = res + (_src [_cur] - '0') * mag;
1024                                         _cur++;
1025                                         count++;
1026                                         mag = mag / 10;
1027                                 }
1028
1029                                 if ((digits_count > 0 && count != digits_count) ||
1030                                                 count > max_digits_count)
1031                                         SetParseError (ParseError.Format);
1032
1033                                 return res;
1034                         }
1035 #endif
1036
1037                         void SetParseError (ParseError error)
1038                         {
1039                                 // We preserve the very first error.
1040                                 if (parse_error != ParseError.None)
1041                                         return;
1042
1043                                 parse_error = error;
1044                         }
1045
1046 #if NET_4_0
1047                         bool CheckParseSuccess (bool tryParse)
1048 #else
1049                         bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse)
1050 #endif
1051                         {
1052                                 // We always report the first error, but for 2.0 we need to give a higher
1053                                 // precence to per-element overflow (as opposed to int32 overflow).
1054 #if NET_4_0
1055                                 if (parse_error == ParseError.Overflow) {
1056 #else
1057                                 if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) {
1058 #endif
1059                                         if (tryParse)
1060                                                 return false;
1061                                         throw new OverflowException (
1062                                                 Locale.GetText ("Invalid time data."));
1063                                 }
1064
1065                                 if (parse_error == ParseError.Format) {
1066                                         if (tryParse)
1067                                                 return false;
1068                                         throw new FormatException (
1069                                                 Locale.GetText ("Invalid format for TimeSpan.Parse."));
1070                                 }
1071
1072                                 return true;
1073                         }
1074
1075 #if NET_4_0
1076                         // We are using a different parse approach in 4.0, due to some changes in the behaviour
1077                         // of the parse routines.
1078                         // The input string is documented as:
1079                         //      Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1080                         //
1081                         // There are some special cases as part of 4.0, however:
1082                         // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss'
1083                         // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value
1084                         // exceeding the valid range for hours: 0-23.
1085                         // 3. The decimal separator can be retrieved from the current culture, as well as keeping support
1086                         // for the '.' value as part of keeping compatibility.
1087                         //
1088                         // So we take the approach to parse, if possible, 4 integers, and depending on both how many were
1089                         // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds.
1090                         //
1091                         public bool Execute (bool tryParse, out TimeSpan result)
1092                         {
1093                                 bool sign;
1094                                 int value1, value2, value3, value4;
1095                                 int days, hours, minutes, seconds;
1096                                 long ticks = 0;
1097
1098                                 result = TimeSpan.Zero;
1099                                 value1 = value2 = value3 = value4 = 0;
1100                                 days = hours = minutes = seconds = 0;
1101
1102                                 Reset ();
1103
1104                                 ParseWhiteSpace ();
1105                                 sign = ParseSign ();
1106
1107                                 // Parse 4 integers, making only the first one non-optional.
1108                                 value1 = ParseInt (false);
1109                                 if (!ParseOptDaysSeparator ()) // Parse either day separator or colon
1110                                         ParseColon (false);
1111                                 int p = _cur;
1112                                 value2 = ParseInt (true);
1113                                 value3 = value4 = 0;
1114                                 if (p < _cur) {
1115                                         ParseColon (true);
1116                                         value3 = ParseInt (true);
1117                                         ParseColon (true);
1118                                         value4 = ParseInt (true);
1119                                 }
1120
1121                                 // We know the precise separator for ticks, so there's no need to guess.
1122                                 if (ParseOptDecimalSeparator ())
1123                                         ticks = ParseTicks ();
1124
1125                                 ParseWhiteSpace ();
1126
1127                                 if (!AtEnd)
1128                                         SetParseError (ParseError.Format);
1129
1130                                 if (Exact)
1131                                         // In Exact mode we cannot allow both ':' and '.' as day separator.
1132                                         if (UseColonAsDaySeparator && parsed_days_separator ||
1133                                                 AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks))
1134                                                 SetParseError (ParseError.Format);
1135
1136                                 switch (parsed_numbers_count) {
1137                                         case 1:
1138                                                 days = value1;
1139                                                 break;
1140                                         case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm'
1141                                                 if (parsed_days_separator)
1142                                                         SetParseError (ParseError.Format);
1143                                                 else {
1144                                                         hours = value1;
1145                                                         minutes = value2;
1146                                                 }
1147                                                 break;
1148                                         case 3: // Assign the first value to days if we parsed a day separator or the value
1149                                                 // is not in the valid range for hours.
1150                                                 if (parsed_days_separator || value1 > 23) {
1151                                                         days = value1;
1152                                                         hours = value2;
1153                                                         minutes = value3;
1154                                                 } else {
1155                                                         hours = value1;
1156                                                         minutes = value2;
1157                                                         seconds = value3;
1158                                                 }
1159                                                 break;
1160                                         case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss'
1161                                                 if (!UseColonAsDaySeparator && !parsed_days_separator)
1162                                                         SetParseError (ParseError.Format);
1163                                                 else {
1164                                                         days = value1;
1165                                                         hours = value2;
1166                                                         minutes = value3;
1167                                                         seconds = value4;
1168                                                 }
1169                                                 break;
1170                                 }
1171
1172                                 if (hours > 23 || minutes > 59 || seconds > 59)
1173                                         SetParseError (ParseError.Overflow);
1174
1175                                 if (!CheckParseSuccess (tryParse))
1176                                         return false;
1177
1178                                 long t;
1179                                 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1180                                         return false;
1181
1182                                 try {
1183                                         t = checked ((sign) ? (-t - ticks) : (t + ticks));
1184                                 } catch (OverflowException) {
1185                                         if (tryParse)
1186                                                 return false;
1187                                         throw;
1188                                 }
1189
1190                                 result = new TimeSpan (t);
1191                                 return true;
1192                         }
1193 #else
1194                         public bool Execute (bool tryParse, out TimeSpan result)
1195                         {
1196                                 bool sign;
1197                                 int days;
1198                                 int hours = 0;
1199                                 int minutes;
1200                                 int seconds;
1201                                 long ticks;
1202
1203                                 result = TimeSpan.Zero;
1204
1205                                 // documented as...
1206                                 // Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
1207                                 // ... but not entirely true as an lonely 
1208                                 // integer will be parsed as a number of days
1209                                 ParseWhiteSpace ();
1210                                 sign = ParseSign ();
1211                                 days = ParseInt (false);
1212                                 if (ParseOptDot ()) {
1213                                         hours = ParseInt (true);
1214                                 }
1215                                 else if (!AtEnd) {
1216                                         hours = days;
1217                                         days = 0;
1218                                 }
1219                                 ParseColon(false);
1220                                 int p = _cur;
1221                                 minutes = ParseInt (true);
1222                                 seconds = 0;
1223                                 if (p < _cur) {
1224                                         ParseColon (true);
1225                                         seconds = ParseInt (true);
1226                                 }
1227
1228                                 if ( ParseOptDot () ) {
1229                                         ticks = ParseTicks ();
1230                                 }
1231                                 else {
1232                                         ticks = 0;
1233                                 }
1234                                 ParseWhiteSpace ();
1235         
1236                                 if (!AtEnd)
1237                                         SetParseError (ParseError.Format);
1238
1239                                 if (!CheckParseSuccess (hours, minutes, seconds, tryParse))
1240                                         return false;
1241
1242                                 long t;
1243                                 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1244                                         return false;
1245
1246                                 try {
1247                                         t = checked ((sign) ? (-t - ticks) : (t + ticks));
1248                                 } catch (OverflowException) {
1249                                         if (tryParse)
1250                                                 return false;
1251                                         throw;
1252                                 }
1253
1254                                 result = new TimeSpan (t);
1255                                 return true;
1256                         }
1257 #endif
1258
1259 #if NET_4_0
1260                         public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result)
1261                         {
1262                                 int days, hours, minutes, seconds;
1263                                 long ticks;
1264                                 FormatElement format_element;
1265
1266                                 days = hours = minutes = seconds = -1;
1267                                 ticks = -1;
1268                                 result = TimeSpan.Zero;
1269                                 Reset ();
1270
1271                                 FormatParser format_parser = new FormatParser (format);
1272
1273                                 for (;;) {
1274                                         // We need to continue even if AtEnd == true, since we could have
1275                                         // a optional second element.
1276                                         if (parse_error != ParseError.None)
1277                                                 break;
1278                                         if (format_parser.AtEnd)
1279                                                 break;
1280
1281                                         format_element = format_parser.GetNextElement ();
1282                                         switch (format_element.Type) {
1283                                                 case FormatElementType.Days:
1284                                                         if (days != -1)
1285                                                                 goto case FormatElementType.Error;
1286                                                         days = ParseIntExact (format_element.IntValue, 8);
1287                                                         break;
1288                                                 case FormatElementType.Hours:
1289                                                         if (hours != -1)
1290                                                                 goto case FormatElementType.Error;
1291                                                         hours = ParseIntExact (format_element.IntValue, 2);
1292                                                         break;
1293                                                 case FormatElementType.Minutes:
1294                                                         if (minutes != -1)
1295                                                                 goto case FormatElementType.Error;
1296                                                         minutes = ParseIntExact (format_element.IntValue, 2);
1297                                                         break;
1298                                                 case FormatElementType.Seconds:
1299                                                         if (seconds != -1)
1300                                                                 goto case FormatElementType.Error;
1301                                                         seconds = ParseIntExact (format_element.IntValue, 2);
1302                                                         break;
1303                                                 case FormatElementType.Ticks:
1304                                                         if (ticks != -1)
1305                                                                 goto case FormatElementType.Error;
1306                                                         ticks = ParseTicksExact (format_element.IntValue,
1307                                                                         format_element.IntValue);
1308                                                         break;
1309                                                 case FormatElementType.TicksUppercase:
1310                                                         // Similar to Milliseconds, but optional and the
1311                                                         // number of F defines the max length, not the required one.
1312                                                         if (ticks != -1)
1313                                                                 goto case FormatElementType.Error;
1314                                                         ticks = ParseTicksExact (0, format_element.IntValue);
1315                                                         break;
1316                                                 case FormatElementType.Literal:
1317                                                         if (!ParseLiteral (format_element.StringValue))
1318                                                                 SetParseError (ParseError.Format);
1319                                                         break;
1320                                                 case FormatElementType.EscapedChar:
1321                                                         if (!ParseChar (format_element.CharValue))
1322                                                                 SetParseError (ParseError.Format);
1323                                                         break;
1324                                                 case FormatElementType.Error:
1325                                                         SetParseError (ParseError.Format);
1326                                                         break;
1327                                         }
1328                                 }
1329
1330                                 if (days == -1)
1331                                         days = 0;
1332                                 if (hours == -1)
1333                                         hours = 0;
1334                                 if (minutes == -1)
1335                                         minutes = 0;
1336                                 if (seconds == -1)
1337                                         seconds = 0;
1338                                 if (ticks == -1)
1339                                         ticks = 0;
1340
1341                                 if (!AtEnd || !format_parser.AtEnd)
1342                                         SetParseError (ParseError.Format);
1343                                 if (hours > 23 || minutes > 59 || seconds > 59)
1344                                         SetParseError (ParseError.Format);
1345
1346                                 if (!CheckParseSuccess (tryParse))
1347                                         return false;
1348
1349                                 long t;
1350                                 if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t))
1351                                         return false;
1352
1353                                 try {
1354                                         t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
1355                                 } catch (OverflowException) {
1356                                         if (tryParse)
1357                                                 return false;
1358                                         throw;
1359                                 }
1360
1361                                 result = new TimeSpan (t);
1362                                 return true;
1363                         }
1364 #endif
1365                 }
1366 #if NET_4_0
1367                 enum FormatElementType 
1368                 {
1369                         Days,
1370                         Hours,
1371                         Minutes,
1372                         Seconds,
1373                         Ticks, // 'f'
1374                         TicksUppercase, // 'F'
1375                         Literal,
1376                         EscapedChar,
1377                         Error,
1378                         End
1379                 }
1380
1381                 struct FormatElement
1382                 {
1383                         public FormatElement (FormatElementType type)
1384                         {
1385                                 Type = type;
1386                                 CharValue = (char)0;
1387                                 IntValue = 0;
1388                                 StringValue = null;
1389                         }
1390
1391                         public FormatElementType Type;
1392                         public char CharValue; // Used by EscapedChar
1393                         public string StringValue; // Used by Literal
1394                         public int IntValue; // Used by numerical elements.
1395                 }
1396
1397                 class FormatParser 
1398                 {
1399                         int cur;
1400                         string format;
1401
1402                         public FormatParser (string format)
1403                         {
1404                                 this.format = format;
1405                         }
1406
1407                         public bool AtEnd {
1408                                 get {
1409                                         return cur >= format.Length;
1410                                 }
1411                         }
1412
1413                         public FormatElement GetNextElement ()
1414                         {
1415                                 FormatElement element = new FormatElement ();
1416
1417                                 if (AtEnd)
1418                                         return new FormatElement (FormatElementType.End);
1419
1420                                 int count = 0;
1421                                 switch (format [cur]) {
1422                                         case 'd':
1423                                                 count = ParseChar ('d');
1424                                                 if (count > 8)
1425                                                         return new FormatElement (FormatElementType.Error);
1426                                                 element.Type = FormatElementType.Days;
1427                                                 element.IntValue = count;
1428                                                 break;
1429                                         case 'h':
1430                                                 count = ParseChar ('h');
1431                                                 if (count > 2)
1432                                                         return new FormatElement (FormatElementType.Error);
1433                                                 element.Type = FormatElementType.Hours;
1434                                                 element.IntValue = count;
1435                                                 break;
1436                                         case 'm':
1437                                                 count = ParseChar ('m');
1438                                                 if (count > 2)
1439                                                         return new FormatElement (FormatElementType.Error);
1440                                                 element.Type = FormatElementType.Minutes;
1441                                                 element.IntValue = count;
1442                                                 break;
1443                                         case 's':
1444                                                 count = ParseChar ('s');
1445                                                 if (count > 2)
1446                                                         return new FormatElement (FormatElementType.Error);
1447                                                 element.Type = FormatElementType.Seconds;
1448                                                 element.IntValue = count;
1449                                                 break;
1450                                         case 'f':
1451                                                 count = ParseChar ('f');
1452                                                 if (count > 7)
1453                                                         return new FormatElement (FormatElementType.Error);
1454                                                 element.Type = FormatElementType.Ticks;
1455                                                 element.IntValue = count;
1456                                                 break;
1457                                         case 'F':
1458                                                 count = ParseChar ('F');
1459                                                 if (count > 7)
1460                                                         return new FormatElement (FormatElementType.Error);
1461                                                 element.Type = FormatElementType.TicksUppercase;
1462                                                 element.IntValue = count;
1463                                                 break;
1464                                         case '%':
1465                                                 cur++;
1466                                                 if (AtEnd)
1467                                                         return new FormatElement (FormatElementType.Error);
1468                                                 if (format [cur] == 'd')
1469                                                         goto case 'd';
1470                                                 else if (format [cur] == 'h')
1471                                                         goto case 'h';
1472                                                 else if (format [cur] == 'm')
1473                                                         goto case 'm';
1474                                                 else if (format [cur] == 's')
1475                                                         goto case 's';
1476                                                 else if (format [cur] == 'f')
1477                                                         goto case 'f';
1478                                                 else if (format [cur] == 'F')
1479                                                         goto case 'F';
1480
1481                                                 return new FormatElement (FormatElementType.Error);
1482                                         case '\'':
1483                                                 string literal = ParseLiteral ();
1484                                                 if (literal == null)
1485                                                         return new FormatElement (FormatElementType.Error);
1486                                                 element.Type = FormatElementType.Literal;
1487                                                 element.StringValue = literal;
1488                                                 break;
1489                                         case '\\':
1490                                                 char escaped_char = ParseEscapedChar ();
1491                                                 if ((int)escaped_char == 0)
1492                                                         return new FormatElement (FormatElementType.Error);
1493                                                 element.Type = FormatElementType.EscapedChar;
1494                                                 element.CharValue = escaped_char;
1495                                                 break;
1496                                         default:
1497                                                 return new FormatElement (FormatElementType.Error);
1498                                 }
1499
1500                                 return element;
1501                         }
1502
1503                         int ParseChar (char c)
1504                         {
1505                                 int count = 0;
1506
1507                                 while (!AtEnd && format [cur] == c) {
1508                                         cur++;
1509                                         count++;
1510                                 }
1511
1512                                 return count;
1513                         }
1514
1515                         char ParseEscapedChar ()
1516                         {
1517                                 if (AtEnd || format [cur] != '\\')
1518                                         return (char)0;
1519
1520                                 cur++;
1521                                 if (AtEnd)
1522                                         return (char)0;
1523
1524                                 return format [cur++];
1525                         }
1526
1527                         string ParseLiteral ()
1528                         {
1529                                 int start;
1530                                 int count = 0;
1531
1532                                 if (AtEnd || format [cur] != '\'')
1533                                         return null;
1534
1535                                 start = ++cur;
1536                                 while (!AtEnd && format [cur] != '\'') {
1537                                         cur++;
1538                                         count++;
1539                                 }
1540
1541                                 if (!AtEnd && format [cur] == '\'') {
1542                                         cur++;
1543                                         return format.Substring (start, count);
1544                                 }
1545
1546                                 return null;
1547                         }
1548                 }
1549 #endif
1550
1551         }
1552 }