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.

824 lines
29 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. using System.IO;
  27. using System.Xml;
  28. using System.Globalization;
  29. namespace Newtonsoft.Json.Utilities
  30. {
  31. internal static class DateTimeUtils
  32. {
  33. internal static readonly long InitialJavaScriptDateTicks = 621355968000000000;
  34. private const string IsoDateFormat = "yyyy-MM-ddTHH:mm:ss.FFFFFFFK";
  35. private const int DaysPer100Years = 36524;
  36. private const int DaysPer400Years = 146097;
  37. private const int DaysPer4Years = 1461;
  38. private const int DaysPerYear = 365;
  39. private const long TicksPerDay = 864000000000L;
  40. private static readonly int[] DaysToMonth365;
  41. private static readonly int[] DaysToMonth366;
  42. static DateTimeUtils()
  43. {
  44. DaysToMonth365 = new[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
  45. DaysToMonth366 = new[] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
  46. }
  47. public static TimeSpan GetUtcOffset(this DateTime d)
  48. {
  49. #if NET20
  50. return TimeZone.CurrentTimeZone.GetUtcOffset(d);
  51. #else
  52. return TimeZoneInfo.Local.GetUtcOffset(d);
  53. #endif
  54. }
  55. #if !(PORTABLE40 || PORTABLE) || NETSTANDARD1_3
  56. public static XmlDateTimeSerializationMode ToSerializationMode(DateTimeKind kind)
  57. {
  58. switch (kind)
  59. {
  60. case DateTimeKind.Local:
  61. return XmlDateTimeSerializationMode.Local;
  62. case DateTimeKind.Unspecified:
  63. return XmlDateTimeSerializationMode.Unspecified;
  64. case DateTimeKind.Utc:
  65. return XmlDateTimeSerializationMode.Utc;
  66. default:
  67. throw MiscellaneousUtils.CreateArgumentOutOfRangeException(nameof(kind), kind, "Unexpected DateTimeKind value.");
  68. }
  69. }
  70. #else
  71. public static string ToDateTimeFormat(DateTimeKind kind)
  72. {
  73. switch (kind)
  74. {
  75. case DateTimeKind.Local:
  76. return IsoDateFormat;
  77. case DateTimeKind.Unspecified:
  78. return "yyyy-MM-ddTHH:mm:ss.FFFFFFF";
  79. case DateTimeKind.Utc:
  80. return "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ";
  81. default:
  82. throw MiscellaneousUtils.CreateArgumentOutOfRangeException(nameof(kind), kind, "Unexpected DateTimeKind value.");
  83. }
  84. }
  85. #endif
  86. internal static DateTime EnsureDateTime(DateTime value, DateTimeZoneHandling timeZone)
  87. {
  88. switch (timeZone)
  89. {
  90. case DateTimeZoneHandling.Local:
  91. value = SwitchToLocalTime(value);
  92. break;
  93. case DateTimeZoneHandling.Utc:
  94. value = SwitchToUtcTime(value);
  95. break;
  96. case DateTimeZoneHandling.Unspecified:
  97. value = new DateTime(value.Ticks, DateTimeKind.Unspecified);
  98. break;
  99. case DateTimeZoneHandling.RoundtripKind:
  100. break;
  101. default:
  102. throw new ArgumentException("Invalid date time handling value.");
  103. }
  104. return value;
  105. }
  106. private static DateTime SwitchToLocalTime(DateTime value)
  107. {
  108. switch (value.Kind)
  109. {
  110. case DateTimeKind.Unspecified:
  111. return new DateTime(value.Ticks, DateTimeKind.Local);
  112. case DateTimeKind.Utc:
  113. return value.ToLocalTime();
  114. case DateTimeKind.Local:
  115. return value;
  116. }
  117. return value;
  118. }
  119. private static DateTime SwitchToUtcTime(DateTime value)
  120. {
  121. switch (value.Kind)
  122. {
  123. case DateTimeKind.Unspecified:
  124. return new DateTime(value.Ticks, DateTimeKind.Utc);
  125. case DateTimeKind.Utc:
  126. return value;
  127. case DateTimeKind.Local:
  128. return value.ToUniversalTime();
  129. }
  130. return value;
  131. }
  132. private static long ToUniversalTicks(DateTime dateTime)
  133. {
  134. if (dateTime.Kind == DateTimeKind.Utc)
  135. {
  136. return dateTime.Ticks;
  137. }
  138. return ToUniversalTicks(dateTime, dateTime.GetUtcOffset());
  139. }
  140. private static long ToUniversalTicks(DateTime dateTime, TimeSpan offset)
  141. {
  142. // special case min and max value
  143. // they never have a timezone appended to avoid issues
  144. if (dateTime.Kind == DateTimeKind.Utc || dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue)
  145. {
  146. return dateTime.Ticks;
  147. }
  148. long ticks = dateTime.Ticks - offset.Ticks;
  149. if (ticks > 3155378975999999999L)
  150. {
  151. return 3155378975999999999L;
  152. }
  153. if (ticks < 0L)
  154. {
  155. return 0L;
  156. }
  157. return ticks;
  158. }
  159. internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime, TimeSpan offset)
  160. {
  161. long universialTicks = ToUniversalTicks(dateTime, offset);
  162. return UniversialTicksToJavaScriptTicks(universialTicks);
  163. }
  164. internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime)
  165. {
  166. return ConvertDateTimeToJavaScriptTicks(dateTime, true);
  167. }
  168. internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime, bool convertToUtc)
  169. {
  170. long ticks = (convertToUtc) ? ToUniversalTicks(dateTime) : dateTime.Ticks;
  171. return UniversialTicksToJavaScriptTicks(ticks);
  172. }
  173. private static long UniversialTicksToJavaScriptTicks(long universialTicks)
  174. {
  175. long javaScriptTicks = (universialTicks - InitialJavaScriptDateTicks) / 10000;
  176. return javaScriptTicks;
  177. }
  178. internal static DateTime ConvertJavaScriptTicksToDateTime(long javaScriptTicks)
  179. {
  180. DateTime dateTime = new DateTime((javaScriptTicks * 10000) + InitialJavaScriptDateTicks, DateTimeKind.Utc);
  181. return dateTime;
  182. }
  183. #region Parse
  184. internal static bool TryParseDateTimeIso(StringReference text, DateTimeZoneHandling dateTimeZoneHandling, out DateTime dt)
  185. {
  186. DateTimeParser dateTimeParser = new DateTimeParser();
  187. if (!dateTimeParser.Parse(text.Chars, text.StartIndex, text.Length))
  188. {
  189. dt = default(DateTime);
  190. return false;
  191. }
  192. DateTime d = CreateDateTime(dateTimeParser);
  193. long ticks;
  194. switch (dateTimeParser.Zone)
  195. {
  196. case ParserTimeZone.Utc:
  197. d = new DateTime(d.Ticks, DateTimeKind.Utc);
  198. break;
  199. case ParserTimeZone.LocalWestOfUtc:
  200. {
  201. TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0);
  202. ticks = d.Ticks + offset.Ticks;
  203. if (ticks <= DateTime.MaxValue.Ticks)
  204. {
  205. d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
  206. }
  207. else
  208. {
  209. ticks += d.GetUtcOffset().Ticks;
  210. if (ticks > DateTime.MaxValue.Ticks)
  211. {
  212. ticks = DateTime.MaxValue.Ticks;
  213. }
  214. d = new DateTime(ticks, DateTimeKind.Local);
  215. }
  216. break;
  217. }
  218. case ParserTimeZone.LocalEastOfUtc:
  219. {
  220. TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0);
  221. ticks = d.Ticks - offset.Ticks;
  222. if (ticks >= DateTime.MinValue.Ticks)
  223. {
  224. d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
  225. }
  226. else
  227. {
  228. ticks += d.GetUtcOffset().Ticks;
  229. if (ticks < DateTime.MinValue.Ticks)
  230. {
  231. ticks = DateTime.MinValue.Ticks;
  232. }
  233. d = new DateTime(ticks, DateTimeKind.Local);
  234. }
  235. break;
  236. }
  237. }
  238. dt = EnsureDateTime(d, dateTimeZoneHandling);
  239. return true;
  240. }
  241. #if !NET20
  242. internal static bool TryParseDateTimeOffsetIso(StringReference text, out DateTimeOffset dt)
  243. {
  244. DateTimeParser dateTimeParser = new DateTimeParser();
  245. if (!dateTimeParser.Parse(text.Chars, text.StartIndex, text.Length))
  246. {
  247. dt = default(DateTimeOffset);
  248. return false;
  249. }
  250. DateTime d = CreateDateTime(dateTimeParser);
  251. TimeSpan offset;
  252. switch (dateTimeParser.Zone)
  253. {
  254. case ParserTimeZone.Utc:
  255. offset = new TimeSpan(0L);
  256. break;
  257. case ParserTimeZone.LocalWestOfUtc:
  258. offset = new TimeSpan(-dateTimeParser.ZoneHour, -dateTimeParser.ZoneMinute, 0);
  259. break;
  260. case ParserTimeZone.LocalEastOfUtc:
  261. offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0);
  262. break;
  263. default:
  264. offset = TimeZoneInfo.Local.GetUtcOffset(d);
  265. break;
  266. }
  267. long ticks = d.Ticks - offset.Ticks;
  268. if (ticks < 0 || ticks > 3155378975999999999)
  269. {
  270. dt = default(DateTimeOffset);
  271. return false;
  272. }
  273. dt = new DateTimeOffset(d, offset);
  274. return true;
  275. }
  276. #endif
  277. private static DateTime CreateDateTime(DateTimeParser dateTimeParser)
  278. {
  279. bool is24Hour;
  280. if (dateTimeParser.Hour == 24)
  281. {
  282. is24Hour = true;
  283. dateTimeParser.Hour = 0;
  284. }
  285. else
  286. {
  287. is24Hour = false;
  288. }
  289. DateTime d = new DateTime(dateTimeParser.Year, dateTimeParser.Month, dateTimeParser.Day, dateTimeParser.Hour, dateTimeParser.Minute, dateTimeParser.Second);
  290. d = d.AddTicks(dateTimeParser.Fraction);
  291. if (is24Hour)
  292. {
  293. d = d.AddDays(1);
  294. }
  295. return d;
  296. }
  297. internal static bool TryParseDateTime(StringReference s, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt)
  298. {
  299. if (s.Length > 0)
  300. {
  301. int i = s.StartIndex;
  302. if (s[i] == '/')
  303. {
  304. if (s.Length >= 9 && s.StartsWith("/Date(") && s.EndsWith(")/"))
  305. {
  306. if (TryParseDateTimeMicrosoft(s, dateTimeZoneHandling, out dt))
  307. {
  308. return true;
  309. }
  310. }
  311. }
  312. else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[i]) && s[i + 10] == 'T')
  313. {
  314. if (TryParseDateTimeIso(s, dateTimeZoneHandling, out dt))
  315. {
  316. return true;
  317. }
  318. }
  319. if (!string.IsNullOrEmpty(dateFormatString))
  320. {
  321. if (TryParseDateTimeExact(s.ToString(), dateTimeZoneHandling, dateFormatString, culture, out dt))
  322. {
  323. return true;
  324. }
  325. }
  326. }
  327. dt = default(DateTime);
  328. return false;
  329. }
  330. internal static bool TryParseDateTime(string s, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt)
  331. {
  332. if (s.Length > 0)
  333. {
  334. if (s[0] == '/')
  335. {
  336. if (s.Length >= 9 && s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal))
  337. {
  338. if (TryParseDateTimeMicrosoft(new StringReference(s.ToCharArray(), 0, s.Length), dateTimeZoneHandling, out dt))
  339. {
  340. return true;
  341. }
  342. }
  343. }
  344. else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[0]) && s[10] == 'T')
  345. {
  346. if (DateTime.TryParseExact(s, IsoDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dt))
  347. {
  348. dt = EnsureDateTime(dt, dateTimeZoneHandling);
  349. return true;
  350. }
  351. }
  352. if (!string.IsNullOrEmpty(dateFormatString))
  353. {
  354. if (TryParseDateTimeExact(s, dateTimeZoneHandling, dateFormatString, culture, out dt))
  355. {
  356. return true;
  357. }
  358. }
  359. }
  360. dt = default(DateTime);
  361. return false;
  362. }
  363. #if !NET20
  364. internal static bool TryParseDateTimeOffset(StringReference s, string dateFormatString, CultureInfo culture, out DateTimeOffset dt)
  365. {
  366. if (s.Length > 0)
  367. {
  368. int i = s.StartIndex;
  369. if (s[i] == '/')
  370. {
  371. if (s.Length >= 9 && s.StartsWith("/Date(") && s.EndsWith(")/"))
  372. {
  373. if (TryParseDateTimeOffsetMicrosoft(s, out dt))
  374. {
  375. return true;
  376. }
  377. }
  378. }
  379. else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[i]) && s[i + 10] == 'T')
  380. {
  381. if (TryParseDateTimeOffsetIso(s, out dt))
  382. {
  383. return true;
  384. }
  385. }
  386. if (!string.IsNullOrEmpty(dateFormatString))
  387. {
  388. if (TryParseDateTimeOffsetExact(s.ToString(), dateFormatString, culture, out dt))
  389. {
  390. return true;
  391. }
  392. }
  393. }
  394. dt = default(DateTimeOffset);
  395. return false;
  396. }
  397. internal static bool TryParseDateTimeOffset(string s, string dateFormatString, CultureInfo culture, out DateTimeOffset dt)
  398. {
  399. if (s.Length > 0)
  400. {
  401. if (s[0] == '/')
  402. {
  403. if (s.Length >= 9 && s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal))
  404. {
  405. if (TryParseDateTimeOffsetMicrosoft(new StringReference(s.ToCharArray(), 0, s.Length), out dt))
  406. {
  407. return true;
  408. }
  409. }
  410. }
  411. else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[0]) && s[10] == 'T')
  412. {
  413. if (DateTimeOffset.TryParseExact(s, IsoDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dt))
  414. {
  415. if (TryParseDateTimeOffsetIso(new StringReference(s.ToCharArray(), 0, s.Length), out dt))
  416. {
  417. return true;
  418. }
  419. }
  420. }
  421. if (!string.IsNullOrEmpty(dateFormatString))
  422. {
  423. if (TryParseDateTimeOffsetExact(s, dateFormatString, culture, out dt))
  424. {
  425. return true;
  426. }
  427. }
  428. }
  429. dt = default(DateTimeOffset);
  430. return false;
  431. }
  432. #endif
  433. private static bool TryParseMicrosoftDate(StringReference text, out long ticks, out TimeSpan offset, out DateTimeKind kind)
  434. {
  435. kind = DateTimeKind.Utc;
  436. int index = text.IndexOf('+', 7, text.Length - 8);
  437. if (index == -1)
  438. {
  439. index = text.IndexOf('-', 7, text.Length - 8);
  440. }
  441. if (index != -1)
  442. {
  443. kind = DateTimeKind.Local;
  444. if (!TryReadOffset(text, index + text.StartIndex, out offset))
  445. {
  446. ticks = 0;
  447. return false;
  448. }
  449. }
  450. else
  451. {
  452. offset = TimeSpan.Zero;
  453. index = text.Length - 2;
  454. }
  455. return (ConvertUtils.Int64TryParse(text.Chars, 6 + text.StartIndex, index - 6, out ticks) == ParseResult.Success);
  456. }
  457. private static bool TryParseDateTimeMicrosoft(StringReference text, DateTimeZoneHandling dateTimeZoneHandling, out DateTime dt)
  458. {
  459. if (!TryParseMicrosoftDate(text, out long ticks, out _, out DateTimeKind kind))
  460. {
  461. dt = default(DateTime);
  462. return false;
  463. }
  464. DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(ticks);
  465. switch (kind)
  466. {
  467. case DateTimeKind.Unspecified:
  468. dt = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified);
  469. break;
  470. case DateTimeKind.Local:
  471. dt = utcDateTime.ToLocalTime();
  472. break;
  473. default:
  474. dt = utcDateTime;
  475. break;
  476. }
  477. dt = EnsureDateTime(dt, dateTimeZoneHandling);
  478. return true;
  479. }
  480. private static bool TryParseDateTimeExact(string text, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt)
  481. {
  482. if (DateTime.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out DateTime temp))
  483. {
  484. temp = EnsureDateTime(temp, dateTimeZoneHandling);
  485. dt = temp;
  486. return true;
  487. }
  488. dt = default(DateTime);
  489. return false;
  490. }
  491. #if !NET20
  492. private static bool TryParseDateTimeOffsetMicrosoft(StringReference text, out DateTimeOffset dt)
  493. {
  494. if (!TryParseMicrosoftDate(text, out long ticks, out TimeSpan offset, out _))
  495. {
  496. dt = default(DateTime);
  497. return false;
  498. }
  499. DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(ticks);
  500. dt = new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset);
  501. return true;
  502. }
  503. private static bool TryParseDateTimeOffsetExact(string text, string dateFormatString, CultureInfo culture, out DateTimeOffset dt)
  504. {
  505. if (DateTimeOffset.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out DateTimeOffset temp))
  506. {
  507. dt = temp;
  508. return true;
  509. }
  510. dt = default(DateTimeOffset);
  511. return false;
  512. }
  513. #endif
  514. private static bool TryReadOffset(StringReference offsetText, int startIndex, out TimeSpan offset)
  515. {
  516. bool negative = (offsetText[startIndex] == '-');
  517. if (ConvertUtils.Int32TryParse(offsetText.Chars, startIndex + 1, 2, out int hours) != ParseResult.Success)
  518. {
  519. offset = default(TimeSpan);
  520. return false;
  521. }
  522. int minutes = 0;
  523. if (offsetText.Length - startIndex > 5)
  524. {
  525. if (ConvertUtils.Int32TryParse(offsetText.Chars, startIndex + 3, 2, out minutes) != ParseResult.Success)
  526. {
  527. offset = default(TimeSpan);
  528. return false;
  529. }
  530. }
  531. offset = TimeSpan.FromHours(hours) + TimeSpan.FromMinutes(minutes);
  532. if (negative)
  533. {
  534. offset = offset.Negate();
  535. }
  536. return true;
  537. }
  538. #endregion
  539. #region Write
  540. internal static void WriteDateTimeString(TextWriter writer, DateTime value, DateFormatHandling format, string formatString, CultureInfo culture)
  541. {
  542. if (string.IsNullOrEmpty(formatString))
  543. {
  544. char[] chars = new char[64];
  545. int pos = WriteDateTimeString(chars, 0, value, null, value.Kind, format);
  546. writer.Write(chars, 0, pos);
  547. }
  548. else
  549. {
  550. writer.Write(value.ToString(formatString, culture));
  551. }
  552. }
  553. internal static int WriteDateTimeString(char[] chars, int start, DateTime value, TimeSpan? offset, DateTimeKind kind, DateFormatHandling format)
  554. {
  555. int pos = start;
  556. if (format == DateFormatHandling.MicrosoftDateFormat)
  557. {
  558. TimeSpan o = offset ?? value.GetUtcOffset();
  559. long javaScriptTicks = ConvertDateTimeToJavaScriptTicks(value, o);
  560. @"\/Date(".CopyTo(0, chars, pos, 7);
  561. pos += 7;
  562. string ticksText = javaScriptTicks.ToString(CultureInfo.InvariantCulture);
  563. ticksText.CopyTo(0, chars, pos, ticksText.Length);
  564. pos += ticksText.Length;
  565. switch (kind)
  566. {
  567. case DateTimeKind.Unspecified:
  568. if (value != DateTime.MaxValue && value != DateTime.MinValue)
  569. {
  570. pos = WriteDateTimeOffset(chars, pos, o, format);
  571. }
  572. break;
  573. case DateTimeKind.Local:
  574. pos = WriteDateTimeOffset(chars, pos, o, format);
  575. break;
  576. }
  577. @")\/".CopyTo(0, chars, pos, 3);
  578. pos += 3;
  579. }
  580. else
  581. {
  582. pos = WriteDefaultIsoDate(chars, pos, value);
  583. switch (kind)
  584. {
  585. case DateTimeKind.Local:
  586. pos = WriteDateTimeOffset(chars, pos, offset ?? value.GetUtcOffset(), format);
  587. break;
  588. case DateTimeKind.Utc:
  589. chars[pos++] = 'Z';
  590. break;
  591. }
  592. }
  593. return pos;
  594. }
  595. internal static int WriteDefaultIsoDate(char[] chars, int start, DateTime dt)
  596. {
  597. int length = 19;
  598. GetDateValues(dt, out int year, out int month, out int day);
  599. CopyIntToCharArray(chars, start, year, 4);
  600. chars[start + 4] = '-';
  601. CopyIntToCharArray(chars, start + 5, month, 2);
  602. chars[start + 7] = '-';
  603. CopyIntToCharArray(chars, start + 8, day, 2);
  604. chars[start + 10] = 'T';
  605. CopyIntToCharArray(chars, start + 11, dt.Hour, 2);
  606. chars[start + 13] = ':';
  607. CopyIntToCharArray(chars, start + 14, dt.Minute, 2);
  608. chars[start + 16] = ':';
  609. CopyIntToCharArray(chars, start + 17, dt.Second, 2);
  610. int fraction = (int)(dt.Ticks % 10000000L);
  611. if (fraction != 0)
  612. {
  613. int digits = 7;
  614. while ((fraction % 10) == 0)
  615. {
  616. digits--;
  617. fraction /= 10;
  618. }
  619. chars[start + 19] = '.';
  620. CopyIntToCharArray(chars, start + 20, fraction, digits);
  621. length += digits + 1;
  622. }
  623. return start + length;
  624. }
  625. private static void CopyIntToCharArray(char[] chars, int start, int value, int digits)
  626. {
  627. while (digits-- != 0)
  628. {
  629. chars[start + digits] = (char)((value % 10) + 48);
  630. value /= 10;
  631. }
  632. }
  633. internal static int WriteDateTimeOffset(char[] chars, int start, TimeSpan offset, DateFormatHandling format)
  634. {
  635. chars[start++] = (offset.Ticks >= 0L) ? '+' : '-';
  636. int absHours = Math.Abs(offset.Hours);
  637. CopyIntToCharArray(chars, start, absHours, 2);
  638. start += 2;
  639. if (format == DateFormatHandling.IsoDateFormat)
  640. {
  641. chars[start++] = ':';
  642. }
  643. int absMinutes = Math.Abs(offset.Minutes);
  644. CopyIntToCharArray(chars, start, absMinutes, 2);
  645. start += 2;
  646. return start;
  647. }
  648. #if !NET20
  649. internal static void WriteDateTimeOffsetString(TextWriter writer, DateTimeOffset value, DateFormatHandling format, string formatString, CultureInfo culture)
  650. {
  651. if (string.IsNullOrEmpty(formatString))
  652. {
  653. char[] chars = new char[64];
  654. int pos = WriteDateTimeString(chars, 0, (format == DateFormatHandling.IsoDateFormat) ? value.DateTime : value.UtcDateTime, value.Offset, DateTimeKind.Local, format);
  655. writer.Write(chars, 0, pos);
  656. }
  657. else
  658. {
  659. writer.Write(value.ToString(formatString, culture));
  660. }
  661. }
  662. #endif
  663. #endregion
  664. private static void GetDateValues(DateTime td, out int year, out int month, out int day)
  665. {
  666. long ticks = td.Ticks;
  667. // n = number of days since 1/1/0001
  668. int n = (int)(ticks / TicksPerDay);
  669. // y400 = number of whole 400-year periods since 1/1/0001
  670. int y400 = n / DaysPer400Years;
  671. // n = day number within 400-year period
  672. n -= y400 * DaysPer400Years;
  673. // y100 = number of whole 100-year periods within 400-year period
  674. int y100 = n / DaysPer100Years;
  675. // Last 100-year period has an extra day, so decrement result if 4
  676. if (y100 == 4)
  677. {
  678. y100 = 3;
  679. }
  680. // n = day number within 100-year period
  681. n -= y100 * DaysPer100Years;
  682. // y4 = number of whole 4-year periods within 100-year period
  683. int y4 = n / DaysPer4Years;
  684. // n = day number within 4-year period
  685. n -= y4 * DaysPer4Years;
  686. // y1 = number of whole years within 4-year period
  687. int y1 = n / DaysPerYear;
  688. // Last year has an extra day, so decrement result if 4
  689. if (y1 == 4)
  690. {
  691. y1 = 3;
  692. }
  693. year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
  694. // n = day number within year
  695. n -= y1 * DaysPerYear;
  696. // Leap year calculation looks different from IsLeapYear since y1, y4,
  697. // and y100 are relative to year 1, not year 0
  698. bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
  699. int[] days = leapYear ? DaysToMonth366 : DaysToMonth365;
  700. // All months have less than 32 days, so n >> 5 is a good conservative
  701. // estimate for the month
  702. int m = n >> 5 + 1;
  703. // m = 1-based month number
  704. while (n >= days[m])
  705. {
  706. m++;
  707. }
  708. month = m;
  709. // Return 1-based day-of-month
  710. day = n - days[m - 1] + 1;
  711. }
  712. }
  713. }