DateConstructor.cs 8.1 KB

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