Skip to content

Commit

Permalink
Fixes for #114. Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nilproject committed Apr 22, 2018
1 parent 6c64c3c commit 3133a17
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 65 deletions.
18 changes: 18 additions & 0 deletions IntegrationTests/BaseLibrary/DateTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NiL.JS.BaseLibrary;
using NiL.JS.Core;
Expand All @@ -22,5 +23,22 @@ public void NewDateShouldContainCurrentTime()
Assert.AreEqual(date.getMinutes(), dateTime.Minute);
Assert.AreEqual(date.getSeconds(), dateTime.Second);
}

[TestMethod]
public void ShouldCorrectHandleSwitchFromDstToStandard_SidneyTimeZone()
{
var timezone = TimeZoneInfo.GetSystemTimeZones()
.First(x => x.BaseUtcOffset.Ticks == 10 * 3600 * 10000000L
&& x.Id.Contains("AUS"));
Date.CurrentTimeZone = timezone;

var d1 = new Date(new Arguments { 953996400000 });
var d2 = new Date(new Arguments { 954000000000 });
var d3 = new Date(new Arguments { 954003600000 });

Assert.IsTrue(d1.ToString().StartsWith("Sun Mar 26 2000 02:00:00 GMT+1100"));
Assert.IsTrue(d2.ToString().StartsWith("Sun Mar 26 2000 02:00:00 GMT+1000"));
Assert.IsTrue(d3.ToString().StartsWith("Sun Mar 26 2000 03:00:00 GMT+1000"));
}
}
}
147 changes: 82 additions & 65 deletions NiL.JS/BaseLibrary/Date.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ namespace NiL.JS.BaseLibrary
#endif
public sealed class Date
{
private static TimeZoneInfo s_currentTimeZone = TimeZoneInfo.Local;
[Hidden]
public static TimeZoneInfo CurrentTimeZone
{
get => s_currentTimeZone;
set => s_currentTimeZone = value ?? throw new ArgumentNullException(nameof(value));
}

private const long _timeAccuracy = TimeSpan.TicksPerMillisecond;
private const long _unixTimeBase = 62135596800000;
private const long _minuteMillisecond = 60 * 1000;
Expand Down Expand Up @@ -38,7 +46,7 @@ public sealed class Date
};

private static readonly string[] daysOfWeek = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
private readonly static string[] months = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
private static readonly string[] months = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

private long _time;
private long _timeZoneOffset;
Expand All @@ -48,16 +56,17 @@ public sealed class Date
public Date()
{
_time = DateTime.Now.Ticks / 10000;
_timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Ticks / 10000;
_timeZoneOffset = CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks / 10000;
_time -= _timeZoneOffset;
}

[DoNotEnumerate]
public Date(DateTime dateTime)
{
_time = dateTime.Ticks / 10000;
_timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(dateTime).Ticks / 10000;
_time -= _timeZoneOffset;
_timeZoneOffset = CurrentTimeZone.GetUtcOffset(dateTime).Ticks / 10000;
if (dateTime.Kind != DateTimeKind.Utc)
_time -= _timeZoneOffset;
}

[DoNotEnumerate]
Expand Down Expand Up @@ -185,7 +194,7 @@ public Date(Arguments args)
private static long getTimeZoneOffset(long time)
{
var dateTime = new DateTime(System.Math.Min(System.Math.Max(time * _timeAccuracy, DateTime.MinValue.Ticks), DateTime.MaxValue.Ticks), DateTimeKind.Utc);
var offset = TimeZoneInfo.Local.GetUtcOffset(dateTime).Ticks / _timeAccuracy;
var offset = CurrentTimeZone.GetUtcOffset(dateTime).Ticks / _timeAccuracy;
return offset;
}

Expand Down Expand Up @@ -225,7 +234,7 @@ public JSValue getTime()
public static JSValue now()
{
var time = DateTime.Now.Ticks / 10000;
var timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Ticks / 10000;
var timeZoneOffset = CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks / 10000;
return time - timeZoneOffset - _unixTimeBase;
}

Expand Down Expand Up @@ -689,26 +698,66 @@ public JSValue toLocaleTimeString()
[DoNotEnumerate]
public JSValue toISOString()
{
try
{
_time -= _timeZoneOffset;
if (_time > 8702135600400000 || _time < -8577864403200000 || _error)
ExceptionHelper.Throw(new RangeError("Invalid time value"));
var y = getYearImpl(true);

return y +
"-" + (this.getMonthImpl(true) + 1).ToString("00") +
"-" + this.getDateImpl(true).ToString("00") +
"T" + this.getHoursImpl(true).ToString("00") +
":" + this.getMinutesImpl(true).ToString("00") +
":" + this.getSecondsImpl().ToString("00") +
"." + (this.getMillisecondsImpl() / 1000.0).ToString(".000", System.Globalization.CultureInfo.InvariantCulture).Substring(1) +
"Z";
}
finally
{
_time += _timeZoneOffset;
}
return toIsoString();
}

private JSValue toIsoString()
{
if ((_time + _timeZoneOffset) > 8702135600400000 || (_time + _timeZoneOffset) < -8577864403200000 || _error)
ExceptionHelper.Throw(new RangeError("Invalid time value"));

var y = getYearImpl(true);

return y +
"-" + (this.getMonthImpl(false) + 1).ToString("00") +
"-" + this.getDateImpl(false).ToString("00") +
"T" + this.getHoursImpl(false).ToString("00") +
":" + this.getMinutesImpl(false).ToString("00") +
":" + this.getSecondsImpl().ToString("00") +
"." + (this.getMillisecondsImpl() / 1000.0).ToString(".000", System.Globalization.CultureInfo.InvariantCulture).Substring(1) +
"Z";
}

