Przeglądaj źródła

Fix default date constructor to clip decimal part (#1340)

Marko Lahma 2 lat temu
rodzic
commit
c7489b54d2

+ 61 - 52
Jint.Tests/Runtime/DateTests.cs

@@ -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);
     }
 }

+ 16 - 39
Jint/Native/Date/DateConstructor.cs

@@ -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",
@@ -71,18 +71,18 @@ namespace Jint.Native.Date
             _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);
         }
@@ -146,7 +146,7 @@ namespace Jint.Native.Date
                 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)
@@ -196,7 +196,7 @@ namespace Jint.Native.Date
                     return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
                 }
 
-                dv = TimeClip(TypeConverter.ToNumber(v));
+                dv = TypeConverter.ToNumber(v);
             }
             else
             {
@@ -218,7 +218,7 @@ namespace Jint.Native.Date
                     DatePrototype.MakeDay(y, m, dt),
                     DatePrototype.MakeTime(h, min, s, milli));
 
-                dv = TimeClip(PrototypeObject.Utc(finalDate));
+                dv = PrototypeObject.Utc(finalDate);
             }
 
             return OrdinaryCreateFromConstructor(
@@ -227,39 +227,16 @@ namespace Jint.Native.Date
                 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)

+ 24 - 0
Jint/Native/Date/DateExtensions.cs

@@ -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;
+    }
+}

+ 6 - 4
Jint/Native/Date/DateInstance.cs

@@ -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()
@@ -29,7 +31,7 @@ public sealed class DateInstance : ObjectInstance
         return DateTime.MinValue;
     }
 
-    public double DateValue { get; internal set; }
+    public double DateValue => _dateValue;
 
     internal bool DateTimeRangeValid => !double.IsNaN(DateValue) && DateValue <= Max && DateValue >= Min;
 

+ 35 - 58
Jint/Native/Date/DatePrototype.cs

@@ -12,7 +12,7 @@ namespace Jint.Native.Date
     /// <summary>
     /// https://tc39.es/ecma262/#sec-properties-of-the-date-prototype-object
     /// </summary>
-    public sealed class DatePrototype : ObjectInstance
+    internal sealed class DatePrototype : ObjectInstance
     {
         // ES6 section 20.3.1.1 Time Values and Time Range
         private const double MinYear = -1000000.0;
@@ -155,7 +155,7 @@ namespace Jint.Native.Date
         /// <summary>
         /// https://tc39.es/ecma262/#sec-date.prototype.tostring
         /// </summary>
-        public JsValue ToString(JsValue thisObj, JsValue[] arg2)
+        internal JsValue ToString(JsValue thisObj, JsValue[] arg2)
         {
             var tv = ThisTimeValue(thisObj);
             return ToDateString(tv);
@@ -456,9 +456,9 @@ namespace Jint.Native.Date
         {
             ThisTimeValue(thisObj);
             var t = TypeConverter.ToNumber(arguments.At(0));
-            var v = TimeClip(t);
+            var v = t.TimeClip();
 
-            ((DateInstance) thisObj).DateValue = v;
+            ((DateInstance) thisObj)._dateValue = t;
             return v;
         }
 
@@ -476,8 +476,8 @@ namespace Jint.Native.Date
             }
 
             var time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
-            var u = TimeClip(Utc(MakeDate(Day(t), time)));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(MakeDate(Day(t), time)).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -495,8 +495,8 @@ namespace Jint.Native.Date
             }
 
             var time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
-            var u = TimeClip(MakeDate(Day(t), time));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = MakeDate(Day(t), time).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -515,8 +515,8 @@ namespace Jint.Native.Date
             }
 
             var date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
-            var u = TimeClip(Utc(date));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(date).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -535,8 +535,8 @@ namespace Jint.Native.Date
             }
 
             var date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
-            var u = TimeClip(date);
-            ((DateInstance) thisObj).DateValue = u;
+            var u = date.TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -556,8 +556,8 @@ namespace Jint.Native.Date
             }
 
             var date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
-            var u = TimeClip(Utc(date));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(date).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -577,8 +577,8 @@ namespace Jint.Native.Date
             }
 
             var date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
