DateConstructor.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System.Globalization;
  2. using System.Runtime.CompilerServices;
  3. using Jint.Collections;
  4. using Jint.Native.Function;
  5. using Jint.Native.Object;
  6. using Jint.Runtime;
  7. using Jint.Runtime.Descriptors;
  8. using Jint.Runtime.Interop;
  9. namespace Jint.Native.Date
  10. {
  11. /// <summary>
  12. /// https://tc39.es/ecma262/#sec-date-constructor
  13. /// </summary>
  14. internal sealed class DateConstructor : FunctionInstance, IConstructor
  15. {
  16. internal static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  17. private static readonly string[] DefaultFormats = {
  18. "yyyy-MM-dd",
  19. "yyyy-MM",
  20. "yyyy"
  21. };
  22. private static readonly string[] SecondaryFormats = {
  23. "yyyy-MM-ddTHH:mm:ss.FFF",
  24. "yyyy-MM-ddTHH:mm:ss",
  25. "yyyy-MM-ddTHH:mm",
  26. // Formats used in DatePrototype toString methods
  27. "ddd MMM dd yyyy HH:mm:ss 'GMT'K",
  28. "ddd MMM dd yyyy",
  29. "HH:mm:ss 'GMT'K",
  30. // standard formats
  31. "yyyy-M-dTH:m:s.FFFK",
  32. "yyyy/M/dTH:m:s.FFFK",
  33. "yyyy-M-dTH:m:sK",
  34. "yyyy/M/dTH:m:sK",
  35. "yyyy-M-dTH:mK",
  36. "yyyy/M/dTH:mK",
  37. "yyyy-M-d H:m:s.FFFK",
  38. "yyyy/M/d H:m:s.FFFK",
  39. "yyyy-M-d H:m:sK",
  40. "yyyy/M/d H:m:sK",
  41. "yyyy-M-d H:mK",
  42. "yyyy/M/d H:mK",
  43. "yyyy-M-dK",
  44. "yyyy/M/dK",
  45. "yyyy-MK",
  46. "yyyy/MK",
  47. "yyyyK",
  48. "THH:mm:ss.FFFK",
  49. "THH:mm:ssK",
  50. "THH:mmK",
  51. "THHK"
  52. };
  53. private static readonly JsString _functionName = new JsString("Date");
  54. internal DateConstructor(
  55. Engine engine,
  56. Realm realm,
  57. FunctionPrototype functionPrototype,
  58. ObjectPrototype objectPrototype)
  59. : base(engine, realm, _functionName)
  60. {
  61. _prototype = functionPrototype;
  62. PrototypeObject = new DatePrototype(engine, this, objectPrototype);
  63. _length = new PropertyDescriptor(7, PropertyFlag.Configurable);
  64. _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
  65. }
  66. internal DatePrototype PrototypeObject { get; }
  67. protected override void Initialize()
  68. {
  69. const PropertyFlag LengthFlags = PropertyFlag.Configurable;
  70. const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  71. var properties = new PropertyDictionary(3, checkExistingKeys: false)
  72. {
  73. ["parse"] = new(new ClrFunctionInstance(Engine, "parse", Parse, 1, LengthFlags), PropertyFlags),
  74. ["UTC"] = new(new ClrFunctionInstance(Engine, "UTC", Utc, 7, LengthFlags), PropertyFlags),
  75. ["now"] = new(new ClrFunctionInstance(Engine, "now", Now, 0, LengthFlags), PropertyFlags)
  76. };
  77. SetProperties(properties);
  78. }
  79. /// <summary>
  80. /// https://tc39.es/ecma262/#sec-date.parse
  81. /// </summary>
  82. private JsValue Parse(JsValue thisObj, JsValue[] arguments)
  83. {
  84. var date = TypeConverter.ToString(arguments.At(0));
  85. var negative = date.StartsWith("-");
  86. if (negative)
  87. {
  88. date = date.Substring(1);
  89. }
  90. var startParen = date.IndexOf('(');
  91. if (startParen != -1)
  92. {
  93. // informative text
  94. date = date.Substring(0, startParen);
  95. }
  96. date = date.Trim();
  97. if (!DateTime.TryParseExact(date, DefaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
  98. {
  99. if (!DateTime.TryParseExact(date, SecondaryFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  100. {
  101. if (!DateTime.TryParse(date, Engine.Options.Culture, DateTimeStyles.AdjustToUniversal, out result))
  102. {
  103. if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  104. {
  105. // unrecognized dates should return NaN (15.9.4.2)
  106. return JsNumber.DoubleNaN;
  107. }
  108. }
  109. }
  110. }
  111. return FromDateTime(result, negative);
  112. }
  113. private static JsValue Utc(JsValue thisObj, JsValue[] arguments)
  114. {
  115. var y = TypeConverter.ToNumber(arguments.At(0));
  116. var m = TypeConverter.ToNumber(arguments.At(1, JsNumber.PositiveZero));
  117. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  118. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  119. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  120. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  121. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  122. var yInteger = TypeConverter.ToInteger(y);
  123. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  124. {
  125. y = yInteger + 1900;
  126. }
  127. var finalDate = DatePrototype.MakeDate(
  128. DatePrototype.MakeDay(y, m, dt),
  129. DatePrototype.MakeTime(h, min, s, milli));
  130. return finalDate.TimeClip();
  131. }
  132. private static JsValue Now(JsValue thisObj, JsValue[] arguments)
  133. {
  134. return System.Math.Floor((DateTime.UtcNow - Epoch).TotalMilliseconds);
  135. }
  136. protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
  137. {
  138. return PrototypeObject.ToString(Construct(Arguments.Empty, thisObject), Arguments.Empty);
  139. }
  140. ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);
  141. /// <summary>
  142. /// https://tc39.es/ecma262/#sec-date
  143. /// </summary>
  144. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  145. private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
  146. {
  147. // fast path is building default, new Date()
  148. if (arguments.Length == 0 || newTarget.IsUndefined())
  149. {
  150. return OrdinaryCreateFromConstructor(
  151. newTarget,
  152. static intrinsics => intrinsics.Date.PrototypeObject,
  153. static (engine, _, dateValue) => new JsDate(engine, dateValue),
  154. (DateTime.UtcNow - Epoch).TotalMilliseconds);
  155. }
  156. return ConstructUnlikely(arguments, newTarget);
  157. }
  158. private JsDate ConstructUnlikely(JsValue[] arguments, JsValue newTarget)
  159. {
  160. double dv;
  161. if (arguments.Length == 1)
  162. {
  163. if (arguments[0] is JsDate date)
  164. {
  165. return Construct(date.DateValue);
  166. }
  167. var v = TypeConverter.ToPrimitive(arguments[0]);
  168. if (v.IsString())
  169. {
  170. return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
  171. }
  172. dv = TypeConverter.ToNumber(v);
  173. }
  174. else
  175. {
  176. var y = TypeConverter.ToNumber(arguments.At(0));
  177. var m = TypeConverter.ToNumber(arguments.At(1));
  178. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  179. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  180. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  181. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  182. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  183. var yInteger = TypeConverter.ToInteger(y);
  184. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  185. {
  186. y += 1900;
  187. }
  188. var finalDate = DatePrototype.MakeDate(
  189. DatePrototype.MakeDay(y, m, dt),
  190. DatePrototype.MakeTime(h, min, s, milli));
  191. dv = PrototypeObject.Utc(finalDate);
  192. }
  193. return OrdinaryCreateFromConstructor(
  194. newTarget,
  195. static intrinsics => intrinsics.Date.PrototypeObject,
  196. static (engine, _, dateValue) => new JsDate(engine, dateValue), dv);
  197. }
  198. public JsDate Construct(DateTimeOffset value) => Construct(value.UtcDateTime);
  199. public JsDate Construct(DateTime value) => Construct(FromDateTime(value));
  200. public JsDate Construct(double time)
  201. {
  202. return OrdinaryCreateFromConstructor(
  203. Undefined,
  204. static intrinsics => intrinsics.Date.PrototypeObject,
  205. static (engine, _, dateValue) => new JsDate(engine, dateValue), time);
  206. }
  207. internal double FromDateTime(DateTime dt, bool negative = false)
  208. {
  209. var convertToUtcAfter = dt.Kind == DateTimeKind.Unspecified;
  210. var dateAsUtc = dt.Kind == DateTimeKind.Local
  211. ? dt.ToUniversalTime()
  212. : DateTime.SpecifyKind(dt, DateTimeKind.Utc);
  213. double result;
  214. if (negative)
  215. {
  216. var zero = (Epoch - DateTime.MinValue).TotalMilliseconds;
  217. result = zero - TimeSpan.FromTicks(dateAsUtc.Ticks).TotalMilliseconds;
  218. result *= -1;
  219. }
  220. else
  221. {
  222. result = (dateAsUtc - Epoch).TotalMilliseconds;
  223. }
  224. if (convertToUtcAfter)
  225. {
  226. result = PrototypeObject.Utc(result);
  227. }
  228. return System.Math.Floor(result);
  229. }
  230. }
  231. }