Browse Source

Date related fixes and improvements (#1107)

Marko Lahma 3 years ago
parent
commit
650c0cbc4e

+ 0 - 5
Jint.Tests.Test262/State.cs

@@ -13,9 +13,4 @@ public static partial class State
     /// Pre-compiled scripts for faster execution.
     /// </summary>
     public static readonly Dictionary<string, Script> Sources = new(StringComparer.OrdinalIgnoreCase);
-
-    /// <summary>
-    /// Time zone to use by default.
-    /// </summary>
-    public static TimeZoneInfo TimeZone;
 }

+ 0 - 15
Jint.Tests.Test262/Test262Harness.settings.json

@@ -101,9 +101,6 @@
     // there is bug in suite and bug in Jint, refer to https://github.com/sebastienros/jint/issues/888 and https://github.com/tc39/test262/issues/2985
     "built-ins/Promise/race/resolve-element-function-name.js",
 
-    "built-ins/Symbol/species/subclassing.js", // subclassing not implemented
-    "built-ins/Date/subclassing.js", // subclassing not implemented
-
     // parsing of large/small years not implemented in .NET (-271821, +271821)
     "built-ins/Date/parse/time-value-maximum-range.js",
 
@@ -222,18 +219,6 @@
     "built-ins/Date/prototype/*/negative-year.js",
     
     // failing tests in new test suite (due to updating to latest and using whole set)
-    "built-ins/Date/prototype/setDate/arg-coercion-order.js",
-    "built-ins/Date/prototype/setHours/arg-coercion-order.js",
-    "built-ins/Date/prototype/setMilliseconds/arg-coercion-order.js",
-    "built-ins/Date/prototype/setMinutes/arg-coercion-order.js",
-    "built-ins/Date/prototype/setMonth/arg-coercion-order.js",
-    "built-ins/Date/prototype/setSeconds/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCDate/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCHours/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCMilliseconds/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCMinutes/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCMonth/arg-coercion-order.js",
-    "built-ins/Date/prototype/setUTCSeconds/arg-coercion-order.js",
     "language/arguments-object/mapped/nonconfigurable-descriptors-define-failure.js",
     "language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js",
     "language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js",

+ 0 - 3
Jint.Tests.Test262/Test262Test.cs

@@ -16,9 +16,6 @@ public abstract partial class Test262Test
     {
         var engine = new Engine(cfg =>
         {
-            cfg
-                .LocalTimeZone(State.TimeZone);
-
             var relativePath = Path.GetDirectoryName(file.FileName);
             cfg.EnableModules(new Test262ModuleLoader(State.Test262Stream.Options.FileSystem, relativePath));
         });

+ 1 - 14
Jint.Tests.Test262/TestHarness.cs

@@ -1,4 +1,3 @@
-using System;
 using System.IO;
 using System.Threading.Tasks;
 
@@ -13,18 +12,6 @@ public partial class TestHarness
 {
     private static partial Task InitializeCustomState()
     {
-        // NOTE: The Date tests in test262 assume the local timezone is Pacific Standard Time
-        try
-        {
-            State.TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
-        }
-        catch (TimeZoneNotFoundException)
-        {
-            // https://stackoverflow.com/questions/47848111/how-should-i-fetch-timezoneinfo-in-a-platform-agnostic-way
-            // should be natively supported soon https://github.com/dotnet/runtime/issues/18644
-            State.TimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
-        }
-
         foreach (var file in State.HarnessFiles)
         {
             var source = file.Program;
@@ -33,4 +20,4 @@ public partial class TestHarness
 
         return Task.CompletedTask;
     }
-}
+}

+ 13 - 13
Jint.Tests/Runtime/EngineTests.cs

@@ -1115,7 +1115,7 @@ namespace Jint.Tests.Runtime
             Assert.Equal(-11 * 60 * 1000, parseLocalEpoch);
 
             var epochToLocalString = engine.Evaluate("var d = new Date(0); d.toString();").AsString();
-            Assert.Equal("Thu Jan 01 1970 00:11:00 GMT+0011", epochToLocalString);
+            Assert.Equal("Thu Jan 01 1970 00:11:00 GMT+0011 (Custom Time)", epochToLocalString);
 
             var epochToUTCString = engine.Evaluate("var d = new Date(0); d.toUTCString();").AsString();
             Assert.Equal("Thu, 01 Jan 1970 00:00:00 GMT", epochToUTCString);
@@ -1210,18 +1210,19 @@ namespace Jint.Tests.Runtime
             Assert.Equal(testDateTimeOffset.UtcDateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture), engine.Evaluate("d.toISOString();").ToString());
         }
 