-            var u = TimeClip(date);
-            ((DateInstance) thisObj).DateValue = u;
+            var u = date.TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -599,8 +599,8 @@ namespace Jint.Native.Date
             }
 
             var date = MakeDate(Day(t), MakeTime(h, m, s, milli));
-            var u = TimeClip(Utc(date));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(date).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -621,8 +621,8 @@ namespace Jint.Native.Date
             }
 
             var newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
-            var v = TimeClip(newDate);
-            ((DateInstance) thisObj).DateValue = v;
+            var v = newDate.TimeClip();
+            ((DateInstance) thisObj)._dateValue = v;
             return v;
         }
 
@@ -641,8 +641,8 @@ namespace Jint.Native.Date
 
             var (year, month, __) = YearMonthDayFromTime(t);
             var newDate = MakeDate(MakeDay(year, month, dt), TimeWithinDay(t));
-            var u = TimeClip(Utc(newDate));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(newDate).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -660,8 +660,8 @@ namespace Jint.Native.Date
             }
 
             var newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t));
-            var u = TimeClip(newDate);
-            ((DateInstance) thisObj).DateValue = u;
+            var u = newDate.TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -680,8 +680,8 @@ namespace Jint.Native.Date
             }
 
             var newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t));
-            var u = TimeClip(Utc(newDate));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(newDate).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -700,8 +700,8 @@ namespace Jint.Native.Date
             }
 
             var newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t));
-            var u = TimeClip(newDate);
-            ((DateInstance) thisObj).DateValue = u;
+            var u = newDate.TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -717,8 +717,8 @@ namespace Jint.Native.Date
             var dt = arguments.Length <= 2 ? DateFromTime(t) : TypeConverter.ToNumber(arguments.At(2));
 
             var newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t));
-            var u = TimeClip(Utc(newDate));
-            ((DateInstance) thisObj).DateValue = u;
+            var u = Utc(newDate).TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -732,7 +732,7 @@ namespace Jint.Native.Date
             var y = TypeConverter.ToNumber(arguments.At(0));
             if (double.IsNaN(y))
             {
-                ((DateInstance) thisObj).DateValue = double.NaN;
+                ((DateInstance) thisObj)._dateValue = double.NaN;
                 return JsNumber.DoubleNaN;
             }
 
@@ -744,7 +744,7 @@ namespace Jint.Native.Date
 
             var newDate = MakeDay(fy, MonthFromTime(t), DateFromTime(t));
             var u = Utc(MakeDate(newDate, TimeWithinDay(t)));
-            ((DateInstance) thisObj).DateValue = TimeClip(u);
+            ((DateInstance) thisObj)._dateValue = u.TimeClip();
             return u;
         }
 
@@ -759,8 +759,8 @@ namespace Jint.Native.Date
             var m = arguments.Length <= 1 ? MonthFromTime(t) : TypeConverter.ToNumber(arguments.At(1));
             var dt = arguments.Length <= 2 ? DateFromTime(t) : TypeConverter.ToNumber(arguments.At(2));
             var newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t));
-            var u = TimeClip(newDate);
-            ((DateInstance) thisObj).DateValue = u;
+            var u = newDate.TimeClip();
+            ((DateInstance) thisObj)._dateValue = u;
             return u;
         }
 
@@ -1144,7 +1144,7 @@ namespace Jint.Native.Date
             return t + LocalTza(t, true);
         }
 
-        public double Utc(double t)
+        internal double Utc(double t)
         {
             return t - LocalTza(t, false);
         }
@@ -1265,21 +1265,6 @@ namespace Jint.Native.Date
             return day * MsPerDay + time;
         }
 
-        private static double TimeClip(double time)
-        {
-            if (!IsFinite(time))
-            {
-                return double.NaN;
-            }
-
-            if (System.Math.Abs(time) > 8640000000000000)
-            {
-                return double.NaN;
-            }
-
-            return (long) time + 0;
-        }
-
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool IsFinite(double value)
         {
@@ -1448,12 +1433,4 @@ namespace Jint.Native.Date
             return "Date.prototype";
         }
     }
-
-    internal static class DateExtensions
-    {
-        public static DateTime ToDateTime(this double t)
-        {
-            return DateConstructor.Epoch.AddMilliseconds(t);
-        }
-    }
 }