DefaultTimeSystem.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. epochMilliseconds = (long) Math.Floor((dateAsUtc - DateConstructor.Epoch).TotalMilliseconds);
  79. return true;
  80. }
  81. return false;
  82. }
  83. }
  84. }
  85. }
  86. var convertToUtcAfter = result.Kind == DateTimeKind.Unspecified;
  87. var dateAsUtc1 = result.Kind == DateTimeKind.Local
  88. ? result.ToUniversalTime()
  89. : DateTime.SpecifyKind(result, DateTimeKind.Utc);
  90. DatePresentation datePresentation = (long) (dateAsUtc1 - DateConstructor.Epoch).TotalMilliseconds;
  91. if (convertToUtcAfter)
  92. {
  93. var offset = GetUtcOffset(datePresentation.Value).TotalMilliseconds;
  94. datePresentation -= offset;
  95. }
  96. epochMilliseconds = datePresentation.Value;
  97. return true;
  98. }
  99. /// <summary>
  100. /// Supports parsing of large (> 10 000) and negative years, should not be needed that often...
  101. /// </summary>
  102. private static bool TryParseLargeYear(string date, out long epochMilliseconds)
  103. {
  104. epochMilliseconds = long.MinValue;
  105. var yearString = date.Substring(0, 7);
  106. if (!int.TryParse(yearString, out var year))
  107. {
  108. return false;
  109. }
  110. if (year == 0 && date[0] == '-')
  111. {
  112. // cannot be negative zero ever
  113. return false;
  114. }
  115. // create replacement string
  116. var dateToParse = "2000" + date.Substring(7);
  117. if (!DateTime.TryParse(dateToParse, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed))
  118. {
  119. return false;
  120. }
  121. var dateTime = parsed.ToUniversalTime();
  122. var datePresentation = DatePrototype.MakeDate(
  123. DatePrototype.MakeDay(year, dateTime.Month - 1, dateTime.Day),
  124. DatePrototype.MakeTime(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond)
  125. );
  126. epochMilliseconds = datePresentation.Value;
  127. return true;
  128. }
  129. public virtual TimeSpan GetUtcOffset(long epochMilliseconds)
  130. {
  131. // we have limited capabilities without addon like NodaTime
  132. if (epochMilliseconds is < -62135596800000L or > 253402300799999L)
  133. {
  134. return this.DefaultTimeZone.BaseUtcOffset;
  135. }
  136. return this.DefaultTimeZone.GetUtcOffset(DateTimeOffset.FromUnixTimeMilliseconds(epochMilliseconds));
  137. }
  138. }