1 //------------------------------------------------------------------------------
2 // <copyright file="XsdDuration.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Xml.Schema {
11 using System.Diagnostics;
15 /// This enum specifies what format should be used when converting string to XsdDateTime
18 internal enum XsdDateTimeFlags {
27 #if !SILVERLIGHT // XDR is not supported in Silverlight
28 XdrDateTimeNoTz = 0x100,
30 XdrTimeNoTz = 0x400, //XDRTime with tz is the same as xsd:time
32 AllXsd = 0xFF //All still does not include the XDR formats
36 /// 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
37 /// of fidelity. XsdDuration structures are immutable once they've been created.
39 internal struct XsdDateTime {
40 // DateTime is being used as an internal representation only
41 // Casting XsdDateTime to DateTime might return a different value
44 // Additional information that DateTime is not preserving
45 // Information is stored in the following format:
47 // 31-24 DateTimeTypeCode
48 // 23-16 XsdDateTimeKind
54 // Subset of XML Schema types XsdDateTime represents
55 enum DateTimeTypeCode {
64 #if !SILVERLIGHT // XDR is not supported in Silverlight
69 // Internal representation of DateTimeKind
70 enum XsdDateTimeKind {
73 LocalWestOfZulu, // GMT-1..14, N..Y
74 LocalEastOfZulu // GMT+1..14, A..M
77 // Masks and shifts used for packing and unpacking extra
78 private const uint TypeMask = 0xFF000000;
79 private const uint KindMask = 0x00FF0000;
80 private const uint ZoneHourMask = 0x0000FF00;
81 private const uint ZoneMinuteMask = 0x000000FF;
82 private const int TypeShift = 24;
83 private const int KindShift = 16;
84 private const int ZoneHourShift = 8;
86 // Maximum number of fraction digits;
87 private const short maxFractionDigits = 7;
89 static readonly int Lzyyyy = "yyyy".Length;
90 static readonly int Lzyyyy_ = "yyyy-".Length;
91 static readonly int Lzyyyy_MM = "yyyy-MM".Length;
92 static readonly int Lzyyyy_MM_ = "yyyy-MM-".Length;
93 static readonly int Lzyyyy_MM_dd = "yyyy-MM-dd".Length;
94 static readonly int Lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
95 static readonly int LzHH = "HH".Length;
96 static readonly int LzHH_ = "HH:".Length;
97 static readonly int LzHH_mm = "HH:mm".Length;
98 static readonly int LzHH_mm_ = "HH:mm:".Length;
99 static readonly int LzHH_mm_ss = "HH:mm:ss".Length;
100 static readonly int Lz_ = "-".Length;
101 static readonly int Lz_zz = "-zz".Length;
102 static readonly int Lz_zz_ = "-zz:".Length;
103 static readonly int Lz_zz_zz = "-zz:zz".Length;
104 static readonly int Lz__ = "--".Length;
105 static readonly int Lz__mm = "--MM".Length;
106 static readonly int Lz__mm_ = "--MM-".Length;
107 static readonly int Lz__mm__ = "--MM--".Length;
108 static readonly int Lz__mm_dd = "--MM-dd".Length;
109 static readonly int Lz___ = "---".Length;
110 static readonly int Lz___dd = "---dd".Length;
115 /// Constructs an XsdDateTime from a string trying all possible formats.
117 public XsdDateTime(string text) : this(text, XsdDateTimeFlags.AllXsd) {
122 /// Constructs an XsdDateTime from a string using specific format.
124 public XsdDateTime(string text, XsdDateTimeFlags kinds) : this() {
125 Parser parser = new Parser();
126 if (! parser.Parse(text, kinds)) {
127 throw new FormatException(Res.GetString(Res.XmlConvert_BadFormat, text, kinds));
129 InitiateXsdDateTime(parser);
133 private XsdDateTime(Parser parser) : this() {
134 InitiateXsdDateTime(parser);
138 private void InitiateXsdDateTime(Parser parser) {
139 dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second);
140 if (parser.fraction != 0) {
141 dt = dt.AddTicks(parser.fraction);
143 extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute);
147 internal static bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result) {
148 Parser parser = new Parser();
149 if (! parser.Parse(text, kinds)) {
150 result = new XsdDateTime();
153 result = new XsdDateTime(parser);
159 /// Constructs an XsdDateTime from a DateTime.
161 public XsdDateTime(DateTime dateTime, XsdDateTimeFlags kinds) {
162 Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
165 DateTimeTypeCode code = (DateTimeTypeCode) (Bits.LeastPosition((uint) kinds) - 1);
168 XsdDateTimeKind kind;
170 switch (dateTime.Kind) {
171 case DateTimeKind.Unspecified: kind = XsdDateTimeKind.Unspecified; break;
172 case DateTimeKind.Utc: kind = XsdDateTimeKind.Zulu; break;
175 Debug.Assert(dateTime.Kind == DateTimeKind.Local, "Unknown DateTimeKind: " + dateTime.Kind);
176 TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
178 if (utcOffset.Ticks < 0) {
179 kind = XsdDateTimeKind.LocalWestOfZulu;
180 zoneHour = -utcOffset.Hours;
181 zoneMinute = -utcOffset.Minutes;
184 kind = XsdDateTimeKind.LocalEastOfZulu;
185 zoneHour = utcOffset.Hours;
186 zoneMinute = utcOffset.Minutes;
192 extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneHour << ZoneHourShift) | zoneMinute);
195 // Constructs an XsdDateTime from a DateTimeOffset
196 public XsdDateTime(DateTimeOffset dateTimeOffset) : this(dateTimeOffset, XsdDateTimeFlags.DateTime) {
199 public XsdDateTime(DateTimeOffset dateTimeOffset, XsdDateTimeFlags kinds) {
200 Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
202 dt = dateTimeOffset.DateTime;
204 TimeSpan zoneOffset = dateTimeOffset.Offset;
205 DateTimeTypeCode code = (DateTimeTypeCode) (Bits.LeastPosition((uint) kinds) - 1);
206 XsdDateTimeKind kind;
207 if (zoneOffset.TotalMinutes < 0) {
208 zoneOffset = zoneOffset.Negate();
209 kind = XsdDateTimeKind.LocalWestOfZulu;
211 else if (zoneOffset.TotalMinutes > 0) {
212 kind = XsdDateTimeKind.LocalEastOfZulu;
215 kind = XsdDateTimeKind.Zulu;
218 extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneOffset.Hours << ZoneHourShift) | zoneOffset.Minutes);
222 /// Returns auxiliary enumeration of XSD date type
224 private DateTimeTypeCode InternalTypeCode {
225 get { return (DateTimeTypeCode)((extra & TypeMask) >> TypeShift); }
229 /// Returns geographical "position" of the value
231 private XsdDateTimeKind InternalKind {
232 get { return (XsdDateTimeKind)((extra & KindMask) >> KindShift); }
237 /// Returns XmlTypeCode of the value being stored
239 public XmlTypeCode TypeCode {
240 get { return typeCodes[(int)InternalTypeCode]; }
244 /// Returns whether object represent local, UTC or unspecified time
246 public DateTimeKind Kind {
248 switch (InternalKind) {
249 case XsdDateTimeKind.Unspecified:
250 return DateTimeKind.Unspecified;
251 case XsdDateTimeKind.Zulu:
252 return DateTimeKind.Utc;
254 // XsdDateTimeKind.LocalEastOfZulu:
255 // XsdDateTimeKind.LocalWestOfZulu:
256 return DateTimeKind.Local;
263 /// Returns the year part of XsdDateTime
264 /// The returned value is integer between 1 and 9999
267 get { return dt.Year; }
271 /// Returns the month part of XsdDateTime
272 /// The returned value is integer between 1 and 12
275 get { return dt.Month; }
279 /// Returns the day of the month part of XsdDateTime
280 /// The returned value is integer between 1 and 31
283 get { return dt.Day; }
287 /// Returns the hour part of XsdDateTime
288 /// The returned value is integer between 0 and 23
291 get { return dt.Hour; }
295 /// Returns the minute part of XsdDateTime
296 /// The returned value is integer between 0 and 60
299 get { return dt.Minute; }
303 /// Returns the second part of XsdDateTime
304 /// The returned value is integer between 0 and 60
307 get { return dt.Second; }
311 /// Returns number of ticks in the fraction of the second
312 /// The returned value is integer between 0 and 9999999
314 public int Fraction {
315 get { return (int)(dt.Ticks - new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second).Ticks); }
319 /// Returns the hour part of the time zone
320 /// The returned value is integer between -13 and 13
322 public int ZoneHour {
324 uint result = (extra & ZoneHourMask) >> ZoneHourShift;
330 /// Returns the minute part of the time zone
331 /// The returned value is integer between 0 and 60
333 public int ZoneMinute {
335 uint result = (extra & ZoneMinuteMask);
341 public DateTime ToZulu() {
342 switch (InternalKind) {
343 case XsdDateTimeKind.Zulu:
345 return new DateTime(dt.Ticks, DateTimeKind.Utc);
346 case XsdDateTimeKind.LocalEastOfZulu:
347 // Adjust to UTC and then convert to local in the current time zone
348 return new DateTime(dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
349 case XsdDateTimeKind.LocalWestOfZulu:
350 // Adjust to UTC and then convert to local in the current time zone
351 return new DateTime(dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
360 /// The following table describes the behaviors of getting the default value
361 /// when a certain year/month/day values are missing.
363 /// An "X" means that the value exists. And "--" means that value is missing.
365 /// Year Month Day => ResultYear ResultMonth ResultDay Note
367 /// X X X Parsed year Parsed month Parsed day
368 /// X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month.
369 /// X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year.
370 /// X -- -- Parsed year First month First day If we have only the year, assume the first day of that year.
372 /// -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year.
373 /// -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day.
374 /// -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month.
375 /// -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date.
377 public static implicit operator DateTime(XsdDateTime xdt) {
379 switch (xdt.InternalTypeCode) {
380 case DateTimeTypeCode.GMonth:
381 case DateTimeTypeCode.GDay:
382 result = new DateTime(DateTime.Now.Year, xdt.Month, xdt.Day);
384 case DateTimeTypeCode.Time:
385 //back to DateTime.Now
386 DateTime currentDateTime = DateTime.Now;
387 TimeSpan addDiff = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day) - new DateTime(xdt.Year, xdt.Month, xdt.Day);
388 result = xdt.dt.Add(addDiff);
396 switch (xdt.InternalKind) {
397 case XsdDateTimeKind.Zulu:
399 result = new DateTime(result.Ticks, DateTimeKind.Utc);
401 case XsdDateTimeKind.LocalEastOfZulu:
402 // Adjust to UTC and then convert to local in the current time zone
403 ticks = result.Ticks - new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0).Ticks;
404 if (ticks < DateTime.MinValue.Ticks)
406 // Underflow. Return the DateTime as local time directly
407 ticks += TimeZoneInfo.Local.GetUtcOffset(result).Ticks;
408 if (ticks < DateTime.MinValue.Ticks)
409 ticks = DateTime.MinValue.Ticks;
410 return new DateTime(ticks, DateTimeKind.Local);
412 result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
414 case XsdDateTimeKind.LocalWestOfZulu:
415 // Adjust to UTC and then convert to local in the current time zone
416 ticks = result.Ticks + new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0).Ticks;
417 if (ticks > DateTime.MaxValue.Ticks)
419 // Overflow. Return the DateTime as local time directly
420 ticks += TimeZoneInfo.Local.GetUtcOffset(result).Ticks;
421 if (ticks > DateTime.MaxValue.Ticks)
422 ticks = DateTime.MaxValue.Ticks;
423 return new DateTime(ticks, DateTimeKind.Local);
425 result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
433 public static implicit operator DateTimeOffset(XsdDateTime xdt) {
436 switch (xdt.InternalTypeCode) {
437 case DateTimeTypeCode.GMonth:
438 case DateTimeTypeCode.GDay:
439 dt = new DateTime( DateTime.Now.Year, xdt.Month, xdt.Day );
441 case DateTimeTypeCode.Time:
442 //back to DateTime.Now
443 DateTime currentDateTime = DateTime.Now;
444 TimeSpan addDiff = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day) - new DateTime(xdt.Year, xdt.Month, xdt.Day);
445 dt = xdt.dt.Add( addDiff );
452 DateTimeOffset result;
453 switch (xdt.InternalKind) {
454 case XsdDateTimeKind.LocalEastOfZulu:
455 result = new DateTimeOffset(dt, new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0));
457 case XsdDateTimeKind.LocalWestOfZulu:
458 result = new DateTimeOffset(dt, new TimeSpan(-xdt.ZoneHour, -xdt.ZoneMinute, 0));
460 case XsdDateTimeKind.Zulu:
461 result = new DateTimeOffset(dt, new TimeSpan( 0 ) );
463 case XsdDateTimeKind.Unspecified:
465 result = new DateTimeOffset(dt, TimeZoneInfo.Local.GetUtcOffset(dt));
474 /// Compares two DateTime values, returning an integer that indicates
475 /// their relationship.
477 public static int Compare(XsdDateTime left, XsdDateTime right) {
478 if (left.extra == right.extra) {
479 return DateTime.Compare(left.dt, right.dt);
482 // Xsd types should be the same for it to be comparable
483 if (left.InternalTypeCode != right.InternalTypeCode) {
484 throw new ArgumentException(Res.GetString(Res.Sch_XsdDateTimeCompare, left.TypeCode, right.TypeCode));
486 // Convert both to UTC
487 return DateTime.Compare(left.GetZuluDateTime(), right.GetZuluDateTime());
492 // Compares this DateTime to a given object. This method provides an
493 // implementation of the IComparable interface. The object
494 // argument must be another DateTime, or otherwise an exception
495 // occurs. Null is considered less than any instance.
497 // Returns a value less than zero if this object
498 /// <include file='doc\DateTime.uex' path='docs/doc[@for="DateTime.CompareTo"]/*' />
499 public int CompareTo(Object value) {
500 if (value == null) return 1;
501 return Compare(this, (XsdDateTime)value);
506 /// Serialization to a string
508 public override string ToString() {
509 StringBuilder sb = new StringBuilder(64);
511 switch (InternalTypeCode) {
512 case DateTimeTypeCode.DateTime:
517 case DateTimeTypeCode.Time:
520 case DateTimeTypeCode.Date:
523 case DateTimeTypeCode.GYearMonth:
524 text = new char[Lzyyyy_MM];
525 IntToCharArray(text, 0, Year, 4);
527 ShortToCharArray(text, Lzyyyy_, Month);
530 case DateTimeTypeCode.GYear:
531 text = new char[Lzyyyy];
532 IntToCharArray(text, 0, Year, 4);
535 case DateTimeTypeCode.GMonthDay:
536 text = new char[Lz__mm_dd];
539 ShortToCharArray(text, Lz__, Month);
541 ShortToCharArray(text, Lz__mm_, Day);
544 case DateTimeTypeCode.GDay:
545 text = new char[Lz___dd];
549 ShortToCharArray(text, Lz___, Day);
552 case DateTimeTypeCode.GMonth:
553 text = new char[Lz__mm__];
556 ShortToCharArray(text, Lz__, Month);
563 return sb.ToString();
566 // Serialize year, month and day
567 private void PrintDate(StringBuilder sb) {
568 char[] text = new char[Lzyyyy_MM_dd];
569 IntToCharArray(text, 0, Year, 4);
571 ShortToCharArray(text, Lzyyyy_, Month);
572 text[Lzyyyy_MM] = '-';
573 ShortToCharArray(text, Lzyyyy_MM_, Day);
577 // Serialize hour, minute, second and fraction
578 private void PrintTime(StringBuilder sb) {
579 char[] text = new char[LzHH_mm_ss];
580 ShortToCharArray(text, 0, Hour);
582 ShortToCharArray(text, LzHH_, Minute);
584 ShortToCharArray(text, LzHH_mm_, Second);
586 int fraction = Fraction;
588 int fractionDigits = maxFractionDigits;
589 while (fraction % 10 == 0) {
593 text = new char[fractionDigits + 1];
595 IntToCharArray(text, 1, fraction, fractionDigits);
600 // Serialize time zone
601 private void PrintZone(StringBuilder sb) {
603 switch (InternalKind) {
604 case XsdDateTimeKind.Zulu:
607 case XsdDateTimeKind.LocalWestOfZulu:
608 text = new char[Lz_zz_zz];
610 ShortToCharArray(text, Lz_, ZoneHour);
612 ShortToCharArray(text, Lz_zz_, ZoneMinute);
615 case XsdDateTimeKind.LocalEastOfZulu:
616 text = new char[Lz_zz_zz];
618 ShortToCharArray(text, Lz_, ZoneHour);
620 ShortToCharArray(text, Lz_zz_, ZoneMinute);
629 // Serialize integer into character array starting with index [start].
630 // Number of digits is set by [digits]
631 private void IntToCharArray(char[] text, int start, int value, int digits) {
632 while(digits -- != 0) {
633 text[start + digits] = (char)(value%10 + '0');
638 // Serialize two digit integer into character array starting with index [start].
639 private void ShortToCharArray(char[] text, int start, int value) {
640 text[start] = (char)(value/10 + '0');
641 text[start + 1] = (char)(value%10 + '0');
645 // Auxiliary for compare.
646 // Returns UTC DateTime
647 private DateTime GetZuluDateTime() {
648 switch (InternalKind) {
649 case XsdDateTimeKind.Zulu:
651 case XsdDateTimeKind.LocalEastOfZulu:
652 return dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0));
653 case XsdDateTimeKind.LocalWestOfZulu:
654 return dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0));
656 return dt.ToUniversalTime();
661 private static readonly XmlTypeCode[] typeCodes = {
662 XmlTypeCode.DateTime,
665 XmlTypeCode.GYearMonth,
667 XmlTypeCode.GMonthDay,
673 // Parsing string according to XML schema spec
675 private const int leapYear = 1904;
676 private const int firstMonth = 1;
677 private const int firstDay = 1;
679 public DateTimeTypeCode typeCode;
687 public XsdDateTimeKind kind;
689 public int zoneMinute;
694 public bool Parse(string text, XsdDateTimeFlags kinds) {
696 this.length = text.Length;
698 // Skip leading withitespace
700 while(start < length && char.IsWhiteSpace(text[start])) {
703 // Choose format starting from the most common and trying not to reparse the same thing too many times
705 #if !SILVERLIGHT // XDR is not supported in Silverlight
706 if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) {
708 if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date)) {
710 if (ParseDate(start)) {
711 if (Test(kinds, XsdDateTimeFlags.DateTime)) {
712 if (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT)) {
713 typeCode = DateTimeTypeCode.DateTime;
717 if (Test(kinds, XsdDateTimeFlags.Date)) {
718 if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd)) {
719 typeCode = DateTimeTypeCode.Date;
723 #if !SILVERLIGHT // XDR is not supported in Silverlight
724 if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) {
725 if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd) || (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT)) ) {
726 typeCode = DateTimeTypeCode.XdrDateTime;
730 if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) {
731 if (ParseChar(start + Lzyyyy_MM_dd, 'T')) {
732 if (ParseTimeAndWhitespace(start + Lzyyyy_MM_ddT)) {
733 typeCode = DateTimeTypeCode.XdrDateTime;
738 typeCode = DateTimeTypeCode.XdrDateTime;
746 if (Test(kinds, XsdDateTimeFlags.Time)) {
747 if (ParseTimeAndZoneAndWhitespace(start)) { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
751 typeCode = DateTimeTypeCode.Time;
756 #if !SILVERLIGHT // XDR is not supported in Silverlight
757 if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) {
758 if (ParseTimeAndWhitespace(start)) { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
762 typeCode = DateTimeTypeCode.Time;
768 if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) {
769 if (Parse4Dig(start , ref year) && 1 <= year) {
770 if (Test(kinds, XsdDateTimeFlags.GYearMonth)) {
772 ParseChar(start + Lzyyyy, '-') &&
773 Parse2Dig(start + Lzyyyy_, ref month) && 1 <= month && month <= 12 &&
774 ParseZoneAndWhitespace(start + Lzyyyy_MM)
777 typeCode = DateTimeTypeCode.GYearMonth;
781 if (Test(kinds, XsdDateTimeFlags.GYear)) {
782 if (ParseZoneAndWhitespace(start + Lzyyyy)) {
785 typeCode = DateTimeTypeCode.GYear;
791 if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) {
793 ParseChar(start , '-') &&
794 ParseChar(start + Lz_, '-') &&
795 Parse2Dig(start + Lz__, ref month) && 1 <= month && month <= 12
797 if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + Lz__mm, '-')) {
799 Parse2Dig(start + Lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) &&
800 ParseZoneAndWhitespace(start + Lz__mm_dd)
803 typeCode = DateTimeTypeCode.GMonthDay;
807 if (Test(kinds, XsdDateTimeFlags.GMonth)) {
808 if (ParseZoneAndWhitespace(start + Lz__mm) || (ParseChar(start + Lz__mm, '-') && ParseChar(start + Lz__mm_, '-') && ParseZoneAndWhitespace(start + Lz__mm__)) ) {
811 typeCode = DateTimeTypeCode.GMonth;
818 if (Test(kinds, XsdDateTimeFlags.GDay)) {
820 ParseChar(start , '-') &&
821 ParseChar(start + Lz_, '-') &&
822 ParseChar(start + Lz__, '-') &&
823 Parse2Dig(start + Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) &&
824 ParseZoneAndWhitespace(start + Lz___dd)
829 typeCode = DateTimeTypeCode.GDay;
837 private bool ParseDate(int start) {
839 Parse4Dig(start , ref year) && 1 <= year &&
840 ParseChar(start + Lzyyyy, '-') &&
841 Parse2Dig(start + Lzyyyy_, ref month) && 1 <= month && month <= 12 &&
842 ParseChar(start + Lzyyyy_MM, '-') &&
843 Parse2Dig(start + Lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month);
846 private bool ParseTimeAndZoneAndWhitespace(int start) {
847 if (ParseTime(ref start)) {
848 if (ParseZoneAndWhitespace(start)) {
855 #if !SILVERLIGHT // XDR is not supported in Silverlight
856 private bool ParseTimeAndWhitespace(int start) {
857 if (ParseTime(ref start)) {
858 while(start < length ) {//&& char.IsWhiteSpace(text[start])) {
861 return start == length;
867 static int[] Power10 = new int[maxFractionDigits] {-1, 10, 100, 1000, 10000, 100000, 1000000};
868 private bool ParseTime(ref int start) {
870 Parse2Dig(start , ref hour) && hour < 24 &&
871 ParseChar(start + LzHH, ':') &&
872 Parse2Dig(start + LzHH_, ref minute) && minute < 60 &&
873 ParseChar(start + LzHH_mm, ':') &&
874 Parse2Dig(start + LzHH_mm_, ref second) && second < 60
877 if (ParseChar(start, '.')) {
878 // Parse factional part of seconds
879 // We allow any number of digits, but keep only first 7
881 int fractionDigits = 0;
883 while (++start < length) {
884 int d = text[start] - '0';
885 if (9u < (uint) d) { // d < 0 || 9 < d
888 if (fractionDigits < maxFractionDigits) {
889 this.fraction = (this.fraction * 10) + d;
890 } else if (fractionDigits == maxFractionDigits) {
896 } else if (round < 0 && d != 0) {
901 if (fractionDigits < maxFractionDigits) {
902 if (fractionDigits == 0) {
903 return false; // cannot end with .
905 fraction *= Power10[maxFractionDigits - fractionDigits];
908 round = fraction & 1;
915 // cleanup - conflict with gYear
920 private bool ParseZoneAndWhitespace(int start) {
921 if (start < length) {
922 char ch = text[start];
923 if (ch == 'Z' || ch == 'z') {
924 kind = XsdDateTimeKind.Zulu;
927 else if (start + 5 < length) {
929 Parse2Dig(start + Lz_, ref zoneHour) && zoneHour <= 99 &&
930 ParseChar(start + Lz_zz, ':') &&
931 Parse2Dig(start + Lz_zz_, ref zoneMinute) && zoneMinute <= 99
934 kind = XsdDateTimeKind.LocalWestOfZulu;
937 else if (ch == '+') {
938 kind = XsdDateTimeKind.LocalEastOfZulu;
944 while(start < length && char.IsWhiteSpace(text[start])) {
947 return start == length;
951 private bool Parse4Dig(int start, ref int num) {
952 if (start + 3 < length) {
953 int d4 = text[start] - '0';
954 int d3 = text[start + 1] - '0';
955 int d2 = text[start + 2] - '0';
956 int d1 = text[start + 3] - '0';
957 if (0 <= d4 && d4 < 10 &&
958 0 <= d3 && d3 < 10 &&
959 0 <= d2 && d2 < 10 &&
962 num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
969 private bool Parse2Dig(int start, ref int num) {
970 if (start + 1 < length) {
971 int d2 = text[start] - '0';
972 int d1 = text[start + 1] - '0';
973 if (0 <= d2 && d2 < 10 &&
983 private bool ParseChar(int start, char ch) {
984 return start < length && text[start] == ch;
987 private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) {
988 return (left & right) != 0;