2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="XsdDuration.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
2017-08-21 15:34:15 +00:00
// <owner current="true" primary="true">Microsoft</owner>
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
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 ( 9 u < ( 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 ;
}
}
}
}