Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Schema / XsdDateTime.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XsdDuration.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>                                                                
6 //------------------------------------------------------------------------------
7
8 namespace System.Xml.Schema {
9     using System;
10     using System.Xml;
11     using System.Diagnostics;
12     using System.Text;
13
14     /// <summary>
15     /// This enum specifies what format should be used when converting string to XsdDateTime
16     /// </summary>
17     [Flags]
18     internal enum XsdDateTimeFlags {
19         DateTime        = 0x01,
20         Time            = 0x02,
21         Date            = 0x04,
22         GYearMonth      = 0x08, 
23         GYear           = 0x10, 
24         GMonthDay       = 0x20, 
25         GDay            = 0x40, 
26         GMonth          = 0x80,
27 #if !SILVERLIGHT // XDR is not supported in Silverlight
28         XdrDateTimeNoTz = 0x100,
29         XdrDateTime     = 0x200,
30         XdrTimeNoTz     = 0x400,  //XDRTime with tz is the same as xsd:time  
31 #endif
32         AllXsd          = 0xFF //All still does not include the XDR formats
33     }
34
35     /// <summary>
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.
38     /// </summary>
39     internal struct XsdDateTime {
40         // DateTime is being used as an internal representation only
41         // Casting XsdDateTime to DateTime might return a different value
42         private DateTime dt;
43
44         // Additional information that DateTime is not preserving
45         // Information is stored in the following format:
46         // Bits     Info
47         // 31-24    DateTimeTypeCode 
48         // 23-16    XsdDateTimeKind
49         // 15-8     Zone Hours
50         // 7-0      Zone Minutes
51         private uint extra;
52
53
54         // Subset of XML Schema types XsdDateTime represents
55         enum DateTimeTypeCode {
56             DateTime,
57             Time,
58             Date,
59             GYearMonth,
60             GYear,
61             GMonthDay,
62             GDay,
63             GMonth,
64 #if !SILVERLIGHT // XDR is not supported in Silverlight
65             XdrDateTime,
66 #endif
67         }
68
69         // Internal representation of DateTimeKind
70         enum XsdDateTimeKind {
71             Unspecified,
72             Zulu,
73             LocalWestOfZulu,    // GMT-1..14, N..Y
74             LocalEastOfZulu     // GMT+1..14, A..M
75         }
76
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;
85
86         // Maximum number of fraction digits;
87         private const short maxFractionDigits = 7;
88
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;
111
112
113 #if !SILVERLIGHT
114         /// <summary>
115         /// Constructs an XsdDateTime from a string trying all possible formats.
116         /// </summary>
117         public XsdDateTime(string text) : this(text, XsdDateTimeFlags.AllXsd) {
118         }
119 #endif
120
121         /// <summary>
122         /// Constructs an XsdDateTime from a string using specific format.
123         /// </summary>
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));
128             }
129             InitiateXsdDateTime(parser);
130         }
131
132 #if !SILVERLIGHT
133         private XsdDateTime(Parser parser) : this() {
134             InitiateXsdDateTime(parser);
135         }
136 #endif
137
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);
142             }
143             extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute);
144         }
145
146 #if !SILVERLIGHT
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();
151                 return false;
152             }
153             result = new XsdDateTime(parser);
154             return true;
155         }
156 #endif
157
158         /// <summary>
159         /// Constructs an XsdDateTime from a DateTime.
160         /// </summary>
161         public XsdDateTime(DateTime dateTime, XsdDateTimeFlags kinds) {
162             Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
163             dt = dateTime;
164
165             DateTimeTypeCode code = (DateTimeTypeCode) (Bits.LeastPosition((uint) kinds) - 1);
166             int zoneHour = 0;
167             int zoneMinute = 0;
168             XsdDateTimeKind kind;
169
170             switch (dateTime.Kind) {
171                 case DateTimeKind.Unspecified: kind = XsdDateTimeKind.Unspecified; break;
172                 case DateTimeKind.Utc: kind = XsdDateTimeKind.Zulu; break;
173
174                 default: {
175                     Debug.Assert(dateTime.Kind == DateTimeKind.Local, "Unknown DateTimeKind: " + dateTime.Kind);
176                     TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
177
178                     if (utcOffset.Ticks < 0) {
179                         kind = XsdDateTimeKind.LocalWestOfZulu;
180                         zoneHour = -utcOffset.Hours;
181                         zoneMinute = -utcOffset.Minutes;
182                     }
183                     else {
184                         kind = XsdDateTimeKind.LocalEastOfZulu;
185                         zoneHour = utcOffset.Hours;
186                         zoneMinute = utcOffset.Minutes;
187                     }
188                     break;
189                 }
190             }
191
192             extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneHour << ZoneHourShift) | zoneMinute);
193         }
194
195         // Constructs an XsdDateTime from a DateTimeOffset
196         public XsdDateTime(DateTimeOffset dateTimeOffset) : this(dateTimeOffset, XsdDateTimeFlags.DateTime) {
197         }
198
199         public XsdDateTime(DateTimeOffset dateTimeOffset, XsdDateTimeFlags kinds) {
200             Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
201
202             dt = dateTimeOffset.DateTime;
203
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;
210             }
211             else if (zoneOffset.TotalMinutes > 0) {
212                 kind = XsdDateTimeKind.LocalEastOfZulu;
213             }
214             else {
215                 kind = XsdDateTimeKind.Zulu;
216             }
217
218             extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneOffset.Hours << ZoneHourShift) | zoneOffset.Minutes);
219         }
220
221         /// <summary>
222         /// Returns auxiliary enumeration of XSD date type
223         /// </summary>
224         private DateTimeTypeCode InternalTypeCode {
225             get { return (DateTimeTypeCode)((extra & TypeMask) >> TypeShift); }
226         }
227
228         /// <summary>
229         /// Returns geographical "position" of the value
230         /// </summary>
231         private XsdDateTimeKind InternalKind {
232             get { return (XsdDateTimeKind)((extra & KindMask) >> KindShift); }
233         }
234
235 #if !SILVERLIGHT
236         /// <summary>
237         /// Returns XmlTypeCode of the value being stored
238         /// </summary>
239         public XmlTypeCode TypeCode {
240             get { return typeCodes[(int)InternalTypeCode]; }
241         }
242
243         /// <summary>
244         /// Returns whether object represent local, UTC or unspecified time
245         /// </summary>
246         public DateTimeKind Kind {
247             get {
248                 switch (InternalKind) {
249                 case XsdDateTimeKind.Unspecified:
250                     return DateTimeKind.Unspecified;
251                 case XsdDateTimeKind.Zulu:
252                     return DateTimeKind.Utc;
253                 default:
254                     // XsdDateTimeKind.LocalEastOfZulu:
255                     // XsdDateTimeKind.LocalWestOfZulu:
256                     return DateTimeKind.Local;
257                 }
258             }
259         }
260 #endif
261
262         /// <summary>
263         /// Returns the year part of XsdDateTime
264         /// The returned value is integer between 1 and 9999
265         /// </summary>
266         public int Year {
267             get { return dt.Year; }
268         }
269  
270         /// <summary>
271         /// Returns the month part of XsdDateTime
272         /// The returned value is integer between 1 and 12
273         /// </summary>
274         public int Month {
275             get { return dt.Month; }
276         }
277  
278         /// <summary>
279         /// Returns the day of the month part of XsdDateTime
280         /// The returned value is integer between 1 and 31
281         /// </summary>
282         public int Day {
283             get { return dt.Day; }
284         }
285  
286         /// <summary>
287         /// Returns the hour part of XsdDateTime
288         /// The returned value is integer between 0 and 23
289         /// </summary>
290         public int Hour {
291             get { return dt.Hour; }
292         }
293  
294         /// <summary>
295         /// Returns the minute part of XsdDateTime
296         /// The returned value is integer between 0 and 60
297         /// </summary>
298         public int Minute {
299             get { return dt.Minute; }
300         }
301  
302         /// <summary>
303         /// Returns the second part of XsdDateTime
304         /// The returned value is integer between 0 and 60
305         /// </summary>
306         public int Second {
307             get { return dt.Second; }
308         }
309
310         /// <summary>
311         /// Returns number of ticks in the fraction of the second
312         /// The returned value is integer between 0 and 9999999
313         /// </summary>
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); }
316         }
317  
318         /// <summary>
319         /// Returns the hour part of the time zone
320         /// The returned value is integer between -13 and 13
321         /// </summary>
322         public int ZoneHour {
323             get { 
324                 uint result = (extra & ZoneHourMask) >> ZoneHourShift; 
325                 return (int)result;
326             }
327         }
328
329         /// <summary>
330         /// Returns the minute part of the time zone
331         /// The returned value is integer between 0 and 60
332         /// </summary>
333         public int ZoneMinute {
334             get { 
335                 uint result = (extra & ZoneMinuteMask); 
336                 return (int)result;
337             }
338         }
339
340 #if !SILVERLIGHT
341         public DateTime ToZulu() {
342             switch (InternalKind) {
343                 case XsdDateTimeKind.Zulu:
344                     // set it to UTC
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); 
352                 default:
353                     return dt;
354             }
355         }
356 #endif
357
358         /// <summary>
359         /// Cast to DateTime
360         /// The following table describes the behaviors of getting the default value
361         /// when a certain year/month/day values are missing.
362         /// 
363         /// An "X" means that the value exists.  And "--" means that value is missing.
364         /// 
365         /// Year    Month   Day =>  ResultYear  ResultMonth     ResultDay       Note
366         /// 
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.
371         /// 
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.
376         /// </summary>
377         public static implicit operator DateTime(XsdDateTime xdt) {
378             DateTime result;
379             switch (xdt.InternalTypeCode) {
380                 case DateTimeTypeCode.GMonth:
381                 case DateTimeTypeCode.GDay:
382                     result = new DateTime(DateTime.Now.Year, xdt.Month, xdt.Day);
383                     break;
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);
389                     break;
390                 default:
391                     result = xdt.dt;
392                     break;
393             }
394
395             long ticks;
396             switch (xdt.InternalKind) {
397                 case XsdDateTimeKind.Zulu:
398                     // set it to UTC
399                     result = new DateTime(result.Ticks, DateTimeKind.Utc);
400                     break;
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)
405                     {
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);
411                     }
412                     result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
413                     break;
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)
418                     {
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);
424                     }
425                     result = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime(); 
426                     break;
427                 default:
428                     break;
429             }
430             return result;
431         }
432
433         public static implicit operator DateTimeOffset(XsdDateTime xdt) {
434             DateTime dt;
435
436             switch (xdt.InternalTypeCode) {
437                 case DateTimeTypeCode.GMonth:
438                 case DateTimeTypeCode.GDay:
439                     dt = new DateTime( DateTime.Now.Year, xdt.Month, xdt.Day );
440                     break;
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 );
446                     break;
447                 default:
448                     dt = xdt.dt;
449                     break;
450             }
451
452             DateTimeOffset result;
453             switch (xdt.InternalKind) {
454                 case XsdDateTimeKind.LocalEastOfZulu:
455                     result = new DateTimeOffset(dt, new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0));
456                     break;
457                 case XsdDateTimeKind.LocalWestOfZulu:
458                     result = new DateTimeOffset(dt, new TimeSpan(-xdt.ZoneHour, -xdt.ZoneMinute, 0));
459                     break;
460                 case XsdDateTimeKind.Zulu:
461                     result = new DateTimeOffset(dt, new TimeSpan( 0 ) );
462                     break;
463                 case XsdDateTimeKind.Unspecified:
464                 default:
465                     result = new DateTimeOffset(dt, TimeZoneInfo.Local.GetUtcOffset(dt));
466                     break;
467             }
468
469             return result;
470         }
471
472 #if !SILVERLIGHT
473         /// <summary>
474         /// Compares two DateTime values, returning an integer that indicates
475         /// their relationship.
476         /// </summary>
477         public static int Compare(XsdDateTime left, XsdDateTime right) {
478             if (left.extra == right.extra) {
479                 return DateTime.Compare(left.dt, right.dt);
480             }
481             else {
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));
485                 }
486                 // Convert both to UTC
487                 return DateTime.Compare(left.GetZuluDateTime(), right.GetZuluDateTime());
488
489             }
490         }
491
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.
496         //
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);
502         }
503 #endif
504
505         /// <summary>
506         /// Serialization to a string
507         /// </summary>
508         public override string ToString() {
509             StringBuilder sb = new StringBuilder(64);
510             char[] text;
511             switch (InternalTypeCode) {
512             case DateTimeTypeCode.DateTime:
513                 PrintDate(sb);
514                 sb.Append('T');
515                 PrintTime(sb);
516                 break;
517             case DateTimeTypeCode.Time:
518                 PrintTime(sb);
519                 break;
520             case DateTimeTypeCode.Date:
521                 PrintDate(sb);
522                 break;
523             case DateTimeTypeCode.GYearMonth:
524                 text = new char[Lzyyyy_MM];
525                 IntToCharArray(text, 0, Year, 4);
526                 text[Lzyyyy] = '-';
527                 ShortToCharArray(text, Lzyyyy_, Month);
528                 sb.Append(text);
529                 break;
530             case DateTimeTypeCode.GYear:
531                 text = new char[Lzyyyy];
532                 IntToCharArray(text, 0, Year, 4);
533                 sb.Append(text);
534                 break;
535             case DateTimeTypeCode.GMonthDay:
536                 text = new char[Lz__mm_dd];
537                 text[0] = '-';
538                 text[Lz_] = '-';
539                 ShortToCharArray(text, Lz__, Month);
540                 text[Lz__mm] = '-';
541                 ShortToCharArray(text, Lz__mm_, Day);
542                 sb.Append(text);
543                 break;
544             case DateTimeTypeCode.GDay:
545                 text = new char[Lz___dd];
546                 text[0] = '-';
547                 text[Lz_] = '-';
548                 text[Lz__] = '-';
549                 ShortToCharArray(text, Lz___, Day);
550                 sb.Append(text);
551                 break;
552             case DateTimeTypeCode.GMonth:
553                 text = new char[Lz__mm__];
554                 text[0] = '-';
555                 text[Lz_] = '-';
556                 ShortToCharArray(text, Lz__, Month);
557                 text[Lz__mm] = '-';
558                 text[Lz__mm_] = '-';
559                 sb.Append(text);
560                 break;
561             }
562             PrintZone(sb);
563             return sb.ToString();
564         }
565
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);
570             text[Lzyyyy] = '-';
571             ShortToCharArray(text, Lzyyyy_, Month);
572             text[Lzyyyy_MM] = '-';
573             ShortToCharArray(text, Lzyyyy_MM_, Day);
574             sb.Append(text);
575         }
576
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);
581             text[LzHH] = ':';
582             ShortToCharArray(text, LzHH_, Minute);
583             text[LzHH_mm] = ':';
584             ShortToCharArray(text, LzHH_mm_, Second);
585             sb.Append(text);
586             int fraction = Fraction;
587             if (fraction != 0) {   
588                 int fractionDigits = maxFractionDigits;
589                 while (fraction % 10 == 0) {
590                     fractionDigits --;
591                     fraction /= 10;
592                 }
593                 text = new char[fractionDigits + 1];
594                 text[0] = '.';
595                 IntToCharArray(text, 1, fraction, fractionDigits);
596                 sb.Append(text);
597             }
598         }
599
600         // Serialize time zone
601         private void PrintZone(StringBuilder sb) {
602             char[] text;
603             switch (InternalKind) {
604             case XsdDateTimeKind.Zulu: 
605                 sb.Append('Z');
606                 break;
607             case XsdDateTimeKind.LocalWestOfZulu:
608                 text = new char[Lz_zz_zz];
609                 text[0] = '-';
610                 ShortToCharArray(text, Lz_, ZoneHour);
611                 text[Lz_zz] = ':';
612                 ShortToCharArray(text, Lz_zz_, ZoneMinute);
613                 sb.Append(text);
614                 break;
615             case XsdDateTimeKind.LocalEastOfZulu:
616                 text = new char[Lz_zz_zz];
617                 text[0] = '+';
618                 ShortToCharArray(text, Lz_, ZoneHour);
619                 text[Lz_zz] = ':';
620                 ShortToCharArray(text, Lz_zz_, ZoneMinute);
621                 sb.Append(text);
622                 break;
623             default:
624                 // do nothing
625                 break;
626             }
627         }
628
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');
634                 value /= 10;
635             }
636         }
637
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');
642         }
643
644 #if !SILVERLIGHT
645         // Auxiliary for compare. 
646         // Returns UTC DateTime
647         private DateTime GetZuluDateTime() {
648             switch (InternalKind) {
649             case XsdDateTimeKind.Zulu:
650                 return dt;
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));
655             default:
656                 return dt.ToUniversalTime();
657             }
658         }
659 #endif
660
661         private static readonly XmlTypeCode[] typeCodes = {
662             XmlTypeCode.DateTime,
663             XmlTypeCode.Time,
664             XmlTypeCode.Date,
665             XmlTypeCode.GYearMonth,
666             XmlTypeCode.GYear,
667             XmlTypeCode.GMonthDay,
668             XmlTypeCode.GDay,
669             XmlTypeCode.GMonth
670         };
671
672
673         // Parsing string according to XML schema spec
674         struct Parser {
675             private const int leapYear = 1904;
676             private const int firstMonth = 1;
677             private const int firstDay = 1;
678
679             public DateTimeTypeCode typeCode;
680             public int year;
681             public int month;
682             public int day;
683             public int hour;
684             public int minute;
685             public int second;
686             public int fraction;
687             public XsdDateTimeKind kind;
688             public int zoneHour;
689             public int zoneMinute;
690
691             private string text;
692             private int length;
693
694             public bool Parse(string text, XsdDateTimeFlags kinds) {
695                 this.text = text;
696                 this.length = text.Length;
697
698                 // Skip leading withitespace
699                 int start = 0;
700                 while(start < length && char.IsWhiteSpace(text[start])) {
701                     start ++;
702                 }
703                 // Choose format starting from the most common and trying not to reparse the same thing too many times
704
705 #if !SILVERLIGHT // XDR is not supported in Silverlight
706                 if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) {
707 #else
708                 if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date)) {
709 #endif
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;
714                                 return true;
715                             }
716                         }
717                         if (Test(kinds, XsdDateTimeFlags.Date)) {  
718                             if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd)) {
719                                 typeCode = DateTimeTypeCode.Date;
720                                 return true;
721                             }
722                         }
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;
727                                 return true;
728                             }
729                         }
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;
734                                     return true;
735                                 }
736                             }
737                             else {
738                                 typeCode = DateTimeTypeCode.XdrDateTime;
739                                 return true;
740                             }
741                         }
742 #endif
743                     }
744                 }
745
746                 if (Test(kinds, XsdDateTimeFlags.Time)) {
747                     if (ParseTimeAndZoneAndWhitespace(start)) { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
748                         year = leapYear;
749                         month = firstMonth;
750                         day = firstDay;
751                         typeCode = DateTimeTypeCode.Time;
752                         return true;
753                     }
754                 }
755
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
759                         year = leapYear;
760                         month = firstMonth;
761                         day = firstDay;
762                         typeCode = DateTimeTypeCode.Time;
763                         return true;
764                     }
765                 }
766 #endif
767
768                 if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) {
769                     if (Parse4Dig(start ,         ref year) && 1 <= year) {
770                         if (Test(kinds, XsdDateTimeFlags.GYearMonth)) {
771                             if (
772                                 ParseChar(start + Lzyyyy,     '-') &&
773                                 Parse2Dig(start + Lzyyyy_,    ref month) && 1 <= month && month <= 12 &&
774                                 ParseZoneAndWhitespace(start + Lzyyyy_MM)
775                             ) {
776                                 day = firstDay;
777                                 typeCode = DateTimeTypeCode.GYearMonth;
778                                 return true;
779                             }
780                         }
781                         if (Test(kinds, XsdDateTimeFlags.GYear)) {
782                             if (ParseZoneAndWhitespace(start + Lzyyyy)) {
783                                 month = firstMonth;
784                                 day = firstDay;
785                                 typeCode = DateTimeTypeCode.GYear;
786                                 return true;
787                             }
788                         }                        
789                     }
790                 }
791                 if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) {
792                     if (
793                         ParseChar(start ,         '-') &&
794                         ParseChar(start + Lz_,    '-') &&
795                         Parse2Dig(start + Lz__,   ref month) && 1 <= month && month <= 12
796                     ) {
797                         if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + Lz__mm, '-')) {
798                             if (
799                                 Parse2Dig(start + Lz__mm_,     ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) &&
800                                 ParseZoneAndWhitespace(start + Lz__mm_dd)
801                             ) {
802                                 year = leapYear;
803                                 typeCode = DateTimeTypeCode.GMonthDay;
804                                 return true;
805                             }
806                         }
807                         if (Test(kinds, XsdDateTimeFlags.GMonth)) {
808                             if (ParseZoneAndWhitespace(start + Lz__mm) || (ParseChar(start + Lz__mm, '-') && ParseChar(start + Lz__mm_, '-') && ParseZoneAndWhitespace(start + Lz__mm__)) ) {
809                                 year = leapYear;
810                                 day = firstDay;
811                                 typeCode = DateTimeTypeCode.GMonth;
812                                 return true;
813                             }
814                         }
815                     }
816                 
817                 }
818                 if (Test(kinds, XsdDateTimeFlags.GDay)) {
819                     if (
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)
825
826                     ) {
827                         year = leapYear;
828                         month = firstMonth;
829                         typeCode = DateTimeTypeCode.GDay;
830                         return true;
831                     }
832                 }
833                 return false;
834             }
835
836
837             private bool ParseDate(int start) {
838                 return  
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);
844             }
845
846             private bool ParseTimeAndZoneAndWhitespace(int start) {
847                 if (ParseTime(ref start)) {
848                     if (ParseZoneAndWhitespace(start)) {
849                         return true;
850                     }
851                 }
852                 return false;
853             }
854
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])) {
859                         start ++;
860                     }
861                     return start == length;    
862                 }
863                 return false;
864             }
865 #endif
866
867             static int[] Power10 = new int[maxFractionDigits] {-1, 10, 100, 1000, 10000, 100000, 1000000};
868             private bool ParseTime(ref int start) {
869                 if (
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
875                 ) {
876                     start += LzHH_mm_ss; 
877                     if (ParseChar(start, '.')) {
878                         // Parse factional part of seconds
879                         // We allow any number of digits, but keep only first 7
880                         this.fraction = 0;
881                         int fractionDigits = 0;
882                         int round = 0;
883                         while (++start < length) {
884                             int d = text[start] - '0';
885                             if (9u < (uint) d) { // d < 0 || 9 < d
886                                 break;
887                             }
888                             if (fractionDigits < maxFractionDigits) {
889                                 this.fraction = (this.fraction * 10) + d;
890                             } else if (fractionDigits == maxFractionDigits) {
891                                 if (5 < d) {
892                                     round = 1;
893                                 } else if (d == 5) {
894                                     round = -1;
895                                 }
896                             } else if (round < 0 && d != 0) {
897                                 round = 1;
898                             }
899                             fractionDigits ++;
900                         }
901                         if (fractionDigits < maxFractionDigits) {
902                             if (fractionDigits == 0) {
903                                 return false; // cannot end with .
904                             }
905                             fraction *= Power10[maxFractionDigits - fractionDigits];
906                         } else {
907                             if (round < 0) {
908                                 round = fraction & 1;
909                             }
910                             fraction += round;
911                         }
912                     }
913                     return true;
914                 }
915                 // cleanup - conflict with gYear
916                 hour = 0;
917                 return false;
918             }
919
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;
925                         start ++;
926                     }
927                     else if (start + 5 < length) {
928                         if (
929                             Parse2Dig(start + Lz_,       ref zoneHour) && zoneHour <= 99 &&
930                             ParseChar(start + Lz_zz,     ':') &&
931                             Parse2Dig(start + Lz_zz_,    ref zoneMinute) && zoneMinute <= 99
932                         ) {
933                             if (ch == '-') {
934                                 kind = XsdDateTimeKind.LocalWestOfZulu;
935                                 start += Lz_zz_zz;
936                             }
937                             else if (ch == '+') {
938                                 kind = XsdDateTimeKind.LocalEastOfZulu;
939                                 start += Lz_zz_zz;
940                             }
941                         }
942                     }
943                 }
944                 while(start < length && char.IsWhiteSpace(text[start])) {
945                     start ++;
946                 }
947                 return start == length;
948             }
949
950
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 &&
960                         0 <= d1 && d1 < 10
961                     ) {
962                         num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
963                         return true;
964                     }
965                 }
966                 return false;
967             }
968
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 &&
974                         0 <= d1 && d1 < 10
975                         ) {
976                         num =  d2 * 10 + d1;
977                         return true;
978                     }
979                 }
980                 return false;
981             }
982
983             private bool ParseChar(int start, char ch) {
984                 return start < length && text[start] == ch;
985             }
986
987             private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right) {
988                 return (left & right) != 0;
989             }
990
991         }
992     }
993 }