994 lines
40 KiB
C#
994 lines
40 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="XsdDuration.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
// <owner current="true" primary="true">[....]</owner>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.Xml.Schema {
|
||
|
using System;
|
||
|
using System.Xml;
|
||
|
using System.Diagnostics;
|
||
|
using System.Text;
|
||
|
|
||
|
/// <summary>
|
||
|
/// This enum specifies what format should be used when converting string to XsdDateTime
|
||
|
/// </summary>
|
||
|
[Flags]
|
||
|
internal enum XsdDateTimeFlags {
|
||
|
DateTime = 0x01,
|
||
|
Time = 0x02,
|
||
|
Date = 0x04,
|
||
|
GYearMonth = 0x08,
|
||
|
GYear = 0x10,
|
||
|
GMonthDay = 0x20,
|
||
|
GDay = 0x40,
|
||
|
GMonth = 0x80,
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
XdrDateTimeNoTz = 0x100,
|
||
|
XdrDateTime = 0x200,
|
||
|
XdrTimeNoTz = 0x400, //XDRTime with tz is the same as xsd:time
|
||
|
#endif
|
||
|
AllXsd = 0xFF //All still does not include the XDR formats
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This structure extends System.DateTime to support timeInTicks zone and Gregorian types scomponents of an Xsd Duration. It is used internally to support Xsd durations without loss
|
||
|
/// of fidelity. XsdDuration structures are immutable once they've been created.
|
||
|
/// </summary>
|
||
|
internal struct XsdDateTime {
|
||
|
// DateTime is being used as an internal representation only
|
||
|
// Casting XsdDateTime to DateTime might return a different value
|
||
|
private DateTime dt;
|
||
|
|
||
|
// Additional information that DateTime is not preserving
|
||
|
// Information is stored in the following format:
|
||
|
// Bits Info
|
||
|
// 31-24 DateTimeTypeCode
|
||
|
// 23-16 XsdDateTimeKind
|
||
|
// 15-8 Zone Hours
|
||
|
// 7-0 Zone Minutes
|
||
|
private uint extra;
|
||
|
|
||
|
|
||
|
// Subset of XML Schema types XsdDateTime represents
|
||
|
enum DateTimeTypeCode {
|
||
|
DateTime,
|
||
|
Time,
|
||
|
Date,
|
||
|
GYearMonth,
|
||
|
GYear,
|
||
|
GMonthDay,
|
||
|
GDay,
|
||
|
GMonth,
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
XdrDateTime,
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Internal representation of DateTimeKind
|
||
|
enum XsdDateTimeKind {
|
||
|
Unspecified,
|
||
|
Zulu,
|
||
|
LocalWestOfZulu, // GMT-1..14, N..Y
|
||
|
LocalEastOfZulu // GMT+1..14, A..M
|
||
|
}
|
||
|
|
||
|
// Masks and shifts used for packing and unpacking extra
|
||
|
private const uint TypeMask = 0xFF000000;
|
||
|
private const uint KindMask = 0x00FF0000;
|
||
|
private const uint ZoneHourMask = 0x0000FF00;
|
||
|
private const uint ZoneMinuteMask = 0x000000FF;
|
||
|
private const int TypeShift = 24;
|
||
|
private const int KindShift = 16;
|
||
|
private const int ZoneHourShift = 8;
|
||
|
|
||
|
// Maximum number of fraction digits;
|
||
|
private const short maxFractionDigits = 7;
|
||
|
|
||
|
static readonly int Lzyyyy = "yyyy".Length;
|
||
|
static readonly int Lzyyyy_ = "yyyy-".Length;
|
||
|
static readonly int Lzyyyy_MM = "yyyy-MM".Length;
|
||
|
static readonly int Lzyyyy_MM_ = "yyyy-MM-".Length;
|
||
|
static readonly int Lzyyyy_MM_dd = "yyyy-MM-dd".Length;
|
||
|
static readonly int Lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
|
||
|
static readonly int LzHH = "HH".Length;
|
||
|
static readonly int LzHH_ = "HH:".Length;
|
||
|
static readonly int LzHH_mm = "HH:mm".Length;
|
||
|
static readonly int LzHH_mm_ = "HH:mm:".Length;
|
||
|
static readonly int LzHH_mm_ss = "HH:mm:ss".Length;
|
||
|
static readonly int Lz_ = "-".Length;
|
||
|
static readonly int Lz_zz = "-zz".Length;
|
||
|
static readonly int Lz_zz_ = "-zz:".Length;
|
||
|
static readonly int Lz_zz_zz = "-zz:zz".Length;
|
||
|
static readonly int Lz__ = "--".Length;
|
||
|
static readonly int Lz__mm = "--MM".Length;
|
||
|
static readonly int Lz__mm_ = "--MM-".Length;
|
||
|
static readonly int Lz__mm__ = "--MM--".Length;
|
||
|
static readonly int Lz__mm_dd = "--MM-dd".Length;
|
||
|
static readonly int Lz___ = "---".Length;
|
||
|
static readonly int Lz___dd = "---dd".Length;
|
||
|
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
/// <summary>
|
||
|
/// Constructs an XsdDateTime from a string trying all possible formats.
|
||
|
/// </summary>
|
||
|
public XsdDateTime(string text) : this(text, XsdDateTimeFlags.AllXsd) {
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs an XsdDateTime from a string using specific format.
|
||
|
/// </summary>
|
||
|
public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() {
|
||
|
Parser parser = new Parser();
|
||
|
if (! parser.Parse(text, kinds)) {
|
||
|
throw new FormatException(Res.GetString(Res.XmlConvert_BadFormat, text, kinds));
|
||
|
}
|
||
|
InitiateXsdDateTime(parser);
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
private XsdDateTime(Parser parser) : this() {
|
||
|
InitiateXsdDateTime(parser);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
private void InitiateXsdDateTime(Parser parser) {
|
||
|
dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second);
|
||
|
if (parser.fraction != 0) {
|
||
|
dt = dt.AddTicks(parser.fraction);
|
||
|
}
|
||
|
extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute);
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
internal static bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result) {
|
||
|
Parser parser = new Parser();
|
||
|
if (! parser.Parse(text, kinds)) {
|
||
|
result = new XsdDateTime();
|
||
|
return false;
|
||
|
}
|
||
|
result = new XsdDateTime(parser);
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs an XsdDateTime from a DateTime.
|
||
|
/// </summary>
|
||
|
public XsdDateTime(DateTime dateTime, XsdDateTimeFlags kinds) {
|
||
|
Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
|
||
|
dt = dateTime;
|
||
|
|
||
|
DateTimeTypeCode code = (DateTimeTypeCode) (Bits.LeastPosition((uint) kinds) - 1);
|
||
|
int zoneHour = 0;
|
||
|
int zoneMinute = 0;
|
||
|
XsdDateTimeKind kind;
|
||
|
|
||
|
switch (dateTime.Kind) {
|
||
|
case DateTimeKind.Unspecified: kind = XsdDateTimeKind.Unspecified; break;
|
||
|
case DateTimeKind.Utc: kind = XsdDateTimeKind.Zulu; break;
|
||
|
|
||
|
default: {
|
||
|
Debug.Assert(dateTime.Kind == DateTimeKind.Local, "Unknown DateTimeKind: " + dateTime.Kind);
|
||
|
TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
|
||
|
|
||
|
if (utcOffset.Ticks < 0) {
|
||
|
kind = XsdDateTimeKind.LocalWestOfZulu;
|
||
|
zoneHour = -utcOffset.Hours;
|
||
|
zoneMinute = -utcOffset.Minutes;
|
||
|
}
|
||
|
else {
|
||
|
kind = XsdDateTimeKind.LocalEastOfZulu;
|
||
|
zoneHour = utcOffset.Hours;
|
||
|
zoneMinute = utcOffset.Minutes;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneHour << ZoneHourShift) | zoneMinute);
|
||
|
}
|
||
|
|
||
|
// Constructs an XsdDateTime from a DateTimeOffset
|
||
|
public XsdDateTime(DateTimeOffset dateTimeOffset) : this(dateTimeOffset, XsdDateTimeFlags.DateTime) {
|
||
|
}
|
||
|
|
||
|
public XsdDateTime(DateTimeOffset dateTimeOffset, XsdDateTimeFlags kinds) {
|
||
|
Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
|
||
|
|
||
|
dt = dateTimeOffset.DateTime;
|
||
|
|
||
|
TimeSpan zoneOffset = dateTimeOffset.Offset;
|
||
|
DateTimeTypeCode code = (DateTimeTypeCode) (Bits.LeastPosition((uint) kinds) - 1);
|
||
|
XsdDateTimeKind kind;
|
||
|
if (zoneOffset.TotalMinutes < 0) {
|
||
|
zoneOffset = zoneOffset.Negate();
|
||
|
kind = XsdDateTimeKind.LocalWestOfZulu;
|
||
|
}
|
||
|
else if (zoneOffset.TotalMinutes > 0) {
|
||
|
kind = XsdDateTimeKind.LocalEastOfZulu;
|
||
|
}
|
||
|
else {
|
||
|
kind = XsdDateTimeKind.Zulu;
|
||
|
}
|
||
|
|
||
|
extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneOffset.Hours << ZoneHourShift) | zoneOffset.Minutes);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns auxiliary enumeration of XSD date type
|
||
|
/// </summary>
|
||
|
private DateTimeTypeCode InternalTypeCode {
|
||
|
get { return (DateTimeTypeCode)((extra & TypeMask) >> TypeShift); }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns geographical "position" of the value
|
||
|
/// </summary>
|
||
|
private XsdDateTimeKind InternalKind {
|
||
|
get { return (XsdDateTimeKind)((extra & KindMask) >> KindShift); }
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
/// <summary>
|
||
|
/// Returns XmlTypeCode of the value being stored
|
||
|
/// </summary>
|
||
|
public XmlTypeCode TypeCode {
|
||
|
get { return typeCodes[(int)InternalTypeCode]; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns whether object represent local, UTC or unspecified time
|
||
|
/// </summary>
|
||
|
public DateTimeKind Kind {
|
||
|
get {
|
||
|
switch (InternalKind) {
|
||
|
case XsdDateTimeKind.Unspecified:
|
||
|
return DateTimeKind.Unspecified;
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
return DateTimeKind.Utc;
|
||
|
default:
|
||
|
// XsdDateTimeKind.LocalEastOfZulu:
|
||
|
// XsdDateTimeKind.LocalWestOfZulu:
|
||
|
return DateTimeKind.Local;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the year part of XsdDateTime
|
||
|
/// The returned value is integer between 1 and 9999
|
||
|
/// </summary>
|
||
|
public int Year {
|
||
|
get { return dt.Year; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the month part of XsdDateTime
|
||
|
/// The returned value is integer between 1 and 12
|
||
|
/// </summary>
|
||
|
public int Month {
|
||
|
get { return dt.Month; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the day of the month part of XsdDateTime
|
||
|
/// The returned value is integer between 1 and 31
|
||
|
/// </summary>
|
||
|
public int Day {
|
||
|
get { return dt.Day; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the hour part of XsdDateTime
|
||
|
/// The returned value is integer between 0 and 23
|
||
|
/// </summary>
|
||
|
public int Hour {
|
||
|
get { return dt.Hour; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the minute part of XsdDateTime
|
||
|
/// The returned value is integer between 0 and 60
|
||
|
/// </summary>
|
||
|
public int Minute {
|
||
|
get { return dt.Minute; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the second part of XsdDateTime
|
||
|
/// The returned value is integer between 0 and 60
|
||
|
/// </summary>
|
||
|
public int Second {
|
||
|
get { return dt.Second; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns number of ticks in the fraction of the second
|
||
|
/// The returned value is integer between 0 and 9999999
|
||
|
/// </summary>
|
||
|
public int Fraction {
|
||
|
get { return (int)(dt.Ticks - new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second).Ticks); }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the hour part of the time zone
|
||
|
/// The returned value is integer between -13 and 13
|
||
|
/// </summary>
|
||
|
public int ZoneHour {
|
||
|
get {
|
||
|
uint result = (extra & ZoneHourMask) >> ZoneHourShift;
|
||
|
return (int)result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the minute part of the time zone
|
||
|
/// The returned value is integer between 0 and 60
|
||
|
/// </summary>
|
||
|
public int ZoneMinute {
|
||
|
get {
|
||
|
uint result = (extra & ZoneMinuteMask);
|
||
|
return (int)result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
public DateTime ToZulu() {
|
||
|
switch (InternalKind) {
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
// set it to UTC
|
||
|
return new DateTime(dt.Ticks, DateTimeKind.Utc);
|
||
|
case XsdDateTimeKind.LocalEastOfZulu:
|
||
|
// Adjust to UTC and then convert to local in the current time zone
|
||
|
return new DateTime(dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
|
||
|
case XsdDateTimeKind.LocalWestOfZulu:
|
||
|
// Adjust to UTC and then convert to local in the current time zone
|
||
|
return new DateTime(dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
|
||
|
default:
|
||
|
return dt;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Cast to DateTime
|
||
|
/// The following table describes the behaviors of getting the default value
|
||
|
/// when a certain year/month/day values are missing.
|
||
|
///
|
||
|
/// An "X" means that the value exists. And "--" means that value is missing.
|
||
|
///
|
||
|
/// Year Month Day => ResultYear ResultMonth ResultDay Note
|
||
|
///
|
||
|
/// X X X Parsed year Parsed month Parsed day
|
||
|
/// X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month.
|
||
|
/// X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year.
|
||
|
/// X -- -- Parsed year First month First day If we have only the year, assume the first day of that year.
|
||
|
///
|
||
|
/// -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year.
|
||
|
/// -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day.
|
||
|
/// -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month.
|
||
|
/// -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date.
|
||
|
/// </summary>
|
||
|
public static implicit operator DateTime(XsdDateTime xdt) {
|
||
|
DateTime result;
|
||
|
switch (xdt.InternalTypeCode) {
|
||
|
case DateTimeTypeCode.GMonth:
|
||
|
case DateTimeTypeCode.GDay:
|
||
|
result = new DateTime(DateTime.Now.Year, xdt.Month, xdt.Day);
|
||
|
break;
|
||
|
case DateTimeTypeCode.Time:
|
||
|
//back to DateTime.Now
|
||
|
DateTime currentDateTime = DateTime.Now;
|
||
|
TimeSpan addDiff = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day) - new DateTime(xdt.Year, xdt.Month, xdt.Day);
|
||
|
result = xdt.dt.Add(addDiff);
|
||
|
break;
|
||
|
default:
|
||
|
result = xdt.dt;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
long ticks;
|
||
|
switch (xdt.InternalKind) {
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
// set it to UTC
|
||
|
result = new DateTime(result.Ticks, DateTimeKind.Utc);
|
||
|
break;
|
||
|
case XsdDateTimeKind.LocalEastOfZulu:
|
||
|
// Adjust to UTC and then convert to local in the current time zone
|
||
|
ticks = result.Ticks - new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0).Ticks;
|
||
|
if (ticks < DateTime.MinValue.Ticks)
|
||
|
{
|
||
|
// Underflow. Return the DateTime as local time directly
|
||
|
ticks += TimeZoneInfo.Local.GetUtcOffset(result).Ticks;
|
||
|
if (ticks < DateTime.MinValue.Ticks)
|
||
|
ticks = DateTime.MinValue.Ticks;
|
||
|
return new DateTime(ticks, DateTimeKind.Local);
|
||
|
}
|
||
|
result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
|
||
|
break;
|
||
|
case XsdDateTimeKind.LocalWestOfZulu:
|
||
|
// Adjust to UTC and then convert to local in the current time zone
|
||
|
ticks = result.Ticks + new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0).Ticks;
|
||
|
if (ticks > DateTime.MaxValue.Ticks)
|
||
|
{
|
||
|
// Overflow. Return the DateTime as local time directly
|
||
|
ticks += TimeZoneInfo.Local.GetUtcOffset(result).Ticks;
|
||
|
if (ticks > DateTime.MaxValue.Ticks)
|
||
|
ticks = DateTime.MaxValue.Ticks;
|
||
|
return new DateTime(ticks, DateTimeKind.Local);
|
||
|
}
|
||
|
result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public static implicit operator DateTimeOffset(XsdDateTime xdt) {
|
||
|
DateTime dt;
|
||
|
|
||
|
switch (xdt.InternalTypeCode) {
|
||
|
case DateTimeTypeCode.GMonth:
|
||
|
case DateTimeTypeCode.GDay:
|
||
|
dt = new DateTime( DateTime.Now.Year, xdt.Month, xdt.Day );
|
||
|
break;
|
||
|
case DateTimeTypeCode.Time:
|
||
|
//back to DateTime.Now
|
||
|
DateTime currentDateTime = DateTime.Now;
|
||
|
TimeSpan addDiff = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day) - new DateTime(xdt.Year, xdt.Month, xdt.Day);
|
||
|
dt = xdt.dt.Add( addDiff );
|
||
|
break;
|
||
|
default:
|
||
|
dt = xdt.dt;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
DateTimeOffset result;
|
||
|
switch (xdt.InternalKind) {
|
||
|
case XsdDateTimeKind.LocalEastOfZulu:
|
||
|
result = new DateTimeOffset(dt, new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0));
|
||
|
break;
|
||
|
case XsdDateTimeKind.LocalWestOfZulu:
|
||
|
result = new DateTimeOffset(dt, new TimeSpan(-xdt.ZoneHour, -xdt.ZoneMinute, 0));
|
||
|
break;
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
result = new DateTimeOffset(dt, new TimeSpan( 0 ) );
|
||
|
break;
|
||
|
case XsdDateTimeKind.Unspecified:
|
||
|
default:
|
||
|
result = new DateTimeOffset(dt, TimeZoneInfo.Local.GetUtcOffset(dt));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
/// <summary>
|
||
|
/// Compares two DateTime values, returning an integer that indicates
|
||
|
/// their relationship.
|
||
|
/// </summary>
|
||
|
public static int Compare(XsdDateTime left, XsdDateTime right) {
|
||
|
if (left.extra == right.extra) {
|
||
|
return DateTime.Compare(left.dt, right.dt);
|
||
|
}
|
||
|
else {
|
||
|
// Xsd types should be the same for it to be comparable
|
||
|
if (left.InternalTypeCode != right.InternalTypeCode) {
|
||
|
throw new ArgumentException(Res.GetString(Res.Sch_XsdDateTimeCompare, left.TypeCode, right.TypeCode));
|
||
|
}
|
||
|
// Convert both to UTC
|
||
|
return DateTime.Compare(left.GetZuluDateTime(), right.GetZuluDateTime());
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Compares this DateTime to a given object. This method provides an
|
||
|
// implementation of the IComparable interface. The object
|
||
|
// argument must be another DateTime, or otherwise an exception
|
||
|
// occurs. Null is considered less than any instance.
|
||
|
//
|
||
|
// Returns a value less than zero if this object
|
||
|
/// <include file='doc\DateTime.uex' path='docs/doc[@for="DateTime.CompareTo"]/*' />
|
||
|
public int CompareTo(Object value) {
|
||
|
if (value == null) return 1;
|
||
|
return Compare(this, (XsdDateTime)value);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Serialization to a string
|
||
|
/// </summary>
|
||
|
public override string ToString() {
|
||
|
StringBuilder sb = new StringBuilder(64);
|
||
|
char[] text;
|
||
|
switch (InternalTypeCode) {
|
||
|
case DateTimeTypeCode.DateTime:
|
||
|
PrintDate(sb);
|
||
|
sb.Append('T');
|
||
|
PrintTime(sb);
|
||
|
break;
|
||
|
case DateTimeTypeCode.Time:
|
||
|
PrintTime(sb);
|
||
|
break;
|
||
|
case DateTimeTypeCode.Date:
|
||
|
PrintDate(sb);
|
||
|
break;
|
||
|
case DateTimeTypeCode.GYearMonth:
|
||
|
text = new char[Lzyyyy_MM];
|
||
|
IntToCharArray(text, 0, Year, 4);
|
||
|
text[Lzyyyy] = '-';
|
||
|
ShortToCharArray(text, Lzyyyy_, Month);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
case DateTimeTypeCode.GYear:
|
||
|
text = new char[Lzyyyy];
|
||
|
IntToCharArray(text, 0, Year, 4);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
case DateTimeTypeCode.GMonthDay:
|
||
|
text = new char[Lz__mm_dd];
|
||
|
text[0] = '-';
|
||
|
text[Lz_] = '-';
|
||
|
ShortToCharArray(text, Lz__, Month);
|
||
|
text[Lz__mm] = '-';
|
||
|
ShortToCharArray(text, Lz__mm_, Day);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
case DateTimeTypeCode.GDay:
|
||
|
text = new char[Lz___dd];
|
||
|
text[0] = '-';
|
||
|
text[Lz_] = '-';
|
||
|
text[Lz__] = '-';
|
||
|
ShortToCharArray(text, Lz___, Day);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
case DateTimeTypeCode.GMonth:
|
||
|
text = new char[Lz__mm__];
|
||
|
text[0] = '-';
|
||
|
text[Lz_] = '-';
|
||
|
ShortToCharArray(text, Lz__, Month);
|
||
|
text[Lz__mm] = '-';
|
||
|
text[Lz__mm_] = '-';
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
}
|
||
|
PrintZone(sb);
|
||
|
return sb.ToString();
|
||
|
}
|
||
|
|
||
|
// Serialize year, month and day
|
||
|
private void PrintDate(StringBuilder sb) {
|
||
|
char[] text = new char[Lzyyyy_MM_dd];
|
||
|
IntToCharArray(text, 0, Year, 4);
|
||
|
text[Lzyyyy] = '-';
|
||
|
ShortToCharArray(text, Lzyyyy_, Month);
|
||
|
text[Lzyyyy_MM] = '-';
|
||
|
ShortToCharArray(text, Lzyyyy_MM_, Day);
|
||
|
sb.Append(text);
|
||
|
}
|
||
|
|
||
|
// Serialize hour, minute, second and fraction
|
||
|
private void PrintTime(StringBuilder sb) {
|
||
|
char[] text = new char[LzHH_mm_ss];
|
||
|
ShortToCharArray(text, 0, Hour);
|
||
|
text[LzHH] = ':';
|
||
|
ShortToCharArray(text, LzHH_, Minute);
|
||
|
text[LzHH_mm] = ':';
|
||
|
ShortToCharArray(text, LzHH_mm_, Second);
|
||
|
sb.Append(text);
|
||
|
int fraction = Fraction;
|
||
|
if (fraction != 0) {
|
||
|
int fractionDigits = maxFractionDigits;
|
||
|
while (fraction % 10 == 0) {
|
||
|
fractionDigits --;
|
||
|
fraction /= 10;
|
||
|
}
|
||
|
text = new char[fractionDigits + 1];
|
||
|
text[0] = '.';
|
||
|
IntToCharArray(text, 1, fraction, fractionDigits);
|
||
|
sb.Append(text);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Serialize time zone
|
||
|
private void PrintZone(StringBuilder sb) {
|
||
|
char[] text;
|
||
|
switch (InternalKind) {
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
sb.Append('Z');
|
||
|
break;
|
||
|
case XsdDateTimeKind.LocalWestOfZulu:
|
||
|
text = new char[Lz_zz_zz];
|
||
|
text[0] = '-';
|
||
|
ShortToCharArray(text, Lz_, ZoneHour);
|
||
|
text[Lz_zz] = ':';
|
||
|
ShortToCharArray(text, Lz_zz_, ZoneMinute);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
case XsdDateTimeKind.LocalEastOfZulu:
|
||
|
text = new char[Lz_zz_zz];
|
||
|
text[0] = '+';
|
||
|
ShortToCharArray(text, Lz_, ZoneHour);
|
||
|
text[Lz_zz] = ':';
|
||
|
ShortToCharArray(text, Lz_zz_, ZoneMinute);
|
||
|
sb.Append(text);
|
||
|
break;
|
||
|
default:
|
||
|
// do nothing
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Serialize integer into character array starting with index [start].
|
||
|
// Number of digits is set by [digits]
|
||
|
private void IntToCharArray(char[] text, int start, int value, int digits) {
|
||
|
while(digits -- != 0) {
|
||
|
text[start + digits] = (char)(value%10 + '0');
|
||
|
value /= 10;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Serialize two digit integer into character array starting with index [start].
|
||
|
private void ShortToCharArray(char[] text, int start, int value) {
|
||
|
text[start] = (char)(value/10 + '0');
|
||
|
text[start + 1] = (char)(value%10 + '0');
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT
|
||
|
// Auxiliary for compare.
|
||
|
// Returns UTC DateTime
|
||
|
private DateTime GetZuluDateTime() {
|
||
|
switch (InternalKind) {
|
||
|
case XsdDateTimeKind.Zulu:
|
||
|
return dt;
|
||
|
case XsdDateTimeKind.LocalEastOfZulu:
|
||
|
return dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0));
|
||
|
case XsdDateTimeKind.LocalWestOfZulu:
|
||
|
return dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0));
|
||
|
default:
|
||
|
return dt.ToUniversalTime();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
private static readonly XmlTypeCode[] typeCodes = {
|
||
|
XmlTypeCode.DateTime,
|
||
|
XmlTypeCode.Time,
|
||
|
XmlTypeCode.Date,
|
||
|
XmlTypeCode.GYearMonth,
|
||
|
XmlTypeCode.GYear,
|
||
|
XmlTypeCode.GMonthDay,
|
||
|
XmlTypeCode.GDay,
|
||
|
XmlTypeCode.GMonth
|
||
|
};
|
||
|
|
||
|
|
||
|
// Parsing string according to XML schema spec
|
||
|
struct Parser {
|
||
|
private const int leapYear = 1904;
|
||
|
private const int firstMonth = 1;
|
||
|
private const int firstDay = 1;
|
||
|
|
||
|
public DateTimeTypeCode typeCode;
|
||
|
public int year;
|
||
|
public int month;
|
||
|
public int day;
|
||
|
public int hour;
|
||
|
public int minute;
|
||
|
public int second;
|
||
|
public int fraction;
|
||
|
public XsdDateTimeKind kind;
|
||
|
public int zoneHour;
|
||
|
public int zoneMinute;
|
||
|
|
||
|
private string text;
|
||
|
private int length;
|
||
|
|
||
|
public bool Parse(string text, XsdDateTimeFlags kinds) {
|
||
|
this.text = text;
|
||
|
this.length = text.Length;
|
||
|
|
||
|
// Skip leading withitespace
|
||
|
int start = 0;
|
||
|
while(start < length && char.IsWhiteSpace(text[start])) {
|
||
|
start ++;
|
||
|
}
|
||
|
// Choose format starting from the most common and trying not to reparse the same thing too many times
|
||
|
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) {
|
||
|
#else
|
||
|
if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date)) {
|
||
|
#endif
|
||
|
if (ParseDate(start)) {
|
||
|
if (Test(kinds, XsdDateTimeFlags.DateTime)) {
|
||
|
if (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT)) {
|
||
|
typeCode = DateTimeTypeCode.DateTime;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.Date)) {
|
||
|
if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd)) {
|
||
|
typeCode = DateTimeTypeCode.Date;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) {
|
||
|
if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd) || (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT)) ) {
|
||
|
typeCode = DateTimeTypeCode.XdrDateTime;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) {
|
||
|
if (ParseChar(start + Lzyyyy_MM_dd, 'T')) {
|
||
|
if (ParseTimeAndWhitespace(start + Lzyyyy_MM_ddT)) {
|
||
|
typeCode = DateTimeTypeCode.XdrDateTime;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
typeCode = DateTimeTypeCode.XdrDateTime;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Test(kinds, XsdDateTimeFlags.Time)) {
|
||
|
if (ParseTimeAndZoneAndWhitespace(start)) { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
|
||
|
year = leapYear;
|
||
|
month = firstMonth;
|
||
|
day = firstDay;
|
||
|
typeCode = DateTimeTypeCode.Time;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) {
|
||
|
if (ParseTimeAndWhitespace(start)) { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
|
||
|
year = leapYear;
|
||
|
month = firstMonth;
|
||
|
day = firstDay;
|
||
|
typeCode = DateTimeTypeCode.Time;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) {
|
||
|
if (Parse4Dig(start , ref year) && 1 <= year) {
|
||
|
if (Test(kinds, XsdDateTimeFlags.GYearMonth)) {
|
||
|
if (
|
||
|
ParseChar(start + Lzyyyy, '-') &&
|
||
|
Parse2Dig(start + Lzyyyy_, ref month) && 1 <= month && month <= 12 &&
|
||
|
ParseZoneAndWhitespace(start + Lzyyyy_MM)
|
||
|
) {
|
||
|
day = firstDay;
|
||
|
typeCode = DateTimeTypeCode.GYearMonth;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.GYear)) {
|
||
|
if (ParseZoneAndWhitespace(start + Lzyyyy)) {
|
||
|
month = firstMonth;
|
||
|
day = firstDay;
|
||
|
typeCode = DateTimeTypeCode.GYear;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) {
|
||
|
if (
|
||
|
ParseChar(start , '-') &&
|
||
|
ParseChar(start + Lz_, '-') &&
|
||
|
Parse2Dig(start + Lz__, ref month) && 1 <= month && month <= 12
|
||
|
) {
|
||
|
if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + Lz__mm, '-')) {
|
||
|
if (
|
||
|
Parse2Dig(start + Lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) &&
|
||
|
ParseZoneAndWhitespace(start + Lz__mm_dd)
|
||
|
) {
|
||
|
year = leapYear;
|
||
|
typeCode = DateTimeTypeCode.GMonthDay;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.GMonth)) {
|
||
|
if (ParseZoneAndWhitespace(start + Lz__mm) || (ParseChar(start + Lz__mm, '-') && ParseChar(start + Lz__mm_, '-') && ParseZoneAndWhitespace(start + Lz__mm__)) ) {
|
||
|
year = leapYear;
|
||
|
day = firstDay;
|
||
|
typeCode = DateTimeTypeCode.GMonth;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
if (Test(kinds, XsdDateTimeFlags.GDay)) {
|
||
|
if (
|
||
|
ParseChar(start , '-') &&
|
||
|
ParseChar(start + Lz_, '-') &&
|
||
|
ParseChar(start + Lz__, '-') &&
|
||
|
Parse2Dig(start + Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) &&
|
||
|
ParseZoneAndWhitespace(start + Lz___dd)
|
||
|
|
||
|
) {
|
||
|
year = leapYear;
|
||
|
month = firstMonth;
|
||
|
typeCode = DateTimeTypeCode.GDay;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
private bool ParseDate(int start) {
|
||
|
return
|
||
|
Parse4Dig(start , ref year) && 1 <= year &&
|
||
|
ParseChar(start + Lzyyyy, '-') &&
|
||
|
Parse2Dig(start + Lzyyyy_, ref month) && 1 <= month && month <= 12 &&
|
||
|
ParseChar(start + Lzyyyy_MM, '-') &&
|
||
|
Parse2Dig(start + Lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month);
|
||
|
}
|
||
|
|
||
|
private bool ParseTimeAndZoneAndWhitespace(int start) {
|
||
|
if (ParseTime(ref start)) {
|
||
|
if (ParseZoneAndWhitespace(start)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
#if !SILVERLIGHT // XDR is not supported in Silverlight
|
||
|
private bool ParseTimeAndWhitespace(int start) {
|
||
|
if (ParseTime(ref start)) {
|
||
|
while(start < length ) {//&& char.IsWhiteSpace(text[start])) {
|
||
|
start ++;
|
||
|
}
|
||
|
return start == length;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int[] Power10 = new int[maxFractionDigits] {-1, 10, 100, 1000, 10000, 100000, 1000000};
|
||
|
private bool ParseTime(ref int start) {
|
||
|
if (
|
||
|
Parse2Dig(start , ref hour) && hour < 24 &&
|
||
|
ParseChar(start + LzHH, ':') &&
|
||
|
Parse2Dig(start + LzHH_, ref minute) && minute < 60 &&
|
||
|
ParseChar(start + LzHH_mm, ':') &&
|
||
|
Parse2Dig(start + LzHH_mm_, ref second) && second < 60
|
||
|
) {
|
||
|
start += LzHH_mm_ss;
|
||
|
if (ParseChar(start, '.')) {
|
||
|
// Parse factional part of seconds
|
||
|
// We allow any number of digits, but keep only first 7
|
||
|
this.fraction = 0;
|
||
|
int fractionDigits = 0;
|
||
|
int round = 0;
|
||
|
while (++start < length) {
|
||
|
int d = text[start] - '0';
|
||
|
if (9u < (uint) d) { // d < 0 || 9 < d
|
||
|
break;
|
||
|
}
|
||
|
if (fractionDigits < maxFractionDigits) {
|
||
|
this.fraction = (this.fraction * 10) + d;
|
||
|
} else if (fractionDigits == maxFractionDigits) {
|
||
|
if (5 < d) {
|
||
|
round = 1;
|
||
|
} else if (d == 5) {
|
||
|
round = -1;
|
||
|
}
|
||
|
} else if (round < 0 && d != 0) {
|
||
|
round = 1;
|
||
|
}
|
||
|
fractionDigits ++;
|
||
|
}
|
||
|
if (fractionDigits < maxFractionDigits) {
|
||
|
if (fractionDigits == 0) {
|
||
|
return false; // cannot end with .
|
||
|
}
|
||
|
fraction *= Power10[maxFractionDigits - fractionDigits];
|
||
|
} else {
|
||
|
if (round < 0) {
|
||
|
round = fraction & 1;
|
||
|
}
|
||
|
fraction += round;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
// cleanup - conflict with gYear
|
||
|
hour = 0;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool ParseZoneAndWhitespace(int start) {
|
||
|
if (start < length) {
|
||
|
char ch = text[start];
|
||
|
if (ch == 'Z' || ch == 'z') {
|
||
|
kind = XsdDateTimeKind.Zulu;
|
||
|
start ++;
|
||
|
}
|
||
|
else if (start + 5 < length) {
|
||
|
if (
|
||
|
Parse2Dig(start + Lz_, ref zoneHour) && zoneHour <= 99 &&
|
||
|
ParseChar(start + Lz_zz, ':') &&
|
||
|
Parse2Dig(start + Lz_zz_, ref zoneMinute) && zoneMinute <= 99
|
||
|
) {
|
||
|
if (ch == '-') {
|
||
|
kind = XsdDateTimeKind.LocalWestOfZulu;
|
||
|
start += Lz_zz_zz;
|
||
|
}
|
||
|
else if (ch == '+') {
|
||
|
kind = XsdDateTimeKind.LocalEastOfZulu;
|
||
|
start += Lz_zz_zz;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
while(start < length && char.IsWhiteSpace(text[start])) {
|
||
|
start ++;
|
||
|
}
|
||
|
return start == length;
|
||
|
}
|
||
|
|
||
|
|
||
|
private bool Parse4Dig(int start, ref int num) {
|
||
|
if (start + 3 < length) {
|
||
|
int d4 = text[start] - '0';
|
||
|
int d3 = text[start + 1] - '0';
|
||
|
int d2 = text[start + 2] - '0';
|
||
|
int d1 = text[start + 3] - '0';
|
||
|
if (0 <= d4 && d4 < 10 &&
|
||
|
0 <= d3 && d3 < 10 &&
|
||
|
0 <= d2 && d2 < 10 &&
|
||
|
0 <= d1 && d1 < 10
|
||
|
) {
|
||
|
num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool Parse2Dig(int start, ref int num) {
|
||
|
if (start + 1 < length) {
|
||
|
int d2 = text[start] - '0';
|
||
|
int d1 = text[start + 1] - '0';
|
||
|
if (0 <= d2 && d2 < 10 &&
|
||
|
0 <= d1 && d1 < 10
|
||
|
) {
|
||
|
num = d2 * 10 + d1;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool ParseChar(int start, char ch) {
|
||
|
return start < length && text[start] == ch;
|
||
|
}
|
||
|
|
||
|
private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) {
|
||
|
return (left & right) != 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|