181b81b4a4
Former-commit-id: cf92446697332992ec36726e78eb8703e1f259d7
1553 lines
38 KiB
C#
1553 lines
38 KiB
C#
//
|
|
// System.TimeSpan.cs
|
|
//
|
|
// Authors:
|
|
// 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
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Globalization;
|
|
|
|
namespace System
|
|
{
|
|
[Serializable]
|
|
[System.Runtime.InteropServices.ComVisible (true)]
|
|
public struct TimeSpan : IComparable, IComparable<TimeSpan>, IEquatable <TimeSpan>
|
|
#if NET_4_0
|
|
, IFormattable
|
|
#endif
|
|
{
|
|
#if MONOTOUCH
|
|
static TimeSpan () {
|
|
if (MonoTouchAOTHelper.FalseFlag) {
|
|
var comparer = new System.Collections.Generic.GenericComparer <TimeSpan> ();
|
|
var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <TimeSpan> ();
|
|
}
|
|
}
|
|
#endif
|
|
public static readonly TimeSpan MaxValue = new TimeSpan (long.MaxValue);
|
|
public static readonly TimeSpan MinValue = new TimeSpan (long.MinValue);
|
|
public static readonly TimeSpan Zero = new TimeSpan (0L);
|
|
|
|
public const long TicksPerDay = 864000000000L;
|
|
public const long TicksPerHour = 36000000000L;
|
|
public const long TicksPerMillisecond = 10000L;
|
|
public const long TicksPerMinute = 600000000L;
|
|
public const long TicksPerSecond = 10000000L;
|
|
|
|
private long _ticks;
|
|
|
|
public TimeSpan (long ticks)
|
|
{
|
|
_ticks = ticks;
|
|
}
|
|
|
|
public TimeSpan (int hours, int minutes, int seconds)
|
|
{
|
|
CalculateTicks (0, hours, minutes, seconds, 0, true, out _ticks);
|
|
}
|
|
|
|
public TimeSpan (int days, int hours, int minutes, int seconds)
|
|
{
|
|
CalculateTicks (days, hours, minutes, seconds, 0, true, out _ticks);
|
|
}
|
|
|
|
public TimeSpan (int days, int hours, int minutes, int seconds, int milliseconds)
|
|
{
|
|
CalculateTicks (days, hours, minutes, seconds, milliseconds, true, out _ticks);
|
|
}
|
|
|
|
internal static bool CalculateTicks (int days, int hours, int minutes, int seconds, int milliseconds, bool throwExc, out long result)
|
|
{
|
|
// there's no overflow checks for hours, minutes, ...
|
|
// so big hours/minutes values can overflow at some point and change expected values
|
|
int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
|
|
int minsec = (minutes * 60);
|
|
long t = ((long)(hrssec + minsec + seconds) * 1000L + (long)milliseconds);
|
|
t *= 10000;
|
|
|
|
result = 0;
|
|
|
|
bool overflow = false;
|
|
// days is problematic because it can overflow but that overflow can be
|
|
// "legal" (i.e. temporary) (e.g. if other parameters are negative) or
|
|
// illegal (e.g. sign change).
|
|
if (days > 0) {
|
|
long td = TicksPerDay * days;
|
|
if (t < 0) {
|
|
long ticks = t;
|
|
t += td;
|
|
// positive days -> total ticks should be lower
|
|
overflow = (ticks > t);
|
|
}
|
|
else {
|
|
t += td;
|
|
// positive + positive != negative result
|
|
overflow = (t < 0);
|
|
}
|
|
}
|
|
else if (days < 0) {
|
|
long td = TicksPerDay * days;
|
|
if (t <= 0) {
|
|
t += td;
|
|
// negative + negative != positive result
|
|
overflow = (t > 0);
|
|
}
|
|
else {
|
|
long ticks = t;
|
|
t += td;
|
|
// negative days -> total ticks should be lower
|
|
overflow = (t > ticks);
|
|
}
|
|
}
|
|
|
|
if (overflow) {
|
|
if (throwExc)
|
|
throw new ArgumentOutOfRangeException (Locale.GetText ("The timespan is too big or too small."));
|
|
return false;
|
|
}
|
|
|
|
result = t;
|
|
return true;
|
|
}
|
|
|
|
public int Days {
|
|
get {
|
|
return (int) (_ticks / TicksPerDay);
|
|
}
|
|
}
|
|
|
|
public int Hours {
|
|
get {
|
|
return (int) (_ticks % TicksPerDay / TicksPerHour);
|
|
}
|
|
}
|
|
|
|
public int Milliseconds {
|
|
get {
|
|
return (int) (_ticks % TicksPerSecond / TicksPerMillisecond);
|
|
}
|
|
}
|
|
|
|
public int Minutes {
|
|
get {
|
|
return (int) (_ticks % TicksPerHour / TicksPerMinute);
|
|
}
|
|
}
|
|
|
|
public int Seconds {
|
|
get {
|
|
return (int) (_ticks % TicksPerMinute / TicksPerSecond);
|
|
}
|
|
}
|
|
|
|
public long Ticks {
|
|
get {
|
|
return _ticks;
|
|
}
|
|
}
|
|
|
|
public double TotalDays {
|
|
get {
|
|
return (double) _ticks / TicksPerDay;
|
|
}
|
|
}
|
|
|
|
public double TotalHours {
|
|
get {
|
|
return (double) _ticks / TicksPerHour;
|
|
}
|
|
}
|
|
|
|
public double TotalMilliseconds {
|
|
get {
|
|
return (double) _ticks / TicksPerMillisecond;
|
|
}
|
|
}
|
|
|
|
public double TotalMinutes {
|
|
get {
|
|
return (double) _ticks / TicksPerMinute;
|
|
}
|
|
}
|
|
|
|
public double TotalSeconds {
|
|
get {
|
|
return (double) _ticks / TicksPerSecond;
|
|
}
|
|
}
|
|
|
|
public TimeSpan Add (TimeSpan ts)
|
|
{
|
|
try {
|
|
checked {
|
|
return new TimeSpan (_ticks + ts.Ticks);
|
|
}
|
|
}
|
|
catch (OverflowException) {
|
|
throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
|
|
}
|
|
}
|
|
|
|
public static int Compare (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
if (t1._ticks < t2._ticks)
|
|
return -1;
|
|
if (t1._ticks > t2._ticks)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
public int CompareTo (object value)
|
|
{
|
|
if (value == null)
|
|
return 1;
|
|
|
|
if (!(value is TimeSpan)) {
|
|
throw new ArgumentException (Locale.GetText ("Argument has to be a TimeSpan."), "value");
|
|
}
|
|
|
|
return Compare (this, (TimeSpan) value);
|
|
}
|
|
|
|
public int CompareTo (TimeSpan value)
|
|
{
|
|
return Compare (this, value);
|
|
}
|
|
|
|
public bool Equals (TimeSpan obj)
|
|
{
|
|
return obj._ticks == _ticks;
|
|
}
|
|
|
|
public TimeSpan Duration ()
|
|
{
|
|
try {
|
|
checked {
|
|
return new TimeSpan (Math.Abs (_ticks));
|
|
}
|
|
}
|
|
catch (OverflowException) {
|
|
throw new OverflowException (Locale.GetText (
|
|
"This TimeSpan value is MinValue so you cannot get the duration."));
|
|
}
|
|
}
|
|
|
|
public override bool Equals (object value)
|
|
{
|
|
if (!(value is TimeSpan))
|
|
return false;
|
|
|
|
return _ticks == ((TimeSpan) value)._ticks;
|
|
}
|
|
|
|
public static bool Equals (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks == t2._ticks;
|
|
}
|
|
|
|
public static TimeSpan FromDays (double value)
|
|
{
|
|
return From (value, TicksPerDay);
|
|
}
|
|
|
|
public static TimeSpan FromHours (double value)
|
|
{
|
|
return From (value, TicksPerHour);
|
|
}
|
|
|
|
public static TimeSpan FromMinutes (double value)
|
|
{
|
|
return From (value, TicksPerMinute);
|
|
}
|
|
|
|
public static TimeSpan FromSeconds (double value)
|
|
{
|
|
return From (value, TicksPerSecond);
|
|
}
|
|
|
|
public static TimeSpan FromMilliseconds (double value)
|
|
{
|
|
return From (value, TicksPerMillisecond);
|
|
}
|
|
|
|
private static TimeSpan From (double value, long tickMultiplicator)
|
|
{
|
|
if (Double.IsNaN (value))
|
|
throw new ArgumentException (Locale.GetText ("Value cannot be NaN."), "value");
|
|
if (Double.IsNegativeInfinity (value) || Double.IsPositiveInfinity (value) ||
|
|
(value < MinValue.Ticks) || (value > MaxValue.Ticks))
|
|
throw new OverflowException (Locale.GetText ("Outside range [MinValue,MaxValue]"));
|
|
|
|
try {
|
|
value = (value * (tickMultiplicator / TicksPerMillisecond));
|
|
|
|
checked {
|
|
long val = (long) Math.Round(value, MidpointRounding.AwayFromZero);
|
|
return new TimeSpan (val * TicksPerMillisecond);
|
|
}
|
|
}
|
|
catch (OverflowException) {
|
|
throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
|
|
}
|
|
}
|
|
|
|
public static TimeSpan FromTicks (long value)
|
|
{
|
|
return new TimeSpan (value);
|
|
}
|
|
|
|
public override int GetHashCode ()
|
|
{
|
|
return _ticks.GetHashCode ();
|
|
}
|
|
|
|
public TimeSpan Negate ()
|
|
{
|
|
if (_ticks == MinValue._ticks)
|
|
throw new OverflowException (Locale.GetText (
|
|
"This TimeSpan value is MinValue and cannot be negated."));
|
|
return new TimeSpan (-_ticks);
|
|
}
|
|
|
|
public static TimeSpan Parse (string s)
|
|
{
|
|
if (s == null) {
|
|
throw new ArgumentNullException ("s");
|
|
}
|
|
|
|
TimeSpan result;
|
|
Parser p = new Parser (s);
|
|
p.Execute (false, out result);
|
|
return result;
|
|
}
|
|
|
|
public static bool TryParse (string s, out TimeSpan result)
|
|
{
|
|
if (s == null) {
|
|
result = TimeSpan.Zero;
|
|
return false;
|
|
}
|
|
|
|
Parser p = new Parser (s);
|
|
return p.Execute (true, out result);
|
|
}
|
|
|
|
#if NET_4_0
|
|
public static TimeSpan Parse (string input, IFormatProvider formatProvider)
|
|
{
|
|
if (input == null)
|
|
throw new ArgumentNullException ("input");
|
|
|
|
TimeSpan result;
|
|
Parser p = new Parser (input, formatProvider);
|
|
p.Execute (false, out result);
|
|
return result;
|
|
}
|
|
|
|
public static bool TryParse (string input, IFormatProvider formatProvider, out TimeSpan result)
|
|
{
|
|
if (string.IsNullOrEmpty (input)) {
|
|
result = TimeSpan.Zero;
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
try {
|
|
checked {
|
|
return new TimeSpan (_ticks - ts.Ticks);
|
|
}
|
|
}
|
|
catch (OverflowException) {
|
|
throw new OverflowException (Locale.GetText ("Resulting timespan is too big."));
|
|
}
|
|
}
|
|
|
|
public override string ToString ()
|
|
{
|
|
StringBuilder sb = new StringBuilder (14);
|
|
|
|
if (_ticks < 0)
|
|
sb.Append ('-');
|
|
|
|
// We need to take absolute values of all components.
|
|
// Can't handle negative timespans by negating the TimeSpan
|
|
// as a whole. This would lead to an overflow for the
|
|
// degenerate case "TimeSpan.MinValue.ToString()".
|
|
if (Days != 0) {
|
|
sb.Append (Math.Abs (Days));
|
|
sb.Append ('.');
|
|
}
|
|
|
|
sb.Append (Math.Abs (Hours).ToString ("D2"));
|
|
sb.Append (':');
|
|
sb.Append (Math.Abs (Minutes).ToString ("D2"));
|
|
sb.Append (':');
|
|
sb.Append (Math.Abs (Seconds).ToString ("D2"));
|
|
|
|
int fractional = (int) Math.Abs (_ticks % TicksPerSecond);
|
|
if (fractional != 0) {
|
|
sb.Append ('.');
|
|
sb.Append (fractional.ToString ("D7"));
|
|
}
|
|
|
|
return sb.ToString ();
|
|
}
|
|
|
|
#if NET_4_0
|
|
public string ToString (string format)
|
|
{
|
|
return ToString (format, null);
|
|
}
|
|
|
|
public string ToString (string format, IFormatProvider formatProvider)
|
|
{
|
|
if (format == null || format.Length == 0 || format == "c" ||
|
|
format == "t" || format == "T") // Default version
|
|
return ToString ();
|
|
|
|
if (format != "g" && format != "G")
|
|
return ToStringCustom (format); // custom formats ignore culture/formatProvider
|
|
|
|
NumberFormatInfo number_info = null;
|
|
if (formatProvider != null)
|
|
number_info = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
|
|
if (number_info == null)
|
|
number_info = Thread.CurrentThread.CurrentCulture.NumberFormat;
|
|
|
|
string decimal_separator = number_info.NumberDecimalSeparator;
|
|
int days, hours, minutes, seconds, milliseconds, fractional;
|
|
|
|
days = Math.Abs (Days);
|
|
hours = Math.Abs (Hours);
|
|
minutes = Math.Abs (Minutes);
|
|
seconds = Math.Abs (Seconds);
|
|
milliseconds = Math.Abs (Milliseconds);
|
|
fractional = (int) Math.Abs (_ticks % TicksPerSecond);
|
|
|
|
// Set Capacity depending on whether it's long or shot format
|
|
StringBuilder sb = new StringBuilder (format == "g" ? 16 : 32);
|
|
if (_ticks < 0)
|
|
sb.Append ('-');
|
|
|
|
switch (format) {
|
|
case "g": // short version
|
|
if (days != 0) {
|
|
sb.Append (days.ToString ());
|
|
sb.Append (':');
|
|
}
|
|
sb.Append (hours.ToString ());
|
|
sb.Append (':');
|
|
sb.Append (minutes.ToString ("D2"));
|
|
sb.Append (':');
|
|
sb.Append (seconds.ToString ("D2"));
|
|
if (milliseconds != 0) {
|
|
sb.Append (decimal_separator);
|
|
sb.Append (milliseconds.ToString ("D3"));
|
|
}
|
|
break;
|
|
case "G": // long version
|
|
sb.Append (days.ToString ("D1"));
|
|
sb.Append (':');
|
|
sb.Append (hours.ToString ("D2"));
|
|
sb.Append (':');
|
|
sb.Append (minutes.ToString ("D2"));
|
|
sb.Append (':');
|
|
sb.Append (seconds.ToString ("D2"));
|
|
sb.Append (decimal_separator);
|
|
sb.Append (fractional.ToString ("D7"));
|
|
break;
|
|
}
|
|
|
|
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:
|
|
//
|
|
// TODO: Unify with datetime ticks formatting
|
|
//
|
|
value = (int)(_ticks % TicksPerSecond);
|
|
if (value == 0) {
|
|
if (element.Type == FormatElementType.Ticks)
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
|
|
int total_length = element.IntValue;
|
|
const int max_length = 7;
|
|
int digits = max_length;
|
|
for (var dv = (int)Math.Pow (10, max_length - 1); dv > value; dv /= 10, --digits)
|
|
;
|
|
|
|
//
|
|
// Skip only leading zeros in F format
|
|
//
|
|
if (element.Type == FormatElementType.TicksUppercase && max_length - digits >= total_length)
|
|
continue;
|
|
|
|
//
|
|
// Add leading zeros
|
|
//
|
|
int leading = 0;
|
|
for (; leading < total_length && leading < max_length - digits; ++leading) {
|
|
sb.Append ("0");
|
|
}
|
|
|
|
if (total_length == leading)
|
|
continue;
|
|
|
|
//
|
|
// Remove trailing zeros
|
|
//
|
|
if (element.Type == FormatElementType.TicksUppercase) {
|
|
while (value % 10 == 0)
|
|
value /= 10;
|
|
}
|
|
|
|
var max_value = (int)Math.Pow (10, total_length - leading);
|
|
while (value >= max_value)
|
|
value /= 10;
|
|
|
|
sb.Append (value.ToString (CultureInfo.InvariantCulture));
|
|
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 t1.Add (t2);
|
|
}
|
|
|
|
public static bool operator == (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks == t2._ticks;
|
|
}
|
|
|
|
public static bool operator > (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks > t2._ticks;
|
|
}
|
|
|
|
public static bool operator >= (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks >= t2._ticks;
|
|
}
|
|
|
|
public static bool operator != (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks != t2._ticks;
|
|
}
|
|
|
|
public static bool operator < (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks < t2._ticks;
|
|
}
|
|
|
|
public static bool operator <= (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1._ticks <= t2._ticks;
|
|
}
|
|
|
|
public static TimeSpan operator - (TimeSpan t1, TimeSpan t2)
|
|
{
|
|
return t1.Subtract (t2);
|
|
}
|
|
|
|
public static TimeSpan operator - (TimeSpan t)
|
|
{
|
|
return t.Negate ();
|
|
}
|
|
|
|
public static TimeSpan operator + (TimeSpan t)
|
|
{
|
|
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;
|
|
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
|
|
// 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);
|
|
}
|
|
|
|
static NumberFormatInfo GetNumberFormatInfo (IFormatProvider formatProvider)
|
|
{
|
|
NumberFormatInfo format = null;
|
|
if (formatProvider != null)
|
|
format = formatProvider.GetFormat (typeof (NumberFormatInfo)) as NumberFormatInfo;
|
|
if (format == null)
|
|
format = Thread.CurrentThread.CurrentCulture.NumberFormat;
|
|
|
|
return format;
|
|
}
|
|
#endif
|
|
|
|
public bool AtEnd {
|
|
get {
|
|
return _cur >= _length;
|
|
}
|
|
}
|
|
|
|
// All "Parse" functions throw a FormatException on syntax error.
|
|
// Their return value is semantic value of the item parsed.
|
|
|
|
// Range checking is spread over three different places:
|
|
// 1) When parsing "int" values, an exception is thrown immediately
|
|
// when the value parsed exceeds the maximum value for an int.
|
|
// 2) An explicit check is built in that checks for hours > 23 and
|
|
// for minutes and seconds > 59.
|
|
// 3) Throwing an exceptions for a final TimeSpan value > MaxValue
|
|
// or < MinValue is left to the TimeSpan constructor called.
|
|
|
|
// Parse zero or more whitespace chars.
|
|
private void ParseWhiteSpace ()
|
|
{
|
|
while (!AtEnd && Char.IsWhiteSpace (_src, _cur)) {
|
|
_cur++;
|
|
}
|
|
}
|
|
|
|
// Parse optional sign character.
|
|
private bool ParseSign ()
|
|
{
|
|
bool res = false;
|
|
|
|
if (!AtEnd && _src[_cur] == '-') {
|
|
res = true;
|
|
_cur++;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (optional && AtEnd)
|
|
return 0;
|
|
|
|
long res = 0;
|
|
int count = 0;
|
|
|
|
while (!AtEnd && Char.IsDigit (_src, _cur)) {
|
|
res = res * 10 + _src[_cur] - '0';
|
|
if (res > Int32.MaxValue) {
|
|
SetParseError (ParseError.Overflow);
|
|
break;
|
|
}
|
|
_cur++;
|
|
count++;
|
|
}
|
|
|
|
if (!optional && (count == 0))
|
|
SetParseError (ParseError.Format);
|
|
#if NET_4_0
|
|
if (count > 0)
|
|
parsed_numbers_count++;
|
|
#endif
|
|
|
|
return (int)res;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
// Just as ParseOptDot, but for decimal separator
|
|
private bool ParseOptDecimalSeparator ()
|
|
{
|
|
if (AtEnd)
|
|
return false;
|
|
|
|
// 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;
|
|
}
|
|
|
|
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 (!AtEnd) {
|
|
if (_src[_cur] == ':')
|
|
_cur++;
|
|
else if (!optional)
|
|
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;
|
|
long res = 0;
|
|
bool digitseen = false;
|
|
|
|
while (mag > 0 && !AtEnd && Char.IsDigit (_src, _cur)) {
|
|
res = res + (_src[_cur] - '0') * mag;
|
|
_cur++;
|
|
mag = mag / 10;
|
|
digitseen = true;
|
|
}
|
|
|
|
if (!digitseen)
|
|
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;
|
|
int days;
|
|
int hours = 0;
|
|
int minutes;
|
|
int seconds;
|
|
long ticks;
|
|
|
|
result = TimeSpan.Zero;
|
|
|
|
// documented as...
|
|
// Parse [ws][-][dd.]hh:mm:ss[.ff][ws]
|
|
// ... but not entirely true as an lonely
|
|
// integer will be parsed as a number of days
|
|
ParseWhiteSpace ();
|
|
sign = ParseSign ();
|
|
days = ParseInt (false);
|
|
if (ParseOptDot ()) {
|
|
hours = ParseInt (true);
|
|
}
|
|
else if (!AtEnd) {
|
|
hours = days;
|
|
days = 0;
|
|
}
|
|
ParseColon(false);
|
|
int p = _cur;
|
|
minutes = ParseInt (true);
|
|
seconds = 0;
|
|
if (p < _cur) {
|
|
ParseColon (true);
|
|
seconds = ParseInt (true);
|
|
}
|
|
|
|
if ( ParseOptDot () ) {
|
|
ticks = ParseTicks ();
|
|
}
|
|
else {
|
|
ticks = 0;
|
|
}
|
|
ParseWhiteSpace ();
|
|
|
|
if (!AtEnd)
|
|
SetParseError (ParseError.Format);
|
|
|
|
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;
|
|
}
|
|
|
|
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 ((style == TimeSpanStyles.AssumeNegative) ? (-t - ticks) : (t + ticks));
|
|
} catch (OverflowException) {
|
|
if (tryParse)
|
|
return false;
|
|
throw;
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
}
|