#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; namespace Newtonsoft.Json.Utilities { internal enum ParserTimeZone { Unspecified = 0, Utc = 1, LocalWestOfUtc = 2, LocalEastOfUtc = 3 } internal struct DateTimeParser { static DateTimeParser() { Power10 = new[] { -1, 10, 100, 1000, 10000, 100000, 1000000 }; Lzyyyy = "yyyy".Length; Lzyyyy_ = "yyyy-".Length; Lzyyyy_MM = "yyyy-MM".Length; Lzyyyy_MM_ = "yyyy-MM-".Length; Lzyyyy_MM_dd = "yyyy-MM-dd".Length; Lzyyyy_MM_ddT = "yyyy-MM-ddT".Length; LzHH = "HH".Length; LzHH_ = "HH:".Length; LzHH_mm = "HH:mm".Length; LzHH_mm_ = "HH:mm:".Length; LzHH_mm_ss = "HH:mm:ss".Length; Lz_ = "-".Length; Lz_zz = "-zz".Length; } public int Year; public int Month; public int Day; public int Hour; public int Minute; public int Second; public int Fraction; public int ZoneHour; public int ZoneMinute; public ParserTimeZone Zone; private char[] _text; private int _end; private static readonly int[] Power10; private static readonly int Lzyyyy; private static readonly int Lzyyyy_; private static readonly int Lzyyyy_MM; private static readonly int Lzyyyy_MM_; private static readonly int Lzyyyy_MM_dd; private static readonly int Lzyyyy_MM_ddT; private static readonly int LzHH; private static readonly int LzHH_; private static readonly int LzHH_mm; private static readonly int LzHH_mm_; private static readonly int LzHH_mm_ss; private static readonly int Lz_; private static readonly int Lz_zz; private const short MaxFractionDigits = 7; public bool Parse(char[] text, int startIndex, int length) { _text = text; _end = startIndex + length; if (ParseDate(startIndex) && ParseChar(Lzyyyy_MM_dd + startIndex, 'T') && ParseTimeAndZoneAndWhitespace(Lzyyyy_MM_ddT + startIndex)) { return true; } return false; } private bool ParseDate(int start) { return (Parse4Digit(start, out Year) && 1 <= Year && ParseChar(start + Lzyyyy, '-') && Parse2Digit(start + Lzyyyy_, out Month) && 1 <= Month && Month <= 12 && ParseChar(start + Lzyyyy_MM, '-') && Parse2Digit(start + Lzyyyy_MM_, out Day) && 1 <= Day && Day <= DateTime.DaysInMonth(Year, Month)); } private bool ParseTimeAndZoneAndWhitespace(int start) { return (ParseTime(ref start) && ParseZone(start)); } private bool ParseTime(ref int start) { if (!(Parse2Digit(start, out Hour) && Hour <= 24 && ParseChar(start + LzHH, ':') && Parse2Digit(start + LzHH_, out Minute) && Minute < 60 && ParseChar(start + LzHH_mm, ':') && Parse2Digit(start + LzHH_mm_, out Second) && Second < 60 && (Hour != 24 || (Minute == 0 && Second == 0)))) // hour can be 24 if minute/second is zero) { return false; } start += LzHH_mm_ss; if (ParseChar(start, '.')) { Fraction = 0; int numberOfDigits = 0; while (++start < _end && numberOfDigits < MaxFractionDigits) { int digit = _text[start] - '0'; if (digit < 0 || digit > 9) { break; } Fraction = (Fraction * 10) + digit; numberOfDigits++; } if (numberOfDigits < MaxFractionDigits) { if (numberOfDigits == 0) { return false; } Fraction *= Power10[MaxFractionDigits - numberOfDigits]; } if (Hour == 24 && Fraction != 0) { return false; } } return true; } private bool ParseZone(int start) { if (start < _end) { char ch = _text[start]; if (ch == 'Z' || ch == 'z') { Zone = ParserTimeZone.Utc; start++; } else { if (start + 2 < _end && Parse2Digit(start + Lz_, out ZoneHour) && ZoneHour <= 99) { switch (ch) { case '-': Zone = ParserTimeZone.LocalWestOfUtc; start += Lz_zz; break; case '+': Zone = ParserTimeZone.LocalEastOfUtc; start += Lz_zz; break; } } if (start < _end) { if (ParseChar(start, ':')) { start += 1; if (start + 1 < _end && Parse2Digit(start, out ZoneMinute) && ZoneMinute <= 99) { start += 2; } } else { if (start + 1 < _end && Parse2Digit(start, out ZoneMinute) && ZoneMinute <= 99) { start += 2; } } } } } return (start == _end); } private bool Parse4Digit(int start, out int num) { if (start + 3 < _end) { int digit1 = _text[start] - '0'; int digit2 = _text[start + 1] - '0'; int digit3 = _text[start + 2] - '0'; int digit4 = _text[start + 3] - '0'; if (0 <= digit1 && digit1 < 10 && 0 <= digit2 && digit2 < 10 && 0 <= digit3 && digit3 < 10 && 0 <= digit4 && digit4 < 10) { num = (((((digit1 * 10) + digit2) * 10) + digit3) * 10) + digit4; return true; } } num = 0; return false; } private bool Parse2Digit(int start, out int num) { if (start + 1 < _end) { int digit1 = _text[start] - '0'; int digit2 = _text[start + 1] - '0'; if (0 <= digit1 && digit1 < 10 && 0 <= digit2 && digit2 < 10) { num = (digit1 * 10) + digit2; return true; } } num = 0; return false; } private bool ParseChar(int start, char ch) { return (start < _end && _text[start] == ch); } } }