Skip to content

Commit

Permalink
Fix default date constructor to clip decimal part (#1340)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Nov 1, 2022
1 parent 421401b commit c7489b5
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 153 deletions.
113 changes: 61 additions & 52 deletions Jint.Tests/Runtime/DateTests.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,73 @@
namespace Jint.Tests.Runtime
namespace Jint.Tests.Runtime;

public class DateTests
{
public class DateTests
private readonly Engine _engine;

public DateTests()
{
private readonly Engine _engine;
_engine = new Engine()
.SetValue("log", new Action<object>(Console.WriteLine))
.SetValue("assert", new Action<bool>(Assert.True))
.SetValue("equal", new Action<object, object>(Assert.Equal));
}

public DateTests()
{
_engine = new Engine()
.SetValue("log", new Action<object>(Console.WriteLine))
.SetValue("assert", new Action<bool>(Assert.True))
.SetValue("equal", new Action<object, object>(Assert.Equal));
}
[Fact]
public void NaNToString()
{
var value = _engine.Evaluate("new Date(NaN).toString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToString()
{
var value = _engine.Evaluate("new Date(NaN).toString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void NaNToDateString()
{
var value = _engine.Evaluate("new Date(NaN).toDateString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToDateString()
{
var value = _engine.Evaluate("new Date(NaN).toDateString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void NaNToTimeString()
{
var value = _engine.Evaluate("new Date(NaN).toTimeString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToTimeString()
{
var value = _engine.Evaluate("new Date(NaN).toTimeString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void NaNToLocaleString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToLocaleString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void NaNToLocaleDateString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleDateString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToLocaleDateString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleDateString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void NaNToLocaleTimeString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleTimeString();").AsString();
Assert.Equal("Invalid Date", value);
}

[Fact]
public void NaNToLocaleTimeString()
{
var value = _engine.Evaluate("new Date(NaN).toLocaleTimeString();").AsString();
Assert.Equal("Invalid Date", value);
}
[Fact]
public void ToJsonFromNaNObject()
{
var result = _engine.Evaluate("JSON.stringify({ date: new Date(NaN) });");
Assert.Equal("{\"date\":null}", result.ToString());
}

[Fact]
public void ValuePrecisionIsIntegral()
{
var number = _engine.Evaluate("new Date() / 1").AsNumber();
Assert.Equal((long) number, number);

[Fact]
public void ToJsonFromNaNObject()
{
var result = _engine.Evaluate("JSON.stringify({ date: new Date(NaN) });");
Assert.Equal("{\"date\":null}", result.ToString());
}
var dateInstance = _engine.Realm.Intrinsics.Date.Construct(123.456d);
Assert.Equal((long) dateInstance.DateValue, dateInstance.DateValue);
}
}
55 changes: 16 additions & 39 deletions Jint/Native/Date/DateConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Jint.Native.Date
/// </summary>
public sealed class DateConstructor : FunctionInstance, IConstructor
{
internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
internal static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

private static readonly string[] DefaultFormats = {
"yyyy-MM-dd",
Expand Down Expand Up @@ -71,18 +71,18 @@ internal DateConstructor(
_prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
}

public DatePrototype PrototypeObject { get; }
internal DatePrototype PrototypeObject { get; }

protected override void Initialize()
{
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
const PropertyFlag LengthFlags = PropertyFlag.Configurable;
const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;

var properties = new PropertyDictionary(3, checkExistingKeys: false)
{
["parse"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "parse", Parse, 1, lengthFlags), propertyFlags),
["UTC"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "UTC", Utc, 7, lengthFlags), propertyFlags),
["now"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "now", Now, 0, lengthFlags), propertyFlags)
["parse"] = new(new ClrFunctionInstance(Engine, "parse", Parse, 1, LengthFlags), PropertyFlags),
["UTC"] = new(new ClrFunctionInstance(Engine, "UTC", Utc, 7, LengthFlags), PropertyFlags),
["now"] = new(new ClrFunctionInstance(Engine, "now", Now, 0, LengthFlags), PropertyFlags)
};
SetProperties(properties);
}
Expand Down Expand Up @@ -146,7 +146,7 @@ private static JsValue Utc(JsValue thisObj, JsValue[] arguments)
DatePrototype.MakeDay(y, m, dt),
DatePrototype.MakeTime(h, min, s, milli));

return TimeClip(finalDate);
return finalDate.TimeClip();
}

private static JsValue Now(JsValue thisObj, JsValue[] arguments)
Expand Down Expand Up @@ -196,7 +196,7 @@ private DateInstance ConstructUnlikely(JsValue[] arguments, JsValue newTarget)
return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
}

dv = TimeClip(TypeConverter.ToNumber(v));
dv = TypeConverter.ToNumber(v);
}
else
{
Expand All @@ -218,7 +218,7 @@ private DateInstance ConstructUnlikely(JsValue[] arguments, JsValue newTarget)
DatePrototype.MakeDay(y, m, dt),
DatePrototype.MakeTime(h, min, s, milli));

dv = TimeClip(PrototypeObject.Utc(finalDate));
dv = PrototypeObject.Utc(finalDate);
}

return OrdinaryCreateFromConstructor(
Expand All @@ -227,39 +227,16 @@ private DateInstance ConstructUnlikely(JsValue[] arguments, JsValue newTarget)
static (engine, _, dateValue) => new DateInstance(engine, dateValue), dv);
}

public DateInstance Construct(DateTimeOffset value)
{
return Construct(value.UtcDateTime);
}
public DateInstance Construct(DateTimeOffset value) => Construct(value.UtcDateTime);

public DateInstance Construct(DateTime value)
{
return Construct(FromDateTime(value));
}
public DateInstance Construct(DateTime value) => Construct(FromDateTime(value));

public DateInstance Construct(double time)
{
var instance = new DateInstance(_engine, TimeClip(time))
{
_prototype = PrototypeObject
};

return instance;
}

private 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) + 0;
return OrdinaryCreateFromConstructor(
Undefined,
static intrinsics => intrinsics.Date.PrototypeObject,
static (engine, _, dateValue) => new DateInstance(engine, dateValue), time);
}

private double FromDateTime(DateTime dt, bool negative = false)
Expand Down
24 changes: 24 additions & 0 deletions Jint/Native/Date/DateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Jint.Native.Date;

internal static class DateExtensions
{
public static DateTime ToDateTime(this double t)
{
return DateConstructor.Epoch.AddMilliseconds(t);
}

internal static double TimeClip(this double time)
{
if (double.IsInfinity(time) || double.IsNaN(time))
{
return double.NaN;
}

if (System.Math.Abs(time) > 8640000000000000)
{
return double.NaN;
}

return (long) time;
}
}
10 changes: 6 additions & 4 deletions Jint/Native/Date/DateInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ namespace Jint.Native.Date;
public sealed class DateInstance : ObjectInstance
{
// Maximum allowed value to prevent DateTime overflow
private static readonly double Max = (DateTime.MaxValue - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
private static readonly long Max = (long) (DateTime.MaxValue - DateConstructor.Epoch).TotalMilliseconds;

// Minimum allowed value to prevent DateTime overflow
private static readonly double Min = -(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) - DateTime.MinValue).TotalMilliseconds;
private static readonly long Min = (long) -(DateConstructor.Epoch - DateTime.MinValue).TotalMilliseconds;

internal double _dateValue;

public DateInstance(Engine engine, double dateValue)
: base(engine, ObjectClass.Date)
{
DateValue = dateValue;
_dateValue = dateValue.TimeClip();
}

public DateTime ToDateTime()
Expand All @@ -29,7 +31,7 @@ public DateTime ToDateTime()
return DateTime.MinValue;
}

public double DateValue { get; internal set; }
public double DateValue => _dateValue;

internal bool DateTimeRangeValid => !double.IsNaN(DateValue) && DateValue <= Max && DateValue >= Min;

Expand Down
Loading

0 comments on commit c7489b5

Please sign in to comment.