-        [Theory, MemberData("TestDates")]
+        [Theory, MemberData(nameof(TestDates))]
         public void TestDateToStringFormat(DateTime testDate)
         {
             var customTimeZone = _pacificTimeZone;
 
             var engine = new Engine(ctx => ctx.LocalTimeZone(customTimeZone));
-            var testDateTimeOffset = new DateTimeOffset(testDate, customTimeZone.GetUtcOffset(testDate));
-            engine.Execute(
-                string.Format("var d = new Date({0},{1},{2},{3},{4},{5},{6});", testDateTimeOffset.Year, testDateTimeOffset.Month - 1, testDateTimeOffset.Day, testDateTimeOffset.Hour, testDateTimeOffset.Minute, testDateTimeOffset.Second, testDateTimeOffset.Millisecond));
+            var dt = new DateTimeOffset(testDate, customTimeZone.GetUtcOffset(testDate));
+            var dateScript = $"var d = new Date({dt.Year}, {dt.Month - 1}, {dt.Day}, {dt.Hour}, {dt.Minute}, {dt.Second}, {dt.Millisecond});";
+            engine.Execute(dateScript);
 
-            var expected = testDateTimeOffset.ToString("ddd MMM dd yyyy HH:mm:ss", CultureInfo.InvariantCulture);
-            expected += testDateTimeOffset.ToString(" 'GMT'zzz", CultureInfo.InvariantCulture).Replace(":", "");
+            var expected = dt.ToString("ddd MMM dd yyyy HH:mm:ss", CultureInfo.InvariantCulture);
+            expected += dt.ToString(" 'GMT'zzz", CultureInfo.InvariantCulture).Replace(":", "");
+            expected += " (Pacific Standard Time)";
             var actual = engine.Evaluate("d.toString();").ToString();
 
             Assert.Equal(expected, actual);
@@ -1813,9 +1814,9 @@ var prep = function (fn) { fn(); };
             engine.Evaluate(@"
                     var d = new Date(1433160000000);
 
-                    equal('Mon Jun 01 2015 05:00:00 GMT-0700', d.toString());
+                    equal('Mon Jun 01 2015 05:00:00 GMT-0700 (Pacific Standard Time)', d.toString());
                     equal('Mon Jun 01 2015', d.toDateString());
-                    equal('05:00:00 GMT-0700', d.toTimeString());
+                    equal('05:00:00 GMT-0700 (Pacific Standard Time)', d.toTimeString());
                     equal('lundi 1 juin 2015 05:00:00', d.toLocaleString());
                     equal('lundi 1 juin 2015', d.toLocaleDateString());
                     equal('05:00:00', d.toLocaleTimeString());
@@ -1829,13 +1830,12 @@ var prep = function (fn) { fn(); };
             var engine = new Engine(options => options.LocalTimeZone(EST))
                 .SetValue("log", new Action<object>(Console.WriteLine))
                 .SetValue("assert", new Action<bool>(Assert.True))
-                .SetValue("equal", new Action<object, object>(Assert.Equal))
-                ;
+                .SetValue("equal", new Action<object, object>(Assert.Equal));
 
             engine.Evaluate(@"
                     var d = new Date(2016, 8, 1);
-
-                    equal('Thu Sep 01 2016 00:00:00 GMT-0400', d.toString());
+                    // there's a Linux difference, so do a replace
+                    equal('Thu Sep 01 2016 00:00:00 GMT-0400 (US Eastern Standard Time)', d.toString().replace('(Eastern Standard Time)', '(US Eastern Standard Time)'));
                     equal('Thu Sep 01 2016', d.toDateString());
             ");
         }

+ 34 - 7
Jint/Native/Date/DateConstructor.cs

@@ -87,9 +87,26 @@ namespace Jint.Native.Date
             SetProperties(properties);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-date.parse
+        /// </summary>
         private JsValue Parse(JsValue thisObj, JsValue[] arguments)
         {
             var date = TypeConverter.ToString(arguments.At(0));
+            var negative = date.StartsWith("-");
+            if (negative)
+            {
+                date = date.Substring(1);
+            }
+
+            var startParen = date.IndexOf('(');
+            if (startParen != -1)
+            {
+                // informative text
+                date = date.Substring(0, startParen);
+            }
+
+            date = date.Trim();
 
             if (!DateTime.TryParseExact(date, DefaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
             {
@@ -106,7 +123,7 @@ namespace Jint.Native.Date
                 }
             }
 
-            return FromDateTime(result);
+            return FromDateTime(result, negative);
         }
 
         private static JsValue Utc(JsValue thisObj, JsValue[] arguments)
@@ -145,7 +162,7 @@ namespace Jint.Native.Date
         ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.3
+        /// https://tc39.es/ecma262/#sec-date
         /// </summary>
         private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
@@ -158,7 +175,7 @@ namespace Jint.Native.Date
             {
                 if (arguments[0] is DateInstance date)
                 {
-                    return Construct(date.PrimitiveValue);
+                    return Construct(date.DateValue);
                 }
 
                 var v = TypeConverter.ToPrimitive(arguments[0]);
@@ -196,7 +213,7 @@ namespace Jint.Native.Date
                 newTarget,
                 static intrinsics => intrinsics.Date.PrototypeObject,
                 static (engine, realm, _) => new DateInstance(engine));
-            o.PrimitiveValue = dv;
+            o.DateValue = dv;
             return o;
         }
 
@@ -215,7 +232,7 @@ namespace Jint.Native.Date
             var instance = new DateInstance(_engine)
             {
                 _prototype = PrototypeObject,
-                PrimitiveValue = TimeClip(time)
+                DateValue = TimeClip(time)
             };
 
             return instance;
@@ -236,7 +253,7 @@ namespace Jint.Native.Date
             return TypeConverter.ToInteger(time) + 0;
         }
 
-        private double FromDateTime(DateTime dt)
+        private double FromDateTime(DateTime dt, bool negative = false)
         {
             var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);
 
@@ -244,7 +261,17 @@ namespace Jint.Native.Date
                 ? dt.ToUniversalTime()
                 : DateTime.SpecifyKind(dt, DateTimeKind.Utc);
 
-            var result = (dateAsUtc - Epoch).TotalMilliseconds;
+            double result;
+            if (negative)
+            {
+                var zero = (Epoch - DateTime.MinValue).TotalMilliseconds;
+                result = zero - TimeSpan.FromTicks(dateAsUtc.Ticks).TotalMilliseconds;
+                result *= -1;
+            }
+            else
+            {
+                result = (dateAsUtc - Epoch).TotalMilliseconds;
+            }
 
             if (convertToUtcAfter)
             {

+ 7 - 7
Jint/Native/Date/DateInstance.cs

@@ -16,37 +16,37 @@ namespace Jint.Native.Date
         public DateInstance(Engine engine)
             : base(engine, ObjectClass.Date)
         {
-            PrimitiveValue = double.NaN;
+            DateValue = double.NaN;
         }
 
         public DateTime ToDateTime()
         {
             if (DateTimeRangeValid)
             {
-                return DateConstructor.Epoch.AddMilliseconds(PrimitiveValue);
+                return DateConstructor.Epoch.AddMilliseconds(DateValue);
             }
 
             ExceptionHelper.ThrowRangeError(_engine.Realm);
             return DateTime.MinValue;
         }
 
-        public double PrimitiveValue { get; set; }
+        public double DateValue { get; internal set; }
 
-        internal bool DateTimeRangeValid => !double.IsNaN(PrimitiveValue) && PrimitiveValue <= Max && PrimitiveValue >= Min;
+        internal bool DateTimeRangeValid => !double.IsNaN(DateValue) && DateValue <= Max && DateValue >= Min;
 
         public override string ToString()
         {
-            if (double.IsNaN(PrimitiveValue))
+            if (double.IsNaN(DateValue))
             {
                 return "NaN";
             }
 
-            if (double.IsInfinity(PrimitiveValue))
+            if (double.IsInfinity(DateValue))
             {
                 return "Infinity";
             }
 
-            return ToDateTime().ToString("ddd MMM dd yyyy HH:mm:ss 'GMT'K", CultureInfo.InvariantCulture);
+            return ToDateTime().ToString("ddd MMM dd yyyy HH:mm:ss 'GMT'zzz", CultureInfo.InvariantCulture);
         }
     }
 }

File diff suppressed because it is too large
+ 312 - 191
Jint/Native/Date/DatePrototype.cs


Some files were not shown because too many files changed in this diff