DateConstructor.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue
  2. using Jint.Collections;
  3. using Jint.Native.Function;
  4. using Jint.Native.Object;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Interop;
  8. namespace Jint.Native.Date;
  9. /// <summary>
  10. /// https://tc39.es/ecma262/#sec-date-constructor
  11. /// </summary>
  12. internal sealed class DateConstructor : Constructor
  13. {
  14. internal static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  15. private static readonly JsString _functionName = new JsString("Date");
  16. private readonly ITimeSystem _timeSystem;
  17. internal DateConstructor(
  18. Engine engine,
  19. Realm realm,
  20. FunctionPrototype functionPrototype,
  21. ObjectPrototype objectPrototype)
  22. : base(engine, realm, _functionName)
  23. {
  24. _prototype = functionPrototype;
  25. PrototypeObject = new DatePrototype(engine, this, objectPrototype);
  26. _length = new PropertyDescriptor(7, PropertyFlag.Configurable);
  27. _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
  28. _timeSystem = engine.Options.TimeSystem;
  29. }
  30. internal DatePrototype PrototypeObject { get; }
  31. protected override void Initialize()
  32. {
  33. const PropertyFlag LengthFlags = PropertyFlag.Configurable;
  34. const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  35. var properties = new PropertyDictionary(3, checkExistingKeys: false)
  36. {
  37. ["parse"] = new(new ClrFunction(Engine, "parse", Parse, 1, LengthFlags), PropertyFlags),
  38. ["UTC"] = new(new ClrFunction(Engine, "UTC", Utc, 7, LengthFlags), PropertyFlags),
  39. ["now"] = new(new ClrFunction(Engine, "now", Now, 0, LengthFlags), PropertyFlags)
  40. };
  41. SetProperties(properties);
  42. }
  43. /// <summary>
  44. /// https://tc39.es/ecma262/#sec-date.parse
  45. /// </summary>
  46. private JsValue Parse(JsValue thisObject, JsValue[] arguments)
  47. {
  48. var dateString = TypeConverter.ToString(arguments.At(0));
  49. var date = ParseFromString(dateString);
  50. return date.ToJsValue();
  51. }
  52. /// <summary>
  53. /// https://tc39.es/ecma262/#sec-date.parse
  54. /// </summary>
  55. private DatePresentation ParseFromString(string date)
  56. {
  57. if (_timeSystem.TryParse(date, out var result))
  58. {
  59. return result;
  60. }
  61. // unrecognized dates should return NaN
  62. return DatePresentation.NaN;
  63. }
  64. /// <summary>
  65. /// https://tc39.es/ecma262/#sec-date.utc
  66. /// </summary>
  67. private static JsValue Utc(JsValue thisObject, JsValue[] arguments)
  68. {
  69. var y = TypeConverter.ToNumber(arguments.At(0));
  70. var m = TypeConverter.ToNumber(arguments.At(1, JsNumber.PositiveZero));
  71. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  72. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  73. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  74. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  75. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  76. var yInteger = TypeConverter.ToInteger(y);
  77. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  78. {
  79. y = yInteger + 1900;
  80. }
  81. var finalDate = DatePrototype.MakeDate(
  82. DatePrototype.MakeDay(y, m, dt),
  83. DatePrototype.MakeTime(h, min, s, milli));
  84. return finalDate.TimeClip().ToJsValue();
  85. }
  86. private JsValue Now(JsValue thisObject, JsValue[] arguments)
  87. {
  88. return (long) (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds;
  89. }
  90. protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
  91. {
  92. return PrototypeObject.ToString(Construct(Arguments.Empty, thisObject), Arguments.Empty);
  93. }
  94. /// <summary>
  95. /// https://tc39.es/ecma262/#sec-date
  96. /// </summary>
  97. public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
  98. {
  99. // fast path is building default, new Date()
  100. if (arguments.Length == 0 || newTarget.IsUndefined())
  101. {
  102. return OrdinaryCreateFromConstructor(
  103. newTarget,
  104. static intrinsics => intrinsics.Date.PrototypeObject,
  105. static (engine, _, dateValue) => new JsDate(engine, dateValue),
  106. (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds);
  107. }
  108. return ConstructUnlikely(arguments, newTarget);
  109. }
  110. private JsDate ConstructUnlikely(JsValue[] arguments, JsValue newTarget)
  111. {
  112. DatePresentation dv;
  113. if (arguments.Length == 1)
  114. {
  115. if (arguments[0] is JsDate date)
  116. {
  117. return Construct(date._dateValue);
  118. }
  119. var v = TypeConverter.ToPrimitive(arguments[0]);
  120. if (v.IsString())
  121. {
  122. var value = ParseFromString(v.ToString());
  123. return Construct(value);
  124. }
  125. dv = TypeConverter.ToNumber(v);
  126. }
  127. else
  128. {
  129. var y = TypeConverter.ToNumber(arguments.At(0));
  130. var m = TypeConverter.ToNumber(arguments.At(1));
  131. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  132. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  133. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  134. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  135. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  136. var yInteger = TypeConverter.ToInteger(y);
  137. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  138. {
  139. y += 1900;
  140. }
  141. var finalDate = DatePrototype.MakeDate(
  142. DatePrototype.MakeDay(y, m, dt),
  143. DatePrototype.MakeTime(h, min, s, milli));
  144. dv = PrototypeObject.Utc(finalDate).TimeClip();
  145. }
  146. return OrdinaryCreateFromConstructor(
  147. newTarget,
  148. static intrinsics => intrinsics.Date.PrototypeObject,
  149. static (engine, _, dateValue) => new JsDate(engine, dateValue), dv);
  150. }
  151. public JsDate Construct(DateTimeOffset value) => Construct(value.UtcDateTime);
  152. public JsDate Construct(DateTime value) => Construct(FromDateTime(value));
  153. public JsDate Construct(long time)
  154. {
  155. return OrdinaryCreateFromConstructor(
  156. Undefined,
  157. static intrinsics => intrinsics.Date.PrototypeObject,
  158. static (engine, _, dateValue) => new JsDate(engine, dateValue), time);
  159. }
  160. private JsDate Construct(DatePresentation time)
  161. {
  162. return OrdinaryCreateFromConstructor(
  163. Undefined,
  164. static intrinsics => intrinsics.Date.PrototypeObject,
  165. static (engine, _, dateValue) => new JsDate(engine, dateValue), time);
  166. }
  167. internal DatePresentation FromDateTime(DateTime dt, bool negative = false)
  168. {
  169. if (dt == DateTime.MinValue)
  170. {
  171. return DatePresentation.MinValue;
  172. }
  173. if (dt == DateTime.MaxValue)
  174. {
  175. return DatePresentation.MaxValue;
  176. }
  177. var convertToUtcAfter = dt.Kind == DateTimeKind.Unspecified;
  178. var dateAsUtc = dt.Kind == DateTimeKind.Local
  179. ? dt.ToUniversalTime()
  180. : DateTime.SpecifyKind(dt, DateTimeKind.Utc);
  181. DatePresentation result;
  182. if (negative)
  183. {
  184. result = DatePrototype.MakeDate(
  185. DatePrototype.MakeDay(-1 * dateAsUtc.Year, dateAsUtc.Month - 1, dateAsUtc.Day),
  186. DatePrototype.MakeTime(dateAsUtc.Hour, dateAsUtc.Minute, dateAsUtc.Second, dateAsUtc.Millisecond)
  187. );
  188. }
  189. else
  190. {
  191. result = (long) (dateAsUtc - Epoch).TotalMilliseconds;
  192. }
  193. if (convertToUtcAfter)
  194. {
  195. result = PrototypeObject.Utc(result);
  196. }
  197. return result;
  198. }
  199. }