using System;
using System.Globalization;
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Interop;
namespace Jint.Native.Date
{
public sealed class DateConstructor : FunctionInstance, IConstructor
{
internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public DateConstructor(Engine engine) : base(engine, null, null, false)
{
}
public static DateConstructor CreateDateConstructor(Engine engine)
{
var obj = new DateConstructor(engine);
obj.Extensible = true;
// The value of the [[Prototype]] internal property of the Date constructor is the Function prototype object
obj.Prototype = engine.Function.PrototypeObject;
obj.PrototypeObject = DatePrototype.CreatePrototypeObject(engine, obj);
obj.FastAddProperty("length", 7, false, false, false);
// The initial value of Date.prototype is the Date prototype object
obj.FastAddProperty("prototype", obj.PrototypeObject, false, false, false);
return obj;
}
public void Configure()
{
FastAddProperty("parse", new ClrFunctionInstance(Engine, Parse, 1), true, false, true);
FastAddProperty("UTC", new ClrFunctionInstance(Engine, Utc, 7), true, false, true);
FastAddProperty("now", new ClrFunctionInstance(Engine, Now, 0), true, false, true);
}
private JsValue Parse(JsValue thisObj, JsValue[] arguments)
{
DateTime result;
var date = TypeConverter.ToString(arguments.At(0));
if (!DateTime.TryParseExact(date, new[]
{
"yyyy-MM-ddTHH:mm:ss.FFF",
"yyyy-MM-ddTHH:mm:ss",
"yyyy-MM-ddTHH:mm",
"yyyy-MM-dd",
"yyyy-MM",
"yyyy"
}, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out result))
{
if (!DateTime.TryParseExact(date, new[]
{
// Formats used in DatePrototype toString methods
"ddd MMM dd yyyy HH:mm:ss 'GMT'K",
"ddd MMM dd yyyy",
"HH:mm:ss 'GMT'K",
// standard formats
"yyyy-M-dTH:m:s.FFFK",
"yyyy/M/dTH:m:s.FFFK",
"yyyy-M-dTH:m:sK",
"yyyy/M/dTH:m:sK",
"yyyy-M-dTH:mK",
"yyyy/M/dTH:mK",
"yyyy-M-d H:m:s.FFFK",
"yyyy/M/d H:m:s.FFFK",
"yyyy-M-d H:m:sK",
"yyyy/M/d H:m:sK",
"yyyy-M-d H:mK",
"yyyy/M/d H:mK",
"yyyy-M-dK",
"yyyy/M/dK",
"yyyy-MK",
"yyyy/MK",
"yyyyK",
"THH:mm:ss.FFFK",
"THH:mm:ssK",
"THH:mmK",
"THHK"
}, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
{
if (!DateTime.TryParse(date, Engine.Options._Culture, DateTimeStyles.AdjustToUniversal, out result))
{
if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
{
// unrecognized dates should return NaN (15.9.4.2)
return double.NaN;
}
}
}
}
return FromDateTime(result);
}
private JsValue Utc(JsValue thisObj, JsValue[] arguments)
{
return TimeClip(ConstructTimeValue(arguments, useUtc: true));
}
private JsValue Now(JsValue thisObj, JsValue[] arguments)
{
return System.Math.Floor((DateTime.UtcNow - Epoch).TotalMilliseconds);
}
public override JsValue Call(JsValue thisObject, JsValue[] arguments)
{
return PrototypeObject.ToString(Construct(Arguments.Empty), Arguments.Empty);
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.3
///
///
///
public ObjectInstance Construct(JsValue[] arguments)
{
if (arguments.Length == 0)
{
return Construct(DateTime.UtcNow);
}
else if (arguments.Length == 1)
{
var v = TypeConverter.ToPrimitive(arguments[0]);
if (v.IsString())
{
return Construct(Parse(Undefined.Instance, Arguments.From(v)).AsNumber());
}
return Construct(TypeConverter.ToNumber(v));
}
else
{
return Construct(ConstructTimeValue(arguments, useUtc: false));
}
}
private double ConstructTimeValue(JsValue[] arguments, bool useUtc)
{
if (arguments.Length < 2)
{
throw new ArgumentOutOfRangeException("arguments", "There must be at least two arguments.");
}
var y = TypeConverter.ToNumber(arguments[0]);
var m = (int)TypeConverter.ToInteger(arguments[1]);
var dt = arguments.Length > 2 ? (int)TypeConverter.ToInteger(arguments[2]) : 1;
var h = arguments.Length > 3 ? (int)TypeConverter.ToInteger(arguments[3]) : 0;
var min = arguments.Length > 4 ? (int)TypeConverter.ToInteger(arguments[4]) : 0;
var s = arguments.Length > 5 ? (int)TypeConverter.ToInteger(arguments[5]) : 0;
var milli = arguments.Length > 6 ? (int)TypeConverter.ToInteger(arguments[6]) : 0;
for (int i = 2; i < arguments.Length; i++)
{
if (double.IsNaN(TypeConverter.ToNumber(arguments[i])))
{
return double.NaN;
}
}
if ((!double.IsNaN(y)) && (0 <= TypeConverter.ToInteger(y)) && (TypeConverter.ToInteger(y) <= 99))
{
y += 1900;
}
var finalDate = DatePrototype.MakeDate(
DatePrototype.MakeDay(y, m, dt),
DatePrototype.MakeTime(h, min, s, milli));
return TimeClip(useUtc ? finalDate : PrototypeObject.Utc(finalDate));
}
public DatePrototype PrototypeObject { get; private set; }
public DateInstance Construct(DateTimeOffset value)
{
return Construct(value.UtcDateTime);
}
public DateInstance Construct(DateTime value)
{
var instance = new DateInstance(Engine)
{
Prototype = PrototypeObject,
PrimitiveValue = FromDateTime(value),
Extensible = true
};
return instance;
}
public DateInstance Construct(double time)
{
var instance = new DateInstance(Engine)
{
Prototype = PrototypeObject,
PrimitiveValue = TimeClip(time),
Extensible = true
};
return instance;
}
public static double TimeClip(double time)
{
if (double.IsInfinity(time) || double.IsNaN(time))
{
return double.NaN;
}
if (System.Math.Abs(time) > 8640000000000000)
{
return double.NaN;
}
return TypeConverter.ToInteger(time);
}
public double FromDateTime(DateTime dt)
{
var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);
var dateAsUtc = dt.Kind == DateTimeKind.Local
? dt.ToUniversalTime()
: DateTime.SpecifyKind(dt, DateTimeKind.Utc);
var result = (dateAsUtc - Epoch).TotalMilliseconds;
if (convertToUtcAfter)
{
result = PrototypeObject.Utc(result);
}
return System.Math.Floor(result);
}
}
}