DefaultTimeSystem.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System.Globalization;
  2. using Jint.Native;
  3. using Jint.Native.Date;
  4. namespace Jint.Runtime;
  5. public class DefaultTimeSystem : ITimeSystem
  6. {
  7. private static readonly string[] _defaultFormats = { "yyyy-MM-dd", "yyyy-MM", "yyyy" };
  8. private static readonly string[] _secondaryFormats =
  9. {
  10. "yyyy-MM-ddTHH:mm:ss.FFF",
  11. "yyyy-MM-ddTHH:mm:ss",
  12. "yyyy-MM-ddTHH:mm",
  13. // Formats used in DatePrototype toString methods
  14. "ddd MMM dd yyyy HH:mm:ss 'GMT'K",
  15. "ddd MMM dd yyyy",
  16. "HH:mm:ss 'GMT'K",
  17. // standard formats
  18. "yyyy-M-dTH:m:s.FFFK",
  19. "yyyy/M/dTH:m:s.FFFK",
  20. "yyyy-M-dTH:m:sK",
  21. "yyyy/M/dTH:m:sK",
  22. "yyyy-M-dTH:mK",
  23. "yyyy/M/dTH:mK",
  24. "yyyy-M-d H:m:s.FFFK",
  25. "yyyy/M/d H:m:s.FFFK",
  26. "yyyy-M-d H:m:sK",
  27. "yyyy/M/d H:m:sK",
  28. "yyyy-M-d H:mK",
  29. "yyyy/M/d H:mK",
  30. "yyyy-M-dK",
  31. "yyyy/M/dK",
  32. "yyyy-MK",
  33. "yyyy/MK",
  34. "yyyyK",
  35. "THH:mm:ss.FFFK",
  36. "THH:mm:ssK",
  37. "THH:mmK",
  38. "THHK"
  39. };
  40. private readonly CultureInfo _parsingCulture;
  41. public DefaultTimeSystem(TimeZoneInfo timeZoneInfo, CultureInfo parsingCulture)
  42. {
  43. _parsingCulture = parsingCulture;
  44. DefaultTimeZone = timeZoneInfo;
  45. }
  46. public TimeZoneInfo DefaultTimeZone { get; }
  47. public virtual bool TryParse(string date, out long epochMilliseconds)
  48. {
  49. epochMilliseconds = long.MinValue;
  50. if (string.IsNullOrEmpty(date))
  51. {
  52. return false;
  53. }
  54. // special check for large years that always require + or - in front and have 6 digit year
  55. if ((date[0] == '+'|| date[0] == '-') && date.IndexOf('-', 1) == 7)
  56. {
  57. return TryParseLargeYear(date, out epochMilliseconds);
  58. }
  59. var startParen = date.IndexOf('(');
  60. if (startParen != -1)
  61. {
  62. // informative text
  63. date = date.Substring(0, startParen);
  64. }
  65. date = date.Trim();
  66. if (!DateTime.TryParseExact(date, _defaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
  67. {
  68. if (!DateTime.TryParseExact(date, _secondaryFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  69. {
  70. if (!DateTime.TryParse(date, _parsingCulture, DateTimeStyles.AdjustToUniversal, out result))
  71. {
  72. if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  73. {
  74. // fall back to trying with MimeKit
  75. if (DateUtils.TryParse(date, out var mimeKitResult))
  76. {
  77. var dateAsUtc = mimeKitResult.ToUniversalTime();
  78. #pragma warning disable MA0132
  79. epochMilliseconds = (long) Math.Floor((dateAsUtc - DateConstructor.Epoch).TotalMilliseconds);
  80. #pragma warning restore MA0132
  81. return true;
  82. }
  83. return false;
  84. }
  85. }
  86. }
  87. }
  88. var convertToUtcAfter = result.Kind == DateTimeKind.Unspecified;
  89. var dateAsUtc1 = result.Kind == DateTimeKind.Local
  90. ? result.ToUniversalTime()
  91. : DateTime.SpecifyKind(result, DateTimeKind.Utc);
  92. DatePresentation datePresentation = (long) (dateAsUtc1 - DateConstructor.Epoch).TotalMilliseconds;
  93. if (convertToUtcAfter)
  94. {
  95. var offset = GetUtcOffset(datePresentation.Value).TotalMilliseconds;
  96. datePresentation -= offset;
  97. }
  98. epochMilliseconds = datePresentation.Value;
  99. return true;
  100. }
  101. /// <summary>
  102. /// Supports parsing of large (> 10 000) and negative years, should not be needed that often...
  103. /// </summary>
  104. private static bool TryParseLargeYear(string date, out long epochMilliseconds)
  105. {
  106. epochMilliseconds = long.MinValue;
  107. var yearString = date.Substring(0, 7);
  108. if (!int.TryParse(yearString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
  109. {
  110. return false;
  111. }
  112. if (year == 0 && date[0] == '-')
  113. {
  114. // cannot be negative zero ever
  115. return false;
  116. }
  117. // create replacement string
  118. #pragma warning disable CA1845
  119. var dateToParse = "2000" + date.Substring(7);
  120. #pragma warning restore CA1845
  121. if (!DateTime.TryParse(dateToParse, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed))
  122. {
  123. return false;
  124. }
  125. var dateTime = parsed.ToUniversalTime();
  126. var datePresentation = DatePrototype.MakeDate(
  127. DatePrototype.MakeDay(year, dateTime.Month - 1, dateTime.Day),
  128. DatePrototype.MakeTime(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond)
  129. );
  130. epochMilliseconds = datePresentation.Value;
  131. return true;
  132. }
  133. public virtual TimeSpan GetUtcOffset(long epochMilliseconds)
  134. {
  135. // we have limited capabilities without addon like NodaTime
  136. if (epochMilliseconds is < -62135596800000L or > 253402300799999L)
  137. {
  138. return this.DefaultTimeZone.BaseUtcOffset;
  139. }
  140. return this.DefaultTimeZone.GetUtcOffset(DateTimeOffset.FromUnixTimeMilliseconds(epochMilliseconds));
  141. }
  142. }