You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
8.9 KiB

  1. #region License
  2. // Copyright (c) 2007 James Newton-King
  3. //
  4. // Permission is hereby granted, free of charge, to any person
  5. // obtaining a copy of this software and associated documentation
  6. // files (the "Software"), to deal in the Software without
  7. // restriction, including without limitation the rights to use,
  8. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following
  11. // conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23. // OTHER DEALINGS IN THE SOFTWARE.
  24. #endregion
  25. using System;
  26. namespace Newtonsoft.Json.Utilities
  27. {
  28. internal enum ParserTimeZone
  29. {
  30. Unspecified = 0,
  31. Utc = 1,
  32. LocalWestOfUtc = 2,
  33. LocalEastOfUtc = 3
  34. }
  35. internal struct DateTimeParser
  36. {
  37. static DateTimeParser()
  38. {
  39. Power10 = new[] { -1, 10, 100, 1000, 10000, 100000, 1000000 };
  40. Lzyyyy = "yyyy".Length;
  41. Lzyyyy_ = "yyyy-".Length;
  42. Lzyyyy_MM = "yyyy-MM".Length;
  43. Lzyyyy_MM_ = "yyyy-MM-".Length;
  44. Lzyyyy_MM_dd = "yyyy-MM-dd".Length;
  45. Lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
  46. LzHH = "HH".Length;
  47. LzHH_ = "HH:".Length;
  48. LzHH_mm = "HH:mm".Length;
  49. LzHH_mm_ = "HH:mm:".Length;
  50. LzHH_mm_ss = "HH:mm:ss".Length;
  51. Lz_ = "-".Length;
  52. Lz_zz = "-zz".Length;
  53. }
  54. public int Year;
  55. public int Month;
  56. public int Day;
  57. public int Hour;
  58. public int Minute;
  59. public int Second;
  60. public int Fraction;
  61. public int ZoneHour;
  62. public int ZoneMinute;
  63. public ParserTimeZone Zone;
  64. private char[] _text;
  65. private int _end;
  66. private static readonly int[] Power10;
  67. private static readonly int Lzyyyy;
  68. private static readonly int Lzyyyy_;
  69. private static readonly int Lzyyyy_MM;
  70. private static readonly int Lzyyyy_MM_;
  71. private static readonly int Lzyyyy_MM_dd;
  72. private static readonly int Lzyyyy_MM_ddT;
  73. private static readonly int LzHH;
  74. private static readonly int LzHH_;
  75. private static readonly int LzHH_mm;
  76. private static readonly int LzHH_mm_;
  77. private static readonly int LzHH_mm_ss;
  78. private static readonly int Lz_;
  79. private static readonly int Lz_zz;
  80. private const short MaxFractionDigits = 7;
  81. public bool Parse(char[] text, int startIndex, int length)
  82. {
  83. _text = text;
  84. _end = startIndex + length;
  85. if (ParseDate(startIndex) && ParseChar(Lzyyyy_MM_dd + startIndex, 'T') && ParseTimeAndZoneAndWhitespace(Lzyyyy_MM_ddT + startIndex))
  86. {
  87. return true;
  88. }
  89. return false;
  90. }
  91. private bool ParseDate(int start)
  92. {
  93. return (Parse4Digit(start, out Year)
  94. && 1 <= Year
  95. && ParseChar(start + Lzyyyy, '-')
  96. && Parse2Digit(start + Lzyyyy_, out Month)
  97. && 1 <= Month
  98. && Month <= 12
  99. && ParseChar(start + Lzyyyy_MM, '-')
  100. && Parse2Digit(start + Lzyyyy_MM_, out Day)
  101. && 1 <= Day
  102. && Day <= DateTime.DaysInMonth(Year, Month));
  103. }
  104. private bool ParseTimeAndZoneAndWhitespace(int start)
  105. {
  106. return (ParseTime(ref start) && ParseZone(start));
  107. }
  108. private bool ParseTime(ref int start)
  109. {
  110. if (!(Parse2Digit(start, out Hour)
  111. && Hour <= 24
  112. && ParseChar(start + LzHH, ':')
  113. && Parse2Digit(start + LzHH_, out Minute)
  114. && Minute < 60
  115. && ParseChar(start + LzHH_mm, ':')
  116. && Parse2Digit(start + LzHH_mm_, out Second)
  117. && Second < 60
  118. && (Hour != 24 || (Minute == 0 && Second == 0)))) // hour can be 24 if minute/second is zero)
  119. {
  120. return false;
  121. }
  122. start += LzHH_mm_ss;
  123. if (ParseChar(start, '.'))
  124. {
  125. Fraction = 0;
  126. int numberOfDigits = 0;
  127. while (++start < _end && numberOfDigits < MaxFractionDigits)
  128. {
  129. int digit = _text[start] - '0';
  130. if (digit < 0 || digit > 9)
  131. {
  132. break;
  133. }
  134. Fraction = (Fraction * 10) + digit;
  135. numberOfDigits++;
  136. }
  137. if (numberOfDigits < MaxFractionDigits)
  138. {
  139. if (numberOfDigits == 0)
  140. {
  141. return false;
  142. }
  143. Fraction *= Power10[MaxFractionDigits - numberOfDigits];
  144. }
  145. if (Hour == 24 && Fraction != 0)
  146. {
  147. return false;
  148. }
  149. }
  150. return true;
  151. }
  152. private bool ParseZone(int start)
  153. {
  154. if (start < _end)
  155. {
  156. char ch = _text[start];
  157. if (ch == 'Z' || ch == 'z')
  158. {
  159. Zone = ParserTimeZone.Utc;
  160. start++;
  161. }
  162. else
  163. {
  164. if (start + 2 < _end
  165. && Parse2Digit(start + Lz_, out ZoneHour)
  166. && ZoneHour <= 99)
  167. {
  168. switch (ch)
  169. {
  170. case '-':
  171. Zone = ParserTimeZone.LocalWestOfUtc;
  172. start += Lz_zz;
  173. break;
  174. case '+':
  175. Zone = ParserTimeZone.LocalEastOfUtc;
  176. start += Lz_zz;
  177. break;
  178. }
  179. }
  180. if (start < _end)
  181. {
  182. if (ParseChar(start, ':'))
  183. {
  184. start += 1;
  185. if (start + 1 < _end
  186. && Parse2Digit(start, out ZoneMinute)
  187. && ZoneMinute <= 99)
  188. {
  189. start += 2;
  190. }
  191. }
  192. else
  193. {
  194. if (start + 1 < _end
  195. && Parse2Digit(start, out ZoneMinute)
  196. && ZoneMinute <= 99)
  197. {
  198. start += 2;
  199. }
  200. }
  201. }
  202. }
  203. }
  204. return (start == _end);
  205. }
  206. private bool Parse4Digit(int start, out int num)
  207. {
  208. if (start + 3 < _end)
  209. {
  210. int digit1 = _text[start] - '0';
  211. int digit2 = _text[start + 1] - '0';
  212. int digit3 = _text[start + 2] - '0';
  213. int digit4 = _text[start + 3] - '0';
  214. if (0 <= digit1 && digit1 < 10
  215. && 0 <= digit2 && digit2 < 10
  216. && 0 <= digit3 && digit3 < 10
  217. && 0 <= digit4 && digit4 < 10)
  218. {
  219. num = (((((digit1 * 10) + digit2) * 10) + digit3) * 10) + digit4;
  220. return true;
  221. }
  222. }
  223. num = 0;
  224. return false;
  225. }
  226. private bool Parse2Digit(int start, out int num)
  227. {
  228. if (start + 1 < _end)
  229. {
  230. int digit1 = _text[start] - '0';
  231. int digit2 = _text[start + 1] - '0';
  232. if (0 <= digit1 && digit1 < 10
  233. && 0 <= digit2 && digit2 < 10)
  234. {
  235. num = (digit1 * 10) + digit2;
  236. return true;
  237. }
  238. }
  239. num = 0;
  240. return false;
  241. }
  242. private bool ParseChar(int start, char ch)
  243. {
  244. return (start < _end && _text[start] == ch);
  245. }
  246. }
  247. }