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