// Duco Fijma (duco@lorentz.xs4all.nl)
// Andreas Nahr (ClassDevelopment@A-SoftTech.com)
// Sebastien Pouliot <sebastien@ximian.com>
+// 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
[Serializable]
[System.Runtime.InteropServices.ComVisible (true)]
public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
+#if NET_4_0
+ , IFormattable
+#endif
{
#if MONOTOUCH
static TimeSpan () {
value = (value * (tickMultiplicator / TicksPerMillisecond));
checked {
- long val = (long) Math.Round(value);
+ long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
return new TimeSpan (val * TicksPerMillisecond);
}
}
}
#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)
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;
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)
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)
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;
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)
{
while (!AtEnd && Char.IsDigit (_src, _cur)) {
res = res * 10 + _src[_cur] - '0';
if (res > Int32.MaxValue) {
- overflowError = true;
+ SetParseError (ParseError.Overflow);
break;
}
_cur++;
}
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;
}
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)
{
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;
}
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;
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 {
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;
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
+
}
}