DefaultTimeSystem.cs 5.2 KB

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