Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date related fixes and improvements #1107

Merged
merged 3 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 0 additions & 5 deletions Jint.Tests.Test262/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
15 changes: 0 additions & 15 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Expand Down Expand Up @@ -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",
Expand Down
3 changes: 0 additions & 3 deletions Jint.Tests.Test262/Test262Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ private Engine BuildTestExecutor(Test262File file)
{
var engine = new Engine(cfg =>
{
cfg
.LocalTimeZone(State.TimeZone);

var relativePath = Path.GetDirectoryName(file.FileName);
cfg.EnableModules(new Test262ModuleLoader(State.Test262Stream.Options.FileSystem, relativePath));
});
Expand Down
15 changes: 1 addition & 14 deletions Jint.Tests.Test262/TestHarness.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.IO;
using System.Threading.Tasks;

Expand All @@ -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;
Expand All @@ -33,4 +20,4 @@ public partial class TestHarness

return Task.CompletedTask;
}
}
}
26 changes: 13 additions & 13 deletions Jint.Tests/Runtime/EngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,7 @@ public void ShouldUseLocalTimeZoneOverride()
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);
Expand Down Expand Up @@ -1210,18 +1210,19 @@ public void TestDateToISOStringFormat(DateTime testDate)
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);
Expand Down Expand Up @@ -1813,9 +1814,9 @@ public void DateToStringMethodsShouldUseCurrentTimeZoneAndCulture()
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());
Expand All @@ -1829,13 +1830,12 @@ public void DateShouldHonorTimezoneDaylightSavingRules()
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());
");
}
Expand Down
41 changes: 34 additions & 7 deletions Jint/Native/Date/DateConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,26 @@ protected override void Initialize()
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))
{
Expand All @@ -106,7 +123,7 @@ private JsValue Parse(JsValue thisObj, JsValue[] arguments)
}
}

return FromDateTime(result);
return FromDateTime(result, negative);
}

private static JsValue Utc(JsValue thisObj, JsValue[] arguments)
Expand Down Expand Up @@ -145,7 +162,7 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments)
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)
{
Expand All @@ -158,7 +175,7 @@ private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
{
if (arguments[0] is DateInstance date)
{
return Construct(date.PrimitiveValue);
return Construct(date.DateValue);
}

var v = TypeConverter.ToPrimitive(arguments[0]);
Expand Down Expand Up @@ -196,7 +213,7 @@ private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
newTarget,
static intrinsics => intrinsics.Date.PrototypeObject,
static (engine, realm, _) => new DateInstance(engine));
o.PrimitiveValue = dv;
o.DateValue = dv;
return o;
}

Expand All @@ -215,7 +232,7 @@ public DateInstance Construct(double time)
var instance = new DateInstance(_engine)
{
_prototype = PrototypeObject,
PrimitiveValue = TimeClip(time)
DateValue = TimeClip(time)
};

return instance;
Expand All @@ -236,15 +253,25 @@ private static double TimeClip(double time)
return TypeConverter.ToInteger(time) + 0;
}

private double FromDateTime(DateTime dt)
private double FromDateTime(DateTime dt, bool negative = false)
{
var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);

var dateAsUtc = dt.Kind == DateTimeKind.Local
? 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)
{
Expand Down
14 changes: 7 additions & 7 deletions Jint/Native/Date/DateInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,37 @@ public sealed class DateInstance : ObjectInstance
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);
}
}
}