private string stringify(bool withTzo)
{
if (_error)
return "Invalid date";

return stringifyDate(withTzo) + " " + stringifyTime(withTzo);
}

private string stringifyDate(bool withTzo)
{
if (_error)
return "Invalid date";

var res =
daysOfWeek[(getDayImpl(withTzo) + 6) % 7] + " "
+ months[getMonthImpl(withTzo)]
+ " " + getDateImpl(withTzo).ToString("00") + " "
+ getYearImpl(withTzo);
return res;
}

private string stringifyTime(bool withTzo)
{
if (_error)
return "Invalid date";

var offset = new TimeSpan(_timeZoneOffset * _timeAccuracy);
var timeName = CurrentTimeZone.IsDaylightSavingTime(new DateTimeOffset(_time * _timeAccuracy, offset)) ? TimeZoneInfo.Local.DaylightName : TimeZoneInfo.Local.StandardName;
var res =
getHoursImpl(withTzo).ToString("00:")
+ getMinutesImpl(withTzo).ToString("00:")
+ getSecondsImpl().ToString("00")
+ " GMT" + (offset.Ticks > 0 ? "+" : "") + (offset.Hours * 100 + offset.Minutes).ToString("0000") + " (" + timeName + ")";
return res;
}

[Hidden]
public override string ToString()
{
return stringify(true);
}

[DoNotEnumerate]
Expand All @@ -720,37 +769,25 @@ public JSValue toJSON(JSValue obj)
[DoNotEnumerate]
public JSValue toUTCString()
{
return ToString();
return stringify(false);
}

[DoNotEnumerate]
public JSValue toGMTString()
{
return ToString();
return stringify(false);
}

[DoNotEnumerate]
public JSValue toTimeString()
{
var offset = new TimeSpan(_timeZoneOffset * 10000);
var timeName = TimeZoneInfo.Local.IsDaylightSavingTime(new DateTimeOffset(_time * _timeAccuracy, offset)) ? TimeZoneInfo.Local.DaylightName : TimeZoneInfo.Local.StandardName;
var res =
getHoursImpl(true).ToString("00:")
+ getMinutesImpl(true).ToString("00:")
+ getSecondsImpl().ToString("00")
+ " GMT" + (offset.Ticks > 0 ? "+" : "") + (offset.Hours * 100 + offset.Minutes).ToString("0000") + " (" + timeName + ")";
return res;
return stringifyTime(true);
}

[DoNotEnumerate]
public JSValue toDateString()
{
var res =
daysOfWeek[(System.Math.Abs(_time) % _weekMilliseconds) / _dayMilliseconds] + " "
+ months[getMonthImpl(false)]
+ " " + getDateImpl(false).ToString("00") + " "
+ getYearImpl(false);
return res;
return stringifyDate(true);
}

[DoNotEnumerate]
Expand All @@ -762,7 +799,7 @@ public JSValue toLocaleDateString()
while (y < 0)
y += 2800;
var dt = new DateTime(0);
dt = dt.AddDays((System.Math.Abs(_time + _timeZoneOffset) % _weekMilliseconds) / _dayMilliseconds);
dt = dt.AddDays(getDateImpl(true));
dt = dt.AddMonths(getMonthImpl(true));
dt = dt.AddYears(y);
#if (PORTABLE || NETCORE)
Expand All @@ -772,26 +809,6 @@ public JSValue toLocaleDateString()
#endif
}

[Hidden]
public override string ToString()
{
if (_error)
return "Invalid date";

var offset = new TimeSpan(_timeZoneOffset * _timeAccuracy);
var timeName = TimeZoneInfo.Local.IsDaylightSavingTime(new DateTimeOffset(_time * _timeAccuracy, offset)) ? TimeZoneInfo.Local.DaylightName : TimeZoneInfo.Local.StandardName;
var res =
daysOfWeek[System.Math.Abs(_time + _timeZoneOffset) / _dayMilliseconds % 7] + " "
+ months[getMonthImpl(true)]
+ " " + getDateImpl(true).ToString("00") + " "
+ getYearImpl(true) + " "
+ getHoursImpl(true).ToString("00:")
+ getMinutesImpl(true).ToString("00:")
+ getSecondsImpl().ToString("00")
+ " GMT" + (offset.Ticks > 0 ? "+" : "") + (offset.Hours * 100 + offset.Minutes).ToString("0000") + " (" + timeName + ")";
return res;
}

[DoNotEnumerate]
public static JSValue parse(string dateTime)
{
Expand Down Expand Up @@ -1077,7 +1094,7 @@ private static bool parseSelf(string timeStr, out long time, out long timeZoneOf
if (pm)
time += _hourMilliseconds * 12;

timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(new DateTime(time * _timeAccuracy)).Ticks / 10000;
timeZoneOffset = CurrentTimeZone.GetUtcOffset(new DateTime(time * _timeAccuracy)).Ticks / 10000;
if (wasTZ)
time += timeZoneOffset;
}
Expand Down Expand Up @@ -1271,7 +1288,7 @@ private static bool parseIso8601(string timeStr, out long time, out long timeZon

if (computeTzo)
{
timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(new DateTime(time * _timeAccuracy)).Ticks / 10000;
timeZoneOffset = CurrentTimeZone.GetUtcOffset(new DateTime(time * _timeAccuracy)).Ticks / 10000;
time -= timeZoneOffset;
}

Expand Down

0 comments on commit 3133a17

Please sign in to comment.