#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;
}
}