#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of constructor methods return JsValue using Jint.Collections; using Jint.Native.Function; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.Date; /// /// https://tc39.es/ecma262/#sec-date-constructor /// internal sealed class DateConstructor : Constructor { internal static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static readonly JsString _functionName = new JsString("Date"); private readonly ITimeSystem _timeSystem; internal DateConstructor( Engine engine, Realm realm, FunctionPrototype functionPrototype, ObjectPrototype objectPrototype) : base(engine, realm, _functionName) { _prototype = functionPrototype; PrototypeObject = new DatePrototype(engine, this, objectPrototype); _length = new PropertyDescriptor(7, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); _timeSystem = engine.Options.TimeSystem; } internal DatePrototype PrototypeObject { get; } protected override void Initialize() { const PropertyFlag LengthFlags = PropertyFlag.Configurable; const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; var properties = new PropertyDictionary(3, checkExistingKeys: false) { ["parse"] = new(new ClrFunction(Engine, "parse", Parse, 1, LengthFlags), PropertyFlags), ["UTC"] = new(new ClrFunction(Engine, "UTC", Utc, 7, LengthFlags), PropertyFlags), ["now"] = new(new ClrFunction(Engine, "now", Now, 0, LengthFlags), PropertyFlags) }; SetProperties(properties); } /// /// https://tc39.es/ecma262/#sec-date.parse /// private JsValue Parse(JsValue thisObject, JsValue[] arguments) { var dateString = TypeConverter.ToString(arguments.At(0)); var date = ParseFromString(dateString); return date.ToJsValue(); } /// /// https://tc39.es/ecma262/#sec-date.parse /// private DatePresentation ParseFromString(string date) { if (_timeSystem.TryParse(date, out var result)) { return result; } // unrecognized dates should return NaN return DatePresentation.NaN; } /// /// https://tc39.es/ecma262/#sec-date.utc /// private static JsValue Utc(JsValue thisObject, JsValue[] arguments) { var y = TypeConverter.ToNumber(arguments.At(0)); var m = TypeConverter.ToNumber(arguments.At(1, JsNumber.PositiveZero)); var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne)); var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero)); var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero)); var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero)); var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero)); var yInteger = TypeConverter.ToInteger(y); if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99) { y = yInteger + 1900; } var finalDate = DatePrototype.MakeDate( DatePrototype.MakeDay(y, m, dt), DatePrototype.MakeTime(h, min, s, milli)); return finalDate.TimeClip().ToJsValue(); } private JsValue Now(JsValue thisObject, JsValue[] arguments) { return (long) (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds; } protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { return PrototypeObject.ToString(Construct(Arguments.Empty, thisObject), Arguments.Empty); } /// /// https://tc39.es/ecma262/#sec-date /// public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { // fast path is building default, new Date() if (arguments.Length == 0 || newTarget.IsUndefined()) { return OrdinaryCreateFromConstructor( newTarget, static intrinsics => intrinsics.Date.PrototypeObject, static (engine, _, dateValue) => new JsDate(engine, dateValue), (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds); } return ConstructUnlikely(arguments, newTarget); } private JsDate ConstructUnlikely(JsValue[] arguments, JsValue newTarget) { DatePresentation dv; if (arguments.Length == 1) { if (arguments[0] is JsDate date) { return Construct(date._dateValue); } var v = TypeConverter.ToPrimitive(arguments[0]); if (v.IsString()) { var value = ParseFromString(v.ToString()); return Construct(value); } dv = TypeConverter.ToNumber(v); } else { var y = TypeConverter.ToNumber(arguments.At(0)); var m = TypeConverter.ToNumber(arguments.At(1)); var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne)); var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero)); var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero)); var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero)); var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero)); var yInteger = TypeConverter.ToInteger(y); if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99) { y += 1900; } var finalDate = DatePrototype.MakeDate( DatePrototype.MakeDay(y, m, dt), DatePrototype.MakeTime(h, min, s, milli)); dv = PrototypeObject.Utc(finalDate).TimeClip(); } return OrdinaryCreateFromConstructor( newTarget, static intrinsics => intrinsics.Date.PrototypeObject, static (engine, _, dateValue) => new JsDate(engine, dateValue), dv); } public JsDate Construct(DateTimeOffset value) => Construct(value.UtcDateTime); public JsDate Construct(DateTime value) => Construct(FromDateTime(value)); public JsDate Construct(long time) { return OrdinaryCreateFromConstructor( Undefined, static intrinsics => intrinsics.Date.PrototypeObject, static (engine, _, dateValue) => new JsDate(engine, dateValue), time); } private JsDate Construct(DatePresentation time) { return OrdinaryCreateFromConstructor( Undefined, static intrinsics => intrinsics.Date.PrototypeObject, static (engine, _, dateValue) => new JsDate(engine, dateValue), time); } internal DatePresentation FromDateTime(DateTime dt, bool negative = false) { if (dt == DateTime.MinValue) { return DatePresentation.MinValue; } if (dt == DateTime.MaxValue) { return DatePresentation.MaxValue; } var convertToUtcAfter = dt.Kind == DateTimeKind.Unspecified; var dateAsUtc = dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : DateTime.SpecifyKind(dt, DateTimeKind.Utc); DatePresentation result; if (negative) { result = DatePrototype.MakeDate( DatePrototype.MakeDay(-1 * dateAsUtc.Year, dateAsUtc.Month - 1, dateAsUtc.Day), DatePrototype.MakeTime(dateAsUtc.Hour, dateAsUtc.Minute, dateAsUtc.Second, dateAsUtc.Millisecond) ); } else { result = (long) (dateAsUtc - Epoch).TotalMilliseconds; } if (convertToUtcAfter) { result = PrototypeObject.Utc(result); } return result; } }