The Labs \ Source Viewer \ SSCLI \ System.Xml.Schema \ DateTimeTypeCode

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XsdDuration.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. // <owner current="true" primary="true">yanl</owner>
  15. //------------------------------------------------------------------------------
  16. namespace System.Xml.Schema
  17. {
  18.     using System;
  19.     using System.Xml;
  20.     using System.Diagnostics;
  21.     using System.Text;
  22.    
  23.     /// <summary>
  24.     /// This enum specifies what format should be used when converting string to XsdDateTime
  25.     /// </summary>
  26.     [Flags()]
  27.     internal enum XsdDateTimeFlags
  28.     {
  29.         DateTime = 1,
  30.         Time = 2,
  31.         Date = 4,
  32.         GYearMonth = 8,
  33.         GYear = 16,
  34.         GMonthDay = 32,
  35.         GDay = 64,
  36.         GMonth = 128,
  37.         XdrDateTimeNoTz = 256,
  38.         XdrDateTime = 512,
  39.         XdrTimeNoTz = 1024,
  40.         //XDRTime with tz is the same as xsd:time
  41.         AllXsd = 255
  42.         //All still does not include the XDR formats
  43.     }
  44.    
  45.     /// <summary>
  46.     /// 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
  47.     /// of fidelity. XsdDuration structures are immutable once they've been created.
  48.     /// </summary>
  49.     internal struct XsdDateTime
  50.     {
  51.         // DateTime is being used as an internal representation only
  52.         // Casting XsdDateTime to DateTime might return a different value
  53.         private DateTime dt;
  54.        
  55.         // Additional information that DateTime is not preserving
  56.         // Information is stored in the following format:
  57.         // Bits Info
  58.         // 31-24 DateTimeTypeCode
  59.         // 23-16 XsdDateTimeKind
  60.         // 15-8 Zone Hours
  61.         // 7-0 Zone Minutes
  62.         private uint extra;
  63.        
  64.        
  65.         // Subset of XML Schema types XsdDateTime represents
  66.         enum DateTimeTypeCode
  67.         {
  68.             DateTime,
  69.             Time,
  70.             Date,
  71.             GYearMonth,
  72.             GYear,
  73.             GMonthDay,
  74.             GDay,
  75.             GMonth,
  76.             XdrDateTime
  77.         }
  78.        
  79.         // Internal representation of DateTimeKind
  80.         enum XsdDateTimeKind
  81.         {
  82.             Unspecified,
  83.             Zulu,
  84.             LocalWestOfZulu,
  85.             // GMT-1..14, N..Y
  86.             LocalEastOfZulu
  87.             // GMT+1..14, A..M
  88.         }
  89.        
  90.         // Masks and shifts used for packing and unpacking extra
  91.         private const uint TypeMask = 4278190080u;
  92.         private const uint KindMask = 16711680;
  93.         private const uint ZoneHourMask = 65280;
  94.         private const uint ZoneMinuteMask = 255;
  95.         private const int TypeShift = 24;
  96.         private const int KindShift = 16;
  97.         private const int ZoneHourShift = 8;
  98.        
  99.         // Maximum number of fraction digits;
  100.         private const short maxFractionDigits = 7;
  101.        
  102.         static readonly int Lzyyyy = "yyyy".Length;
  103.         static readonly int Lzyyyy_ = "yyyy-".Length;
  104.         static readonly int Lzyyyy_MM = "yyyy-MM".Length;
  105.         static readonly int Lzyyyy_MM_ = "yyyy-MM-".Length;
  106.         static readonly int Lzyyyy_MM_dd = "yyyy-MM-dd".Length;
  107.         static readonly int Lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
  108.         static readonly int LzHH = "HH".Length;
  109.         static readonly int LzHH_ = "HH:".Length;
  110.         static readonly int LzHH_mm = "HH:mm".Length;
  111.         static readonly int LzHH_mm_ = "HH:mm:".Length;
  112.         static readonly int LzHH_mm_ss = "HH:mm:ss".Length;
  113.         static readonly int Lz_ = "-".Length;
  114.         static readonly int Lz_zz = "-zz".Length;
  115.         static readonly int Lz_zz_ = "-zz:".Length;
  116.         static readonly int Lz_zz_zz = "-zz:zz".Length;
  117.         static readonly int Lz__ = "--".Length;
  118.         static readonly int Lz__mm = "--MM".Length;
  119.         static readonly int Lz__mm_ = "--MM-".Length;
  120.         static readonly int Lz__mm__ = "--MM--".Length;
  121.         static readonly int Lz__mm_dd = "--MM-dd".Length;
  122.         static readonly int Lz___ = "---".Length;
  123.         static readonly int Lz___dd = "---dd".Length;
  124.        
  125.        
  126.         /// <summary>
  127.         /// Constructs an XsdDateTime from a string trying all possible formats.
  128.         /// </summary>
  129.         public XsdDateTime(string text) : this(text, XsdDateTimeFlags.AllXsd)
  130.         {
  131.         }
  132.        
  133.         /// <summary>
  134.         /// Constructs an XsdDateTime from a string using specific format.
  135.         /// </summary>
  136.         public XsdDateTime(string text, XsdDateTimeFlags kinds) : this()
  137.         {
  138.             Parser parser = new Parser();
  139.             if (!parser.Parse(text, kinds)) {
  140.                 throw new FormatException(Res.GetString(Res.XmlConvert_BadFormat, text, kinds));
  141.             }
  142.             InitiateXsdDateTime(parser);
  143.         }
  144.        
  145.         private XsdDateTime(Parser parser) : this()
  146.         {
  147.             InitiateXsdDateTime(parser);
  148.         }
  149.        
  150.         private void InitiateXsdDateTime(Parser parser)
  151.         {
  152.             dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second);
  153.             if (parser.fraction != 0) {
  154.                 dt = dt.AddTicks(parser.fraction);
  155.             }
  156.             extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute);
  157.         }
  158.        
  159.         static internal bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result)
  160.         {
  161.             Parser parser = new Parser();
  162.             if (!parser.Parse(text, kinds)) {
  163.                 result = new XsdDateTime();
  164.                 return false;
  165.             }
  166.             result = new XsdDateTime(parser);
  167.             return true;
  168.         }
  169.        
  170.         /// <summary>
  171.         /// Constructs an XsdDateTime from a DateTime.
  172.         /// </summary>
  173.         public XsdDateTime(DateTime dateTime, XsdDateTimeFlags kinds)
  174.         {
  175.             Debug.Assert(Bits.ExactlyOne((uint)kinds), "Only one DateTime type code can be set.");
  176.             dt = dateTime;
  177.            
  178.             DateTimeTypeCode code = (DateTimeTypeCode)(Bits.LeastPosition((uint)kinds) - 1);
  179.             int zoneHour = 0;
  180.             int zoneMinute = 0;
  181.             XsdDateTimeKind kind;
  182.            
  183.             switch (dateTime.Kind) {
  184.                 case DateTimeKind.Unspecified:
  185.                     kind = XsdDateTimeKind.Unspecified;
  186.                     break;
  187.                 case DateTimeKind.Utc:
  188.                     kind = XsdDateTimeKind.Zulu;
  189.                     break;
  190.                 default:
  191.                    
  192.                    
  193.                     {
  194.                         Debug.Assert(dateTime.Kind == DateTimeKind.Local, "Unknown DateTimeKind: " + dateTime.Kind);
  195.                         TimeSpan utcOffset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
  196.                        
  197.                         if (utcOffset.Ticks < 0) {
  198.                             kind = XsdDateTimeKind.LocalWestOfZulu;
  199.                             zoneHour = -utcOffset.Hours;
  200.                             zoneMinute = -utcOffset.Minutes;
  201.                         }
  202.                         else {
  203.                             kind = XsdDateTimeKind.LocalEastOfZulu;
  204.                             zoneHour = utcOffset.Hours;
  205.                             zoneMinute = utcOffset.Minutes;
  206.                         }
  207.                         break;
  208.                     }
  209.                     break;
  210.             }
  211.            
  212.             extra = (uint)(((int)code << TypeShift) | ((int)kind << KindShift) | (zoneHour << ZoneHourShift) | zoneMinute);
  213.         }
  214.        
  215.         /// <summary>
  216.         /// Returns auxiliary enumeration of XSD date type
  217.         /// </summary>
  218.         private DateTimeTypeCode InternalTypeCode {
  219.             get { return (DateTimeTypeCode)((extra & TypeMask) >> TypeShift); }
  220.         }
  221.        
  222.         /// <summary>
  223.         /// Returns geographical "position" of the value
  224.         /// </summary>
  225.         private XsdDateTimeKind InternalKind {
  226.             get { return (XsdDateTimeKind)((extra & KindMask) >> KindShift); }
  227.         }
  228.        
  229.         /// <summary>
  230.         /// Returns XmlTypeCode of the value being stored
  231.         /// </summary>
  232.         public XmlTypeCode TypeCode {
  233.             get { return typeCodes[(int)InternalTypeCode]; }
  234.         }
  235.        
  236.         /// <summary>
  237.         /// Returns whether object represent local, UTC or unspecified time
  238.         /// </summary>
  239.         public DateTimeKind Kind {
  240.             get {
  241.                 switch (InternalKind) {
  242.                     case XsdDateTimeKind.Unspecified:
  243.                         return DateTimeKind.Unspecified;
  244.                     case XsdDateTimeKind.Zulu:
  245.                         return DateTimeKind.Utc;
  246.                     default:
  247.                         // XsdDateTimeKind.LocalEastOfZulu:
  248.                         // XsdDateTimeKind.LocalWestOfZulu:
  249.                         return DateTimeKind.Local;
  250.                 }
  251.             }
  252.         }
  253.        
  254.         /// <summary>
  255.         /// Returns the year part of XsdDateTime
  256.         /// The returned value is integer between 1 and 9999
  257.         /// </summary>
  258.         public int Year {
  259.             get { return dt.Year; }
  260.         }
  261.        
  262.         /// <summary>
  263.         /// Returns the month part of XsdDateTime
  264.         /// The returned value is integer between 1 and 12
  265.         /// </summary>
  266.         public int Month {
  267.             get { return dt.Month; }
  268.         }
  269.        
  270.         /// <summary>
  271.         /// Returns the day of the month part of XsdDateTime
  272.         /// The returned value is integer between 1 and 31
  273.         /// </summary>
  274.         public int Day {
  275.             get { return dt.Day; }
  276.         }
  277.        
  278.         /// <summary>
  279.         /// Returns the hour part of XsdDateTime
  280.         /// The returned value is integer between 0 and 23
  281.         /// </summary>
  282.         public int Hour {
  283.             get { return dt.Hour; }
  284.         }
  285.        
  286.         /// <summary>
  287.         /// Returns the minute part of XsdDateTime
  288.         /// The returned value is integer between 0 and 60
  289.         /// </summary>
  290.         public int Minute {
  291.             get { return dt.Minute; }
  292.         }
  293.        
  294.         /// <summary>
  295.         /// Returns the second part of XsdDateTime
  296.         /// The returned value is integer between 0 and 60
  297.         /// </summary>
  298.         public int Second {
  299.             get { return dt.Second; }
  300.         }
  301.        
  302.         /// <summary>
  303.         /// Returns number of ticks in the fraction of the second
  304.         /// The returned value is integer between 0 and 9999999
  305.         /// </summary>
  306.         public int Fraction {
  307.             get { return (int)(dt.Ticks - new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second).Ticks); }
  308.         }
  309.        
  310.         /// <summary>
  311.         /// Returns the hour part of the time zone
  312.         /// The returned value is integer between -13 and 13
  313.         /// </summary>
  314.         public int ZoneHour {
  315.             get {
  316.                 uint result = (extra & ZoneHourMask) >> ZoneHourShift;
  317.                 return (int)result;
  318.             }
  319.         }
  320.        
  321.         /// <summary>
  322.         /// Returns the minute part of the time zone
  323.         /// The returned value is integer between 0 and 60
  324.         /// </summary>
  325.         public int ZoneMinute {
  326.             get {
  327.                 uint result = (extra & ZoneMinuteMask);
  328.                 return (int)result;
  329.             }
  330.         }
  331.        
  332.         public DateTime ToZulu()
  333.         {
  334.             switch (InternalKind) {
  335.                 case XsdDateTimeKind.Zulu:
  336.                     // set it to UTC
  337.                     return new DateTime(dt.Ticks, DateTimeKind.Utc);
  338.                 case XsdDateTimeKind.LocalEastOfZulu:
  339.                     // Adjust to UTC and then convert to local in the current time zone
  340.                     return new DateTime(dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
  341.                 case XsdDateTimeKind.LocalWestOfZulu:
  342.                     // Adjust to UTC and then convert to local in the current time zone
  343.                     return new DateTime(dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0)).Ticks, DateTimeKind.Utc);
  344.                 default:
  345.                     return dt;
  346.             }
  347.         }
  348.        
  349.         /// <summary>
  350.         /// Cast to DateTime
  351.         /// The following table describes the behaviors of getting the default value
  352.         /// when a certain year/month/day values are missing.
  353.         ///
  354.         /// An "X" means that the value exists. And "--" means that value is missing.
  355.         ///
  356.         /// Year Month Day => ResultYear ResultMonth ResultDay Note
  357.         ///
  358.         /// X X X Parsed year Parsed month Parsed day
  359.         /// X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month.
  360.         /// X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year.
  361.         /// X -- -- Parsed year First month First day If we have only the year, assume the first day of that year.
  362.         ///
  363.         /// -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year.
  364.         /// -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day.
  365.         /// -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month.
  366.         /// -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date.
  367.         /// </summary>
  368.         public static implicit operator DateTime(XsdDateTime xdt)
  369.         {
  370.             DateTime result;
  371.             switch (xdt.InternalTypeCode) {
  372.                 case DateTimeTypeCode.GMonth:
  373.                 case DateTimeTypeCode.GDay:
  374.                     result = new DateTime(DateTime.Now.Year, xdt.Month, xdt.Day);
  375.                     break;
  376.                 case DateTimeTypeCode.Time:
  377.                     //back to DateTime.Now
  378.                     DateTime currentDateTime = DateTime.Now;
  379.                     TimeSpan addDiff = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day) - new DateTime(xdt.Year, xdt.Month, xdt.Day);
  380.                     result = xdt.dt.Add(addDiff);
  381.                     break;
  382.                 default:
  383.                     result = xdt.dt;
  384.                     break;
  385.             }
  386.            
  387.             switch (xdt.InternalKind) {
  388.                 case XsdDateTimeKind.Zulu:
  389.                     // set it to UTC
  390.                     result = new DateTime(result.Ticks, DateTimeKind.Utc);
  391.                     break;
  392.                 case XsdDateTimeKind.LocalEastOfZulu:
  393.                     // Adjust to UTC and then convert to local in the current time zone
  394.                     try {
  395.                         result = result.Subtract(new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0));
  396.                         //NOTE new TimeSpan() will not throw ArgumentOutOfRange as the zone values have already been checked to be less than 99
  397.                     }
  398.                     catch (ArgumentOutOfRangeException) {
  399.                         return new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Local);
  400.                     }
  401.                     result = new DateTime(result.Ticks, DateTimeKind.Utc).ToLocalTime();
  402.                     break;
  403.                 case XsdDateTimeKind.LocalWestOfZulu:
  404.                     // Adjust to UTC and then convert to local in the current time zone
  405.                     try {
  406.                         result = result.Add(new TimeSpan(xdt.ZoneHour, xdt.ZoneMinute, 0));
  407.                         //NOTE new TimeSpan() will not throw ArgumentOutOfRange as the zone values have already been checked to be less than 99
  408.                     }
  409.                     catch (ArgumentOutOfRangeException) {
  410.                         return new DateTime(DateTime.MaxValue.Ticks, DateTimeKind.Local);
  411.                     }
  412.                     result = new DateTime(result.Ticks, DateTimeKind.Utc).ToLocalTime();
  413.                     break;
  414.                 default:
  415.                     break;
  416.             }
  417.             return result;
  418.         }
  419.        
  420.         /// <summary>
  421.         /// Compares two DateTime values, returning an integer that indicates
  422.         /// their relationship.
  423.         /// </summary>
  424.         public static int Compare(XsdDateTime left, XsdDateTime right)
  425.         {
  426.             if (left.extra == right.extra) {
  427.                 return DateTime.Compare(left.dt, right.dt);
  428.             }
  429.             else {
  430.                 // Xsd types should be the same for it to be comparable
  431.                 if (left.InternalTypeCode != right.InternalTypeCode) {
  432.                     throw new ArgumentException(Res.GetString(Res.Sch_XsdDateTimeCompare, left.TypeCode, right.TypeCode));
  433.                 }
  434.                 // Convert both to UTC
  435.                 return DateTime.Compare(left.GetZuluDateTime(), right.GetZuluDateTime());
  436.                
  437.             }
  438.         }
  439.        
  440.         // Compares this DateTime to a given object. This method provides an
  441.         // implementation of the IComparable interface. The object
  442.         // argument must be another DateTime, or otherwise an exception
  443.         // occurs. Null is considered less than any instance.
  444.         //
  445.         // Returns a value less than zero if this object
  446.         /// <include file='doc\DateTime.uex' path='docs/doc[@for="DateTime.CompareTo"]/*' />
  447.         public int CompareTo(object value)
  448.         {
  449.             if (value == null)
  450.                 return 1;
  451.             return Compare(this, (XsdDateTime)value);
  452.         }
  453.        
  454.         /// <summary>
  455.         /// Serialization to a string
  456.         /// </summary>
  457.         public override string ToString()
  458.         {
  459.             StringBuilder sb = new StringBuilder(64);
  460.             char[] text;
  461.             switch (InternalTypeCode) {
  462.                 case DateTimeTypeCode.DateTime:
  463.                     PrintDate(sb);
  464.                     sb.Append('T');
  465.                     PrintTime(sb);
  466.                     break;
  467.                 case DateTimeTypeCode.Time:
  468.                     PrintTime(sb);
  469.                     break;
  470.                 case DateTimeTypeCode.Date:
  471.                     PrintDate(sb);
  472.                     break;
  473.                 case DateTimeTypeCode.GYearMonth:
  474.                     text = new char[Lzyyyy_MM];
  475.                     IntToCharArray(text, 0, Year, 4);
  476.                     text[Lzyyyy] = '-';
  477.                     ShortToCharArray(text, Lzyyyy_, Month);
  478.                     sb.Append(text);
  479.                     break;
  480.                 case DateTimeTypeCode.GYear:
  481.                     text = new char[Lzyyyy];
  482.                     IntToCharArray(text, 0, Year, 4);
  483.                     sb.Append(text);
  484.                     break;
  485.                 case DateTimeTypeCode.GMonthDay:
  486.                     text = new char[Lz__mm_dd];
  487.                     text[0] = '-';
  488.                     text[Lz_] = '-';
  489.                     ShortToCharArray(text, Lz__, Month);
  490.                     text[Lz__mm] = '-';
  491.                     ShortToCharArray(text, Lz__mm_, Day);
  492.                     sb.Append(text);
  493.                     break;
  494.                 case DateTimeTypeCode.GDay:
  495.                     text = new char[Lz___dd];
  496.                     text[0] = '-';
  497.                     text[Lz_] = '-';
  498.                     text[Lz__] = '-';
  499.                     ShortToCharArray(text, Lz___, Day);
  500.                     sb.Append(text);
  501.                     break;
  502.                 case DateTimeTypeCode.GMonth:
  503.                     text = new char[Lz__mm__];
  504.                     text[0] = '-';
  505.                     text[Lz_] = '-';
  506.                     ShortToCharArray(text, Lz__, Month);
  507.                     text[Lz__mm] = '-';
  508.                     text[Lz__mm_] = '-';
  509.                     sb.Append(text);
  510.                     break;
  511.             }
  512.             PrintZone(sb);
  513.             return sb.ToString();
  514.         }
  515.        
  516.         // Serialize year, month and day
  517.         private void PrintDate(StringBuilder sb)
  518.         {
  519.             char[] text = new char[Lzyyyy_MM_dd];
  520.             IntToCharArray(text, 0, Year, 4);
  521.             text[Lzyyyy] = '-';
  522.             ShortToCharArray(text, Lzyyyy_, Month);
  523.             text[Lzyyyy_MM] = '-';
  524.             ShortToCharArray(text, Lzyyyy_MM_, Day);
  525.             sb.Append(text);
  526.         }
  527.        
  528.         // Serialize hour, minute, second and fraction
  529.         private void PrintTime(StringBuilder sb)
  530.         {
  531.             char[] text = new char[LzHH_mm_ss];
  532.             ShortToCharArray(text, 0, Hour);
  533.             text[LzHH] = ':';
  534.             ShortToCharArray(text, LzHH_, Minute);
  535.             text[LzHH_mm] = ':';
  536.             ShortToCharArray(text, LzHH_mm_, Second);
  537.             sb.Append(text);
  538.             int fraction = Fraction;
  539.             if (fraction != 0) {
  540.                 int fractionDigits = maxFractionDigits;
  541.                 while (fraction % 10 == 0) {
  542.                     fractionDigits--;
  543.                     fraction /= 10;
  544.                 }
  545.                 text = new char[fractionDigits + 1];
  546.                 text[0] = '.';
  547.                 IntToCharArray(text, 1, fraction, fractionDigits);
  548.                 sb.Append(text);
  549.             }
  550.         }
  551.        
  552.         // Serialize time zone
  553.         private void PrintZone(StringBuilder sb)
  554.         {
  555.             char[] text;
  556.             switch (InternalKind) {
  557.                 case XsdDateTimeKind.Zulu:
  558.                     sb.Append('Z');
  559.                     break;
  560.                 case XsdDateTimeKind.LocalWestOfZulu:
  561.                     text = new char[Lz_zz_zz];
  562.                     text[0] = '-';
  563.                     ShortToCharArray(text, Lz_, ZoneHour);
  564.                     text[Lz_zz] = ':';
  565.                     ShortToCharArray(text, Lz_zz_, ZoneMinute);
  566.                     sb.Append(text);
  567.                     break;
  568.                 case XsdDateTimeKind.LocalEastOfZulu:
  569.                     text = new char[Lz_zz_zz];
  570.                     text[0] = '+';
  571.                     ShortToCharArray(text, Lz_, ZoneHour);
  572.                     text[Lz_zz] = ':';
  573.                     ShortToCharArray(text, Lz_zz_, ZoneMinute);
  574.                     sb.Append(text);
  575.                     break;
  576.                 default:
  577.                     // do nothing
  578.                     break;
  579.             }
  580.         }
  581.        
  582.         // Serialize integer into character array starting with index [start].
  583.         // Number of digits is set by [digits]
  584.         private void IntToCharArray(char[] text, int start, int value, int digits)
  585.         {
  586.             while (digits-- != 0) {
  587.                 text[start + digits] = (char)(value % 10 + '0');
  588.                 value /= 10;
  589.             }
  590.         }
  591.        
  592.         // Serialize two digit integer into character array starting with index [start].
  593.         private void ShortToCharArray(char[] text, int start, int value)
  594.         {
  595.             text[start] = (char)(value / 10 + '0');
  596.             text[start + 1] = (char)(value % 10 + '0');
  597.         }
  598.        
  599.         // Auxiliary for compare.
  600.         // Returns UTC DateTime
  601.         private DateTime GetZuluDateTime()
  602.         {
  603.             switch (InternalKind) {
  604.                 case XsdDateTimeKind.Zulu:
  605.                     return dt;
  606.                 case XsdDateTimeKind.LocalEastOfZulu:
  607.                     return dt.Subtract(new TimeSpan(ZoneHour, ZoneMinute, 0));
  608.                 case XsdDateTimeKind.LocalWestOfZulu:
  609.                     return dt.Add(new TimeSpan(ZoneHour, ZoneMinute, 0));
  610.                 default:
  611.                     return dt.ToUniversalTime();
  612.             }
  613.         }
  614.        
  615.        
  616.         private static readonly XmlTypeCode[] typeCodes = {XmlTypeCode.DateTime, XmlTypeCode.Time, XmlTypeCode.Date, XmlTypeCode.GYearMonth, XmlTypeCode.GYear, XmlTypeCode.GMonthDay, XmlTypeCode.GDay, XmlTypeCode.GMonth};
  617.        
  618.         // Parsing string according to XML schema spec
  619.         struct Parser
  620.         {
  621.             private const int leapYear = 1904;
  622.             private const int firstMonth = 1;
  623.             private const int firstDay = 1;
  624.            
  625.             public DateTimeTypeCode typeCode;
  626.             public int year;
  627.             public int month;
  628.             public int day;
  629.             public int hour;
  630.             public int minute;
  631.             public int second;
  632.             public int fraction;
  633.             public XsdDateTimeKind kind;
  634.             public int zoneHour;
  635.             public int zoneMinute;
  636.            
  637.             private string text;
  638.             private int length;
  639.            
  640.             public bool Parse(string text, XsdDateTimeFlags kinds)
  641.             {
  642.                 this.text = text;
  643.                 this.length = text.Length;
  644.                
  645.                 // Skip leading withitespace
  646.                 int start = 0;
  647.                 while (start < length && char.IsWhiteSpace(text[start])) {
  648.                     start++;
  649.                 }
  650.                 // Choose format starting from the most common and trying not to reparse the same thing too many times
  651.                 if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz)) {
  652.                     if (ParseDate(start)) {
  653.                         if (Test(kinds, XsdDateTimeFlags.DateTime)) {
  654.                             if (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT)) {
  655.                                 typeCode = DateTimeTypeCode.DateTime;
  656.                                 return true;
  657.                             }
  658.                         }
  659.                         if (Test(kinds, XsdDateTimeFlags.Date)) {
  660.                             if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd)) {
  661.                                 typeCode = DateTimeTypeCode.Date;
  662.                                 return true;
  663.                             }
  664.                         }
  665.                         if (Test(kinds, XsdDateTimeFlags.XdrDateTime)) {
  666.                             if (ParseZoneAndWhitespace(start + Lzyyyy_MM_dd) || (ParseChar(start + Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + Lzyyyy_MM_ddT))) {
  667.                                 typeCode = DateTimeTypeCode.XdrDateTime;
  668.                                 return true;
  669.                             }
  670.                         }
  671.                         if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz)) {
  672.                             if (ParseChar(start + Lzyyyy_MM_dd, 'T')) {
  673.                                 if (ParseTimeAndWhitespace(start + Lzyyyy_MM_ddT)) {
  674.                                     typeCode = DateTimeTypeCode.XdrDateTime;
  675.                                     return true;
  676.                                 }
  677.                             }
  678.                             else {
  679.                                 typeCode = DateTimeTypeCode.XdrDateTime;
  680.                                 return true;
  681.                             }
  682.                         }
  683.                     }
  684.                 }
  685.                
  686.                 if (Test(kinds, XsdDateTimeFlags.Time)) {
  687.                     if (ParseTimeAndZoneAndWhitespace(start)) {
  688.                         //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
  689.                         year = leapYear;
  690.                         month = firstMonth;
  691.                         day = firstDay;
  692.                         typeCode = DateTimeTypeCode.Time;
  693.                         return true;
  694.                     }
  695.                 }
  696.                 if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz)) {
  697.                     if (ParseTimeAndWhitespace(start)) {
  698.                         //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
  699.                         year = leapYear;
  700.                         month = firstMonth;
  701.                         day = firstDay;
  702.                         typeCode = DateTimeTypeCode.Time;
  703.                         return true;
  704.                     }
  705.                 }
  706.                
  707.                 if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear)) {
  708.                     if (Parse4Dig(start, ref year) && 1 <= year) {
  709.                         if (Test(kinds, XsdDateTimeFlags.GYearMonth)) {
  710.                             if (ParseChar(start + Lzyyyy, '-') && Parse2Dig(start + Lzyyyy_, ref month) && 1 <= month && month <= 12 && ParseZoneAndWhitespace(start + Lzyyyy_MM)) {
  711.                                 day = firstDay;
  712.                                 typeCode = DateTimeTypeCode.GYearMonth;
  713.                                 return true;
  714.                             }
  715.                         }
  716.                         if (Test(kinds, XsdDateTimeFlags.GYear)) {
  717.                             if (ParseZoneAndWhitespace(start + Lzyyyy)) {
  718.                                 month = firstMonth;
  719.                                 day = firstDay;
  720.                                 typeCode = DateTimeTypeCode.GYear;
  721.                                 return true;
  722.                             }
  723.                         }
  724.                     }
  725.                 }
  726.                 if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth)) {
  727.                     if (ParseChar(start, '-') && ParseChar(start + Lz_, '-') && Parse2Dig(start + Lz__, ref month) && 1 <= month && month <= 12) {
  728.                         if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + Lz__mm, '-')) {
  729.                             if (Parse2Dig(start + Lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) && ParseZoneAndWhitespace(start + Lz__mm_dd)) {
  730.                                 year = leapYear;
  731.                                 typeCode = DateTimeTypeCode.GMonthDay;
  732.                                 return true;
  733.                             }
  734.                         }
  735.                         if (Test(kinds, XsdDateTimeFlags.GMonth)) {
  736.                             if (ParseZoneAndWhitespace(start + Lz__mm) || (ParseChar(start + Lz__mm, '-') && ParseChar(start + Lz__mm_, '-') && ParseZoneAndWhitespace(start + Lz__mm__))) {
  737.                                 year = leapYear;
  738.                                 day = firstDay;
  739.                                 typeCode = DateTimeTypeCode.GMonth;
  740.                                 return true;
  741.                             }
  742.                         }
  743.                     }
  744.                    
  745.                 }
  746.                 if (Test(kinds, XsdDateTimeFlags.GDay)) {
  747.                    
  748.                     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)) {
  749.                         year = leapYear;
  750.                         month = firstMonth;
  751.                         typeCode = DateTimeTypeCode.GDay;
  752.                         return true;
  753.                     }
  754.                 }
  755.                 return false;
  756.             }
  757.            
  758.            
  759.             private bool ParseDate(int start)
  760.             {
  761.                 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);
  762.             }
  763.            
  764.             private bool ParseTimeAndZoneAndWhitespace(int start)
  765.             {
  766.                 if (ParseTime(ref start)) {
  767.                     if (ParseZoneAndWhitespace(start)) {
  768.                         return true;
  769.                     }
  770.                 }
  771.                 return false;
  772.             }
  773.            
  774.             private bool ParseTimeAndWhitespace(int start)
  775.             {
  776.                 if (ParseTime(ref start)) {
  777.                     while (start < length) {
  778.                         //&& char.IsWhiteSpace(text[start])) {
  779.                         start++;
  780.                     }
  781.                     return start == length;
  782.                 }
  783.                 return false;
  784.             }
  785.            
  786.             static int[] Power10 = new int[maxFractionDigits] {-1, 10, 100, 1000, 10000, 100000, 1000000};
  787.             private bool ParseTime(ref int start)
  788.             {
  789.                 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) {
  790.                     start += LzHH_mm_ss;
  791.                     if (ParseChar(start, '.')) {
  792.                         // Parse factional part of seconds
  793.                         // We allow any number of digits, but keep only first 7
  794.                         this.fraction = 0;
  795.                         int fractionDigits = 0;
  796.                         int round = 0;
  797.                         while (++start < length) {
  798.                             int d = text[start] - '0';
  799.                             if (9u < (uint)d) {
  800.                                 // d < 0 || 9 < d
  801.                                 break;
  802.                             }
  803.                             if (fractionDigits < maxFractionDigits) {
  804.                                 this.fraction = (this.fraction * 10) + d;
  805.                             }
  806.                             else if (fractionDigits == maxFractionDigits) {
  807.                                 if (5 < d) {
  808.                                     round = 1;
  809.                                 }
  810.                                 else if (d == 5) {
  811.                                     round = -1;
  812.                                 }
  813.                             }
  814.                             else if (round < 0 && d != 0) {
  815.                                 round = 1;
  816.                             }
  817.                             fractionDigits++;
  818.                         }
  819.                         if (fractionDigits < maxFractionDigits) {
  820.                             if (fractionDigits == 0) {
  821.                                 return false;
  822.                                 // cannot end with .
  823.                             }
  824.                             fraction *= Power10[maxFractionDigits - fractionDigits];
  825.                         }
  826.                         else {
  827.                             if (round < 0) {
  828.                                 round = fraction & 1;
  829.                             }
  830.                             fraction += round;
  831.                         }
  832.                     }
  833.                     return true;
  834.                 }
  835.                 // cleanup - conflict with gYear
  836.                 hour = 0;
  837.                 return false;
  838.             }
  839.            
  840.             private bool ParseZoneAndWhitespace(int start)
  841.             {
  842.                 if (start < length) {
  843.                     char ch = text[start];
  844.                     if (ch == 'Z' || ch == 'z') {
  845.                         kind = XsdDateTimeKind.Zulu;
  846.                         start++;
  847.                     }
  848.                     else if (start + 5 < length) {
  849.                         if (Parse2Dig(start + Lz_, ref zoneHour) && zoneHour <= 99 && ParseChar(start + Lz_zz, ':') && Parse2Dig(start + Lz_zz_, ref zoneMinute) && zoneMinute <= 99) {
  850.                             if (ch == '-') {
  851.                                 kind = XsdDateTimeKind.LocalWestOfZulu;
  852.                                 start += Lz_zz_zz;
  853.                             }
  854.                             else if (ch == '+') {
  855.                                 kind = XsdDateTimeKind.LocalEastOfZulu;
  856.                                 start += Lz_zz_zz;
  857.                             }
  858.                         }
  859.                     }
  860.                 }
  861.                 while (start < length && char.IsWhiteSpace(text[start])) {
  862.                     start++;
  863.                 }
  864.                 return start == length;
  865.             }
  866.            
  867.            
  868.             private bool Parse4Dig(int start, ref int num)
  869.             {
  870.                 if (start + 3 < length) {
  871.                     int d4 = text[start] - '0';
  872.                     int d3 = text[start + 1] - '0';
  873.                     int d2 = text[start + 2] - '0';
  874.                     int d1 = text[start + 3] - '0';
  875.                     if (0 <= d4 && d4 < 10 && 0 <= d3 && d3 < 10 && 0 <= d2 && d2 < 10 && 0 <= d1 && d1 < 10) {
  876.                         num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
  877.                         return true;
  878.                     }
  879.                 }
  880.                 return false;
  881.             }
  882.            
  883.             private bool Parse2Dig(int start, ref int num)
  884.             {
  885.                 if (start + 1 < length) {
  886.                     int d2 = text[start] - '0';
  887.                     int d1 = text[start + 1] - '0';
  888.                     if (0 <= d2 && d2 < 10 && 0 <= d1 && d1 < 10) {
  889.                         num = d2 * 10 + d1;
  890.                         return true;
  891.                     }
  892.                 }
  893.                 return false;
  894.             }
  895.            
  896.             private bool ParseChar(int start, char ch)
  897.             {
  898.                 return start < length && text[start] == ch;
  899.             }
  900.            
  901.             private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right)
  902.             {
  903.                 return (left & right) != 0;
  904.             }
  905.            
  906.         }
  907.     }
  908. }

Developer Fusion