DefaultTimeSystem.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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 virtual DateTimeOffset GetUtcNow()
  47. {
  48. return DateTimeOffset.UtcNow;
  49. }
  50. public TimeZoneInfo DefaultTimeZone { get; }
  51. public virtual bool TryParse(string date, out long epochMilliseconds)
  52. {
  53. epochMilliseconds = long.MinValue;
  54. if (string.IsNullOrEmpty(date))
  55. {
  56. return false;
  57. }
  58. // special check for large years that always require + or - in front and have 6 digit year
  59. if ((date[0] == '+' || date[0] == '-') && date.IndexOf('-', 1) == 7)
  60. {
  61. return TryParseLargeYear(date, out epochMilliseconds);
  62. }
  63. var startParen = date.IndexOf('(');
  64. if (startParen != -1)
  65. {
  66. // informative text
  67. date = date.Substring(0, startParen);
  68. }
  69. date = date.Trim();
  70. if (!DateTime.TryParseExact(date, _defaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
  71. {
  72. if (!DateTime.TryParseExact(date, _secondaryFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  73. {
  74. if (!DateTime.TryParse(date, _parsingCulture, DateTimeStyles.AdjustToUniversal, out result))
  75. {
  76. if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  77. {
  78. // fall back to trying with MimeKit
  79. if (DateUtils.TryParse(date, out var mimeKitResult))
  80. {
  81. var dateAsUtc = mimeKitResult.ToUniversalTime();
  82. #pragma warning disable MA0132
  83. epochMilliseconds = (long) Math.Floor((dateAsUtc - DateConstructor.Epoch).TotalMilliseconds);
  84. #pragma warning restore MA0132
  85. return true;
  86. }
  87. return false;
  88. }
  89. }
  90. }
  91. }
  92. var convertToUtcAfter = result.Kind == DateTimeKind.Unspecified;
  93. var dateAsUtc1 = result.Kind == DateTimeKind.Local
  94. ? result.ToUniversalTime()
  95. : DateTime.SpecifyKind(result, DateTimeKind.Utc);
  96. DatePresentation datePresentation = (long) (dateAsUtc1 - DateConstructor.Epoch).TotalMilliseconds;
  97. if (convertToUtcAfter)
  98. {
  99. var offset = GetUtcOffset(datePresentation.Value).TotalMilliseconds;
  100. datePresentation -= offset;
  101. }
  102. epochMilliseconds = datePresentation.Value;
  103. return true;
  104. }
  105. /// <summary>
  106. /// Supports parsing of large (> 10 000) and negative years, should not be needed that often...
  107. /// </summary>
  108. private static bool TryParseLargeYear(string date, out long epochMilliseconds)
  109. {
  110. epochMilliseconds = long.MinValue;
  111. var yearString = date.Substring(0, 7);
  112. if (!int.TryParse(yearString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
  113. {
  114. return false;
  115. }
  116. if (year == 0 && date[0] == '-')
  117. {
  118. // cannot be negative zero ever
  119. return false;
  120. }
  121. // create replacement string
  122. #pragma warning disable CA1845
  123. var dateToParse = "2000" + date.Substring(7);
  124. #pragma warning restore CA1845
  125. if (!DateTime.TryParse(dateToParse, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed))
  126. {
  127. return false;
  128. }
  129. var dateTime = parsed.ToUniversalTime();
  130. var datePresentation = DatePrototype.MakeDate(
  131. DatePrototype.MakeDay(year, dateTime.Month - 1, dateTime.Day),
  132. DatePrototype.MakeTime(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond)
  133. );
  134. epochMilliseconds = datePresentation.Value;
  135. return true;
  136. }
  137. public virtual TimeSpan GetUtcOffset(long epochMilliseconds)
  138. {
  139. // we have limited capabilities without addon like NodaTime
  140. if (epochMilliseconds is < -62135596800000L or > 253402300799999L)
  141. {
  142. return this.DefaultTimeZone.BaseUtcOffset;
  143. }
  144. return this.DefaultTimeZone.GetUtcOffset(DateTimeOffset.FromUnixTimeMilliseconds(epochMilliseconds));
  145. }
  146. }