X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem%2FTimeSpan.cs;h=7762dfc9177d180adbe5d7ec43d4a55ef72d1ce7;hb=5ad1099341581dee94f77b32db728918e90fa64f;hp=7b8f0dc7f9e38f920dc023904ae625f24a19a5d5;hpb=27d07d4257f8105c0476485a3ed9fb244640a019;p=mono.git diff --git a/mcs/class/corlib/System/TimeSpan.cs b/mcs/class/corlib/System/TimeSpan.cs index 7b8f0dc7f9e..7762dfc9177 100644 --- a/mcs/class/corlib/System/TimeSpan.cs +++ b/mcs/class/corlib/System/TimeSpan.cs @@ -5,10 +5,12 @@ // Duco Fijma (duco@lorentz.xs4all.nl) // Andreas Nahr (ClassDevelopment@A-SoftTech.com) // Sebastien Pouliot +// Marek Safar (marek.safar@gmail.com) // // (C) 2001 Duco Fijma // (C) 2004 Andreas Nahr // Copyright (C) 2004 Novell (http://www.novell.com) +// Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -39,6 +41,9 @@ namespace System [Serializable] [System.Runtime.InteropServices.ComVisible (true)] public struct TimeSpan : IComparable, IComparable, IEquatable +#if NET_4_0 + , IFormattable +#endif { #if MONOTOUCH static TimeSpan () { @@ -306,7 +311,7 @@ namespace System value = (value * (tickMultiplicator / TicksPerMillisecond)); checked { - long val = (long) Math.Round(value); + long val = (long) Math.Round(value, MidpointRounding.AwayFromZero); return new TimeSpan (val * TicksPerMillisecond); } } @@ -357,27 +362,127 @@ namespace System } #if NET_4_0 - public static TimeSpan Parse (string s, IFormatProvider formatProvider) + public static TimeSpan Parse (string input, IFormatProvider formatProvider) { - if (s == null) - throw new ArgumentNullException ("s"); + if (input == null) + throw new ArgumentNullException ("input"); TimeSpan result; - Parser p = new Parser (s, formatProvider); + Parser p = new Parser (input, formatProvider); p.Execute (false, out result); return result; } - public static bool TryParse (string s, IFormatProvider formatProvider, out TimeSpan result) + public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result) { - if (s == null || s.Length == 0) { + if (string.IsNullOrEmpty (input)) { result = TimeSpan.Zero; return false; } - Parser p = new Parser (s, formatProvider); + Parser p = new Parser (input, formatProvider); return p.Execute (true, out result); } + + public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider) + { + if (format == null) + throw new ArgumentNullException ("format"); + + return ParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None); + } + + public static TimeSpan ParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles) + { + if (format == null) + throw new ArgumentNullException ("format"); + + return ParseExact (input, new string [] { format }, formatProvider, styles); + } + + public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider) + { + return ParseExact (input, formats, formatProvider, TimeSpanStyles.None); + } + + public static TimeSpan ParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles) + { + if (input == null) + throw new ArgumentNullException ("input"); + if (formats == null) + throw new ArgumentNullException ("formats"); + + // All the errors found during the parsing process are reported as FormatException. + TimeSpan result; + if (!TryParseExact (input, formats, formatProvider, styles, out result)) + throw new FormatException ("Invalid format."); + + return result; + } + + public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, out TimeSpan result) + { + return TryParseExact (input, new string [] { format }, formatProvider, TimeSpanStyles.None, out result); + } + + public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles, + out TimeSpan result) + { + return TryParseExact (input, new string [] { format }, formatProvider, styles, out result); + } + + public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, out TimeSpan result) + { + return TryParseExact (input, formats, formatProvider, TimeSpanStyles.None, out result); + } + + public static bool TryParseExact (string input, string [] formats, IFormatProvider formatProvider, TimeSpanStyles styles, + out TimeSpan result) + { + result = TimeSpan.Zero; + + if (input == null || formats == null || formats.Length == 0) + return false; + + Parser p = new Parser (input, formatProvider); + p.Exact = true; + + foreach (string format in formats) { + if (format == null || format.Length == 0) + return false; // wrong format, return immediately. + + switch (format) { + case "g": + p.AllMembersRequired = false; + p.CultureSensitive = true; + p.UseColonAsDaySeparator = true; + break; + case "G": + p.AllMembersRequired = true; + p.CultureSensitive = true; + p.UseColonAsDaySeparator = true; + break; + case "c": + p.AllMembersRequired = false; + p.CultureSensitive = false; + p.UseColonAsDaySeparator = false; + break; + default: + // Single letter formats other than the defined ones are not accepted. + if (format.Length == 1) + return false; + // custom format + if (p.ExecuteWithFormat (format, styles, true, out result)) + return true; + continue; + } + + if (p.Execute (true, out result)) + return true; + } + + return false; + } #endif public TimeSpan Subtract (TimeSpan ts) @@ -431,15 +536,16 @@ namespace System public string ToString (string format, IFormatProvider formatProvider) { - if (format == null || format.Length == 0 || format == "c") // Default version + if (format == null || format.Length == 0 || format == "c" || + format == "t" || format == "T") // Default version return ToString (); if (format != "g" && format != "G") - throw new FormatException ("The format is not recognized."); + return ToStringCustom (format); // custom formats ignore culture/formatProvider NumberFormatInfo number_info = null; if (formatProvider != null) - number_info = (NumberFormatInfo)formatProvider.GetFormat (typeof (NumberFormatInfo)); + number_info = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo; if (number_info == null) number_info = Thread.CurrentThread.CurrentCulture.NumberFormat; @@ -489,6 +595,67 @@ namespace System return sb.ToString (); } + + string ToStringCustom (string format) + { + // Single char formats are not accepted. + if (format.Length < 2) + throw new FormatException ("The format is not recognized."); + + FormatParser parser = new FormatParser (format); + FormatElement element; + int value; + + StringBuilder sb = new StringBuilder (format.Length + 1); + + while (true) { + if (parser.AtEnd) + break; + + element = parser.GetNextElement (); + switch (element.Type) { + case FormatElementType.Days: + value = Math.Abs (Days); + break; + case FormatElementType.Hours: + value = Math.Abs (Hours); + break; + case FormatElementType.Minutes: + value = Math.Abs (Minutes); + break; + case FormatElementType.Seconds: + value = Math.Abs (Seconds); + break; + case FormatElementType.Ticks: + case FormatElementType.TicksUppercase: + value = Math.Abs (Milliseconds); + if (value == 0) { + if (element.Type == FormatElementType.Ticks) + break; + + continue; + } + + int threshold = (int)Math.Pow (10, element.IntValue); + while (value >= threshold) + value /= 10; + sb.Append (value.ToString ()); + continue; + case FormatElementType.EscapedChar: + sb.Append (element.CharValue); + continue; + case FormatElementType.Literal: + sb.Append (element.StringValue); + continue; + default: + throw new FormatException ("The format is not recognized."); + } + + sb.Append (value.ToString ("D" + element.IntValue.ToString ())); + } + + return sb.ToString (); + } #endif public static TimeSpan operator + (TimeSpan t1, TimeSpan t2) @@ -541,23 +708,49 @@ namespace System return t; } + enum ParseError { + None, + Format, + Overflow + } + // Class Parser implements parser for TimeSpan.Parse private class Parser { private string _src; private int _cur = 0; private int _length; - private bool formatError; - private bool overflowError; + ParseError parse_error; +#if NET_4_0 + bool parsed_ticks; + NumberFormatInfo number_format; + int parsed_numbers_count; + bool parsed_days_separator; + + public bool Exact; // no fallback, strict pattern. + public bool AllMembersRequired; + public bool CultureSensitive = true; + public bool UseColonAsDaySeparator = true; +#endif public Parser (string src) { _src = src; _length = _src.Length; +#if NET_4_0 + number_format = GetNumberFormatInfo (null); +#endif } #if NET_4_0 - NumberFormatInfo number_format; + // Reset state data, so we can execute another parse over the input. + void Reset () + { + _cur = 0; + parse_error = ParseError.None; + parsed_ticks = parsed_days_separator = false; + parsed_numbers_count = 0; + } public Parser (string src, IFormatProvider formatProvider) : this (src) @@ -565,11 +758,11 @@ namespace System number_format = GetNumberFormatInfo (formatProvider); } - NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider) + static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider) { NumberFormatInfo format = null; if (formatProvider != null) - format = (NumberFormatInfo) formatProvider.GetFormat (typeof (NumberFormatInfo)); + format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo; if (format == null) format = Thread.CurrentThread.CurrentCulture.NumberFormat; @@ -615,6 +808,34 @@ namespace System return res; } +#if NET_4_0 + // Used for custom formats parsing, where we may need to declare how + // many digits we expect, as well as the maximum allowed. + private int ParseIntExact (int digit_count, int max_digit_count) + { + long res = 0; + int count = 0; + + // We can have more than one preceding zero here. + while (!AtEnd && Char.IsDigit (_src, _cur)) { + res = res * 10 + _src [_cur] - '0'; + if (res > Int32.MaxValue) { + SetParseError (ParseError.Format); + break; + } + _cur++; + count++; + } + + // digit_count = 1 means we can use up to maximum count, + if (count == 0 || (digit_count > 1 && digit_count != count) || + count > max_digit_count) + SetParseError (ParseError.Format); + + return (int)res; + } +#endif + // Parse simple int value private int ParseInt (bool optional) { @@ -627,7 +848,7 @@ namespace System while (!AtEnd && Char.IsDigit (_src, _cur)) { res = res * 10 + _src[_cur] - '0'; if (res > Int32.MaxValue) { - overflowError = true; + SetParseError (ParseError.Overflow); break; } _cur++; @@ -635,41 +856,67 @@ namespace System } if (!optional && (count == 0)) - formatError = true; + SetParseError (ParseError.Format); +#if NET_4_0 + if (count > 0) + parsed_numbers_count++; +#endif return (int)res; } - // Parse optional dot - private bool ParseOptDot () +#if NET_4_0 + // This behaves pretty much like ParseOptDot, but we need to have it + // as a separated routine for both days and decimal separators. + private bool ParseOptDaysSeparator () { if (AtEnd) return false; if (_src[_cur] == '.') { _cur++; + parsed_days_separator = true; return true; } return false; - } + } -#if NET_4_0 // Just as ParseOptDot, but for decimal separator private bool ParseOptDecimalSeparator () { if (AtEnd) return false; - // Use culture information if available. - if (number_format != null) { - string decimal_separator = number_format.NumberDecimalSeparator; - if (String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) { - _cur += decimal_separator.Length; + // we may need to provide compatibility with old versions using '.' + // for culture insensitve and non exact formats. + if (!Exact || !CultureSensitive) + if (_src [_cur] == '.') { + _cur++; return true; } - return false; - } else if (_src [_cur] == '.') { + string decimal_separator = number_format.NumberDecimalSeparator; + if (CultureSensitive && String.Compare (_src, _cur, decimal_separator, 0, decimal_separator.Length) == 0) { + _cur += decimal_separator.Length; + return true; + } + + return false; + } + + private bool ParseLiteral (string value) + { + if (!AtEnd && String.Compare (_src, _cur, value, 0, value.Length) == 0) { + _cur += value.Length; + return true; + } + + return false; + } + + private bool ParseChar (char c) + { + if (!AtEnd && _src [_cur] == c) { _cur++; return true; } @@ -677,6 +924,18 @@ namespace System return false; } #endif + // Parse optional dot + private bool ParseOptDot () + { + if (AtEnd) + return false; + + if (_src[_cur] == '.') { + _cur++; + return true; + } + return false; + } private void ParseColon (bool optional) { @@ -684,11 +943,12 @@ namespace System if (_src[_cur] == ':') _cur++; else if (!optional) - formatError = true; + SetParseError (ParseError.Format); } } // Parse [1..7] digits, representing fractional seconds (ticks) + // In 4.0 more than 7 digits will cause an OverflowException private long ParseTicks () { long mag = 1000000; @@ -703,11 +963,199 @@ namespace System } if (!digitseen) - formatError = true; + SetParseError (ParseError.Format); +#if NET_4_0 + else if (!AtEnd && Char.IsDigit (_src, _cur)) + SetParseError (ParseError.Overflow); + + parsed_ticks = true; +#endif + + return res; + } + +#if NET_4_0 + // Used by custom formats parsing + // digits_count = 0 for digits up to max_digits_count (optional), and other value to + // force a precise number of digits. + private long ParseTicksExact (int digits_count, int max_digits_count) + { + long mag = 1000000; + long res = 0; + int count = 0; + + while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) { + res = res + (_src [_cur] - '0') * mag; + _cur++; + count++; + mag = mag / 10; + } + + if ((digits_count > 0 && count != digits_count) || + count > max_digits_count) + SetParseError (ParseError.Format); return res; } +#endif + + void SetParseError (ParseError error) + { + // We preserve the very first error. + if (parse_error != ParseError.None) + return; + + parse_error = error; + } +#if NET_4_0 + bool CheckParseSuccess (bool tryParse) +#else + bool CheckParseSuccess (int hours, int minutes, int seconds, bool tryParse) +#endif + { + // We always report the first error, but for 2.0 we need to give a higher + // precence to per-element overflow (as opposed to int32 overflow). +#if NET_4_0 + if (parse_error == ParseError.Overflow) { +#else + if (parse_error == ParseError.Overflow || hours > 23 || minutes > 59 || seconds > 59) { +#endif + if (tryParse) + return false; + throw new OverflowException ( + Locale.GetText ("Invalid time data.")); + } + + if (parse_error == ParseError.Format) { + if (tryParse) + return false; + throw new FormatException ( + Locale.GetText ("Invalid format for TimeSpan.Parse.")); + } + + return true; + } + +#if NET_4_0 + // We are using a different parse approach in 4.0, due to some changes in the behaviour + // of the parse routines. + // The input string is documented as: + // Parse [ws][-][dd.]hh:mm:ss[.ff][ws] + // + // There are some special cases as part of 4.0, however: + // 1. ':' *can* be used as days separator, instead of '.', making valid the format 'dd:hh:mm:ss' + // 2. A input in the format 'hh:mm:ss' will end up assigned as 'dd.hh:mm' if the first int has a value + // exceeding the valid range for hours: 0-23. + // 3. The decimal separator can be retrieved from the current culture, as well as keeping support + // for the '.' value as part of keeping compatibility. + // + // So we take the approach to parse, if possible, 4 integers, and depending on both how many were + // actually parsed and what separators were read, assign the values to days/hours/minutes/seconds. + // + public bool Execute (bool tryParse, out TimeSpan result) + { + bool sign; + int value1, value2, value3, value4; + int days, hours, minutes, seconds; + long ticks = 0; + + result = TimeSpan.Zero; + value1 = value2 = value3 = value4 = 0; + days = hours = minutes = seconds = 0; + + Reset (); + + ParseWhiteSpace (); + sign = ParseSign (); + + // Parse 4 integers, making only the first one non-optional. + value1 = ParseInt (false); + if (!ParseOptDaysSeparator ()) // Parse either day separator or colon + ParseColon (false); + int p = _cur; + value2 = ParseInt (true); + value3 = value4 = 0; + if (p < _cur) { + ParseColon (true); + value3 = ParseInt (true); + ParseColon (true); + value4 = ParseInt (true); + } + + // We know the precise separator for ticks, so there's no need to guess. + if (ParseOptDecimalSeparator ()) + ticks = ParseTicks (); + + ParseWhiteSpace (); + + if (!AtEnd) + SetParseError (ParseError.Format); + + if (Exact) + // In Exact mode we cannot allow both ':' and '.' as day separator. + if (UseColonAsDaySeparator && parsed_days_separator || + AllMembersRequired && (parsed_numbers_count < 4 || !parsed_ticks)) + SetParseError (ParseError.Format); + + switch (parsed_numbers_count) { + case 1: + days = value1; + break; + case 2: // Two elements are valid only if they are *exactly* in the format: 'hh:mm' + if (parsed_days_separator) + SetParseError (ParseError.Format); + else { + hours = value1; + minutes = value2; + } + break; + case 3: // Assign the first value to days if we parsed a day separator or the value + // is not in the valid range for hours. + if (parsed_days_separator || value1 > 23) { + days = value1; + hours = value2; + minutes = value3; + } else { + hours = value1; + minutes = value2; + seconds = value3; + } + break; + case 4: // We are either on 'dd.hh:mm:ss' or 'dd:hh:mm:ss' + if (!UseColonAsDaySeparator && !parsed_days_separator) + SetParseError (ParseError.Format); + else { + days = value1; + hours = value2; + minutes = value3; + seconds = value4; + } + break; + } + + if (hours > 23 || minutes > 59 || seconds > 59) + SetParseError (ParseError.Overflow); + + if (!CheckParseSuccess (tryParse)) + return false; + + long t; + if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t)) + return false; + + try { + t = checked ((sign) ? (-t - ticks) : (t + ticks)); + } catch (OverflowException) { + if (tryParse) + return false; + throw; + } + + result = new TimeSpan (t); + return true; + } +#else public bool Execute (bool tryParse, out TimeSpan result) { bool sign; @@ -729,27 +1177,20 @@ namespace System if (ParseOptDot ()) { hours = ParseInt (true); } -#if NET_4_0 - // if the value that was going to be used as 'hours' exceeds the range, - // .net keeps it as days, even if there's a colon instead of a dot ahead - else if (days > 23) { - ParseColon (false); - hours = ParseInt (true); - } -#endif else if (!AtEnd) { hours = days; days = 0; } ParseColon(false); + int p = _cur; minutes = ParseInt (true); - ParseColon (true); - seconds = ParseInt (true); -#if NET_4_0 - if ( ParseOptDecimalSeparator () ) { -#else + seconds = 0; + if (p < _cur) { + ParseColon (true); + seconds = ParseInt (true); + } + if ( ParseOptDot () ) { -#endif ticks = ParseTicks (); } else { @@ -758,28 +1199,124 @@ namespace System ParseWhiteSpace (); if (!AtEnd) - formatError = true; + SetParseError (ParseError.Format); - // Overflow has presceance over FormatException - if (overflowError || hours > 23 || minutes > 59 || seconds > 59) { + if (!CheckParseSuccess (hours, minutes, seconds, tryParse)) + return false; + + long t; + if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t)) + return false; + + try { + t = checked ((sign) ? (-t - ticks) : (t + ticks)); + } catch (OverflowException) { if (tryParse) return false; - throw new OverflowException ( - Locale.GetText ("Invalid time data.")); + throw; } - else if (formatError) { - if (tryParse) - return false; - throw new FormatException ( - Locale.GetText ("Invalid format for TimeSpan.Parse.")); + + result = new TimeSpan (t); + return true; + } +#endif + +#if NET_4_0 + public bool ExecuteWithFormat (string format, TimeSpanStyles style, bool tryParse, out TimeSpan result) + { + int days, hours, minutes, seconds; + long ticks; + FormatElement format_element; + + days = hours = minutes = seconds = -1; + ticks = -1; + result = TimeSpan.Zero; + Reset (); + + FormatParser format_parser = new FormatParser (format); + + for (;;) { + // We need to continue even if AtEnd == true, since we could have + // a optional second element. + if (parse_error != ParseError.None) + break; + if (format_parser.AtEnd) + break; + + format_element = format_parser.GetNextElement (); + switch (format_element.Type) { + case FormatElementType.Days: + if (days != -1) + goto case FormatElementType.Error; + days = ParseIntExact (format_element.IntValue, 8); + break; + case FormatElementType.Hours: + if (hours != -1) + goto case FormatElementType.Error; + hours = ParseIntExact (format_element.IntValue, 2); + break; + case FormatElementType.Minutes: + if (minutes != -1) + goto case FormatElementType.Error; + minutes = ParseIntExact (format_element.IntValue, 2); + break; + case FormatElementType.Seconds: + if (seconds != -1) + goto case FormatElementType.Error; + seconds = ParseIntExact (format_element.IntValue, 2); + break; + case FormatElementType.Ticks: + if (ticks != -1) + goto case FormatElementType.Error; + ticks = ParseTicksExact (format_element.IntValue, + format_element.IntValue); + break; + case FormatElementType.TicksUppercase: + // Similar to Milliseconds, but optional and the + // number of F defines the max length, not the required one. + if (ticks != -1) + goto case FormatElementType.Error; + ticks = ParseTicksExact (0, format_element.IntValue); + break; + case FormatElementType.Literal: + if (!ParseLiteral (format_element.StringValue)) + SetParseError (ParseError.Format); + break; + case FormatElementType.EscapedChar: + if (!ParseChar (format_element.CharValue)) + SetParseError (ParseError.Format); + break; + case FormatElementType.Error: + SetParseError (ParseError.Format); + break; + } } + if (days == -1) + days = 0; + if (hours == -1) + hours = 0; + if (minutes == -1) + minutes = 0; + if (seconds == -1) + seconds = 0; + if (ticks == -1) + ticks = 0; + + if (!AtEnd || !format_parser.AtEnd) + SetParseError (ParseError.Format); + if (hours > 23 || minutes > 59 || seconds > 59) + SetParseError (ParseError.Format); + + if (!CheckParseSuccess (tryParse)) + return false; + long t; if (!TimeSpan.CalculateTicks (days, hours, minutes, seconds, 0, false, out t)) return false; try { - t = checked ((sign) ? (-t - ticks) : (t + ticks)); + t = checked ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks)); } catch (OverflowException) { if (tryParse) return false; @@ -789,6 +1326,192 @@ namespace System result = new TimeSpan (t); return true; } +#endif + } +#if NET_4_0 + enum FormatElementType + { + Days, + Hours, + Minutes, + Seconds, + Ticks, // 'f' + TicksUppercase, // 'F' + Literal, + EscapedChar, + Error, + End + } + + struct FormatElement + { + public FormatElement (FormatElementType type) + { + Type = type; + CharValue = (char)0; + IntValue = 0; + StringValue = null; + } + + public FormatElementType Type; + public char CharValue; // Used by EscapedChar + public string StringValue; // Used by Literal + public int IntValue; // Used by numerical elements. + } + + class FormatParser + { + int cur; + string format; + + public FormatParser (string format) + { + this.format = format; + } + + public bool AtEnd { + get { + return cur >= format.Length; + } + } + + public FormatElement GetNextElement () + { + FormatElement element = new FormatElement (); + + if (AtEnd) + return new FormatElement (FormatElementType.End); + + int count = 0; + switch (format [cur]) { + case 'd': + count = ParseChar ('d'); + if (count > 8) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Days; + element.IntValue = count; + break; + case 'h': + count = ParseChar ('h'); + if (count > 2) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Hours; + element.IntValue = count; + break; + case 'm': + count = ParseChar ('m'); + if (count > 2) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Minutes; + element.IntValue = count; + break; + case 's': + count = ParseChar ('s'); + if (count > 2) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Seconds; + element.IntValue = count; + break; + case 'f': + count = ParseChar ('f'); + if (count > 7) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Ticks; + element.IntValue = count; + break; + case 'F': + count = ParseChar ('F'); + if (count > 7) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.TicksUppercase; + element.IntValue = count; + break; + case '%': + cur++; + if (AtEnd) + return new FormatElement (FormatElementType.Error); + if (format [cur] == 'd') + goto case 'd'; + else if (format [cur] == 'h') + goto case 'h'; + else if (format [cur] == 'm') + goto case 'm'; + else if (format [cur] == 's') + goto case 's'; + else if (format [cur] == 'f') + goto case 'f'; + else if (format [cur] == 'F') + goto case 'F'; + + return new FormatElement (FormatElementType.Error); + case '\'': + string literal = ParseLiteral (); + if (literal == null) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.Literal; + element.StringValue = literal; + break; + case '\\': + char escaped_char = ParseEscapedChar (); + if ((int)escaped_char == 0) + return new FormatElement (FormatElementType.Error); + element.Type = FormatElementType.EscapedChar; + element.CharValue = escaped_char; + break; + default: + return new FormatElement (FormatElementType.Error); + } + + return element; + } + + int ParseChar (char c) + { + int count = 0; + + while (!AtEnd && format [cur] == c) { + cur++; + count++; + } + + return count; + } + + char ParseEscapedChar () + { + if (AtEnd || format [cur] != '\\') + return (char)0; + + cur++; + if (AtEnd) + return (char)0; + + return format [cur++]; + } + + string ParseLiteral () + { + int start; + int count = 0; + + if (AtEnd || format [cur] != '\'') + return null; + + start = ++cur; + while (!AtEnd && format [cur] != '\'') { + cur++; + count++; + } + + if (!AtEnd && format [cur] == '\'') { + cur++; + return format.Substring (start, count); + } + + return null; + } } +#endif + } }