-
Notifications
You must be signed in to change notification settings - Fork 153
Strange behavoir when EnableDateTimeConversion is activ #652
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
Comments
I did some deeper research and found out that it happens in all the years before 1980 and in that year in germany the summertime was introduced. Here is my extended sample code: static void Main(string[] args)
{
//Promise Task conversion
using (V8ScriptEngine engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDateTimeConversion))
{
engine.AddHostType("TestClass", typeof(TestClass));
object result = engine.Evaluate(new DocumentInfo() { Category = ModuleCategory.Standard },
"""
TestClass.Output("TestStart");
const date1 = TestClass.GetDate(1);
TestClass.Output(format(date1));
const date2 = TestClass.GetDate(2);
TestClass.Output(format(date2));
TestClass.Output(format2(date2));
const date3 = TestClass.GetDate(3);
TestClass.Output(format(date3));
TestClass.Output(format2(date3));
const date4 = TestClass.GetDate(4);
TestClass.Output(format(date4));
TestClass.Output(format2(date4));
TestClass.Output("TestEnd");
for(let i = 1965; i <= 2026; i++) {
const dateyear = TestClass.GetDateByYear(i);
TestClass.Output(format(dateyear));
TestClass.Output(format2(dateyear));
}
function format(date) {
return JSON.stringify(date);
}
function format2(date){
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} (${date.getTimezoneOffset()})`
}
"""
);
}
}
}
public static class TestClass
{
public static void Output(string message)
{
Console.WriteLine(message);
}
public static DateTime GetDate(int number)
{
DateTime dateTime;
switch(number)
{
case 1:
dateTime = DateTime.Now;
break;
case 2:
dateTime = DateTime.UtcNow;
break;
case 3:
dateTime = new DateTime(1972, 11, 23, 0, 0 , 0, DateTimeKind.Local);
break;
case 4:
dateTime = new DateTime(1965, 09, 09, 00, 0, 0, DateTimeKind.Local);
break;
default:
dateTime = DateTime.MinValue;
break;
}
return dateTime;
}
public static DateTime GetDateByYear(int year)
{
return new DateTime(year, 09, 09, 0, 0, 0, DateTimeKind.Local);
}
}
``` |
Hi @DavidBal,
OK, so... Is the behavior still unexpected? 😁 Thanks! |
I would say yes! EDIT: |
Understood. Would you mind providing a minimal example with just one date-time value that demonstrates the problem? Sorry, we aren't clear on the issue, and the code produces different output here, presumably due to time zone differences. It might also be helpful to know your exact time zone. Thanks! |
Of course: My timezone is internal class Program
{
static void Main(string[] args)
{
//Promise Task conversion
using (V8ScriptEngine engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDateTimeConversion))
{
engine.AddHostType("TestClass", typeof(TestClass));
//(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien
TimeZoneInfo timeZone = TimeZoneInfo.Local;
TimeZoneInfo germanTimeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
object result = engine.Evaluate(new DocumentInfo() { Category = ModuleCategory.Standard },
"""
const date1 = TestClass.GetDate();
TestClass.Output(format(date1));
TestClass.Output(format2(date1));
function format(date) {
return JSON.stringify(date);
}
function format2(date){
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} (${date.getTimezoneOffset()})`
}
"""
);
}
}
}
public static class TestClass
{
public static void Output(string message)
{
Console.WriteLine(message);
}
public static object GetDate()
{
return new DateTime(1965, 09, 09, 00, 0, 0, DateTimeKind.Local);
}
} Result:
Expected:
I hope this helps. EDIT: It should be:
|
Hi @DavidBal, If we understand correctly, you expect If that's correct, your expectation doesn't match .NET's var time = new DateTime(1965, 9, 9, 0, 0, 0, DateTimeKind.Unspecified);
var zone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
var utc = TimeZoneInfo.ConvertTimeToUtc(time, zone);
Console.WriteLine(utc.ToString("O")); This code produces the following output:
Any idea why that's not in agreement with your expectation? Thanks! |
Now my brain is totally lost... 😄 const date = new Date(1965, 9, 9, 0, 0, 0);
console.log(date, date.getTimezoneOffset()); result:
Ok then the problem is between C# and js if i understand it correclty. C# has a time offset 120 I will do deeper research here...I have already found a workaround, by disabling the auto conversation and providing a conversation function: internal class Program
{
static void Main(string[] args)
{
//Promise Task conversion
using (V8ScriptEngine engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDateTimeConversion))
{
engine.AddHostType("TestClass", typeof(TestClass));
//(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien
TimeZoneInfo timeZone = TimeZoneInfo.Local;
TimeZoneInfo germanTimeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
object result = engine.Evaluate(new DocumentInfo() { Category = ModuleCategory.Standard },
"""
const date1 = TestClass.GetDate();
TestClass.Output(format(date1));
TestClass.Output(format2(date1));
function format(date) {
return JSON.stringify(date);
}
function format2(date){
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} (${date.getTimezoneOffset()})`
}
"""
);
}
using (V8ScriptEngine engine = new V8ScriptEngine())
{
engine.AddHostType("TestClass", typeof(TestClass));
//(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien
TimeZoneInfo timeZone = TimeZoneInfo.Local;
TimeZoneInfo germanTimeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
object result = engine.Evaluate(new DocumentInfo() { Category = ModuleCategory.Standard },
"""
const date1 = TestClass.GetDateFixed();
TestClass.Output(format(date1));
TestClass.Output(format2(date1));
function format(date) {
return JSON.stringify(date);
}
function format2(date){
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} (${date.getTimezoneOffset()})`
}
"""
);
}
}
}
public static class TestClass
{
public static void Output(string message)
{
Console.WriteLine(message);
}
public static DateTime GetDate()
{
return new DateTime(1965, 09, 09, 00, 0, 0, DateTimeKind.Local);
}
public static object GetDateFixed()
{
DateTime date = GetDate();
return V8ScriptEngine.Current.Evaluate(
$"""
new Date({date.Year}, {date.Month - 1}, {date.Day}, {date.Hour}, {date.Minute}, {date.Second}, {date.Microsecond});
"""
);
}
} result:
As always thanks for your input, i still think there is a problem somewhere but i dont can catch it at moment. Kind regards, EDIT: console.log(Intl.DateTimeFormat().resolvedOptions().timeZone) //Result for me: "Europe/Berlin" |
Hi @ClearScriptLib, the problem is caused by the diffrenz in the offset in c# (120) and js (60). That comes from diffrent timezone modells used in c# and js. Do you see any way that could be corrected by clearscript? Kind regards, |
Hi David,
According to our (very crude) research, the JavaScript result is the correct one for October 9, 1965. Daylight Saving Time had been discontinued on October 3 of that year, but .NET doesn't seem to account for that, at least on Windows. DST was reintroduced in 1980, so JavaScript and .NET presumably agree for all dates after that, at least.
As you can see, date-time conversion is a very tricky subject. Apparently, in Berlin, DST was abolished in 1949, then briefly reintroduced in the early 1960s, then abandoned again in 1965, to be adopted again in 1980. And that's just one time zone. Dealing with all that is a job for subject matter experts, and that's not us 😁. One thing we could probably do is allow hosts to override ClearScript's Cheers! EDIT: As we suspected, .NET produces the correct result on Linux. |
That matches with my research.
That could help. Because then i can implement a workaround for this, with out searching for every place a DateTime is moved between c# and js.
As always thank's for your support. Kind regards, |
Hey @ClearScriptLib, @DavidBal made me aware of this issue, since we are using a component of his which uses ClearScript, so we are indirectly affected as well. First of all, thank you for your research and help confirming the conversion bug. A method to easily override the automatic conversion would be very much appreciated, as it is certainly better than not being able to do anything at all. :) However, I would guess most ClearScript users are also not subject matter experts on this topic. Leaving the conversions to "us" might therefor produce more / different bugs down the line. Thanks! |
Hi @DonatJR,
Absolutely. The idea here is that hosts would delegate the calculation to another date-time conversion library, perhaps something like Noda Time.
Since the Linux version of .NET handles this correctly, it's most likely a Windows issue and probably not addressable due to long-term compatibility concerns. Nevertheless, we've posted a .NET runtime issue here. By the way, it isn't the most efficient solution in the world, but with ClearScript it's possible to use V8 itself as a date-time conversion library. Let us know if you'd like an example. Cheers! |
Hey, thanks for creating the issue on the dotnet repo and for keeping us updated! 👍🏼 :)
I am not sure if this is applicable to our goal. We want to pass Cheers! |
Hi @DonatJR,
Here's an example that patches .NET's First, you'll need the Harmony NuGet package. Next, add this class somewhere near your application's startup code: // using HarmonyLib;
internal static class DateTimePatcher {
private static readonly V8ScriptEngine _engine = new();
private static readonly ScriptObject _toUnixMs = (ScriptObject)_engine.Evaluate(@"(
function (y, mo, d, h, mi, s, ms) {
return new Date(y, mo - 1, d, h, mi, s, ms).valueOf();
}
)");
private static readonly ScriptObject _toLocal = (ScriptObject)_engine.Evaluate(@"(
function (ms) {
var d = new Date(ms);
return new Int32Array([
d.getFullYear(), d.getMonth(), d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()
]);
}
)");
private static bool ToUniversalTime(DateTime __instance, out DateTime __result) {
if (__instance.Kind == DateTimeKind.Utc) {
__result = __instance;
}
else {
var dt = DateTime.SpecifyKind(__instance, DateTimeKind.Local);
var ms = Convert.ToInt64(_toUnixMs.InvokeAsFunction(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond));
__result = DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime;
}
return false;
}
private static bool ToLocalTime(DateTime __instance, out DateTime __result) {
if (__instance.Kind == DateTimeKind.Local) {
__result = __instance;
}
else {
var ms = new DateTimeOffset(DateTime.SpecifyKind(__instance, DateTimeKind.Utc)).ToUnixTimeMilliseconds();
var arr = new int[7];
((ITypedArray<int>)_toLocal.InvokeAsFunction(ms)).Read(0, 7, arr, 0);
__result = new DateTime(arr[0], arr[1] + 1, arr[2], arr[3], arr[4], arr[5], arr[6], DateTimeKind.Local);
}
return false;
}
public static void Patch() {
var harmony = new Harmony(typeof(DateTimePatcher).FullName);
harmony.Patch(
typeof(DateTime).GetMethod(nameof(DateTime.ToUniversalTime)),
typeof(DateTimePatcher).GetMethod(nameof(ToUniversalTime), BindingFlags.NonPublic | BindingFlags.Static)
);
harmony.Patch(
typeof(DateTime).GetMethod(nameof(DateTime.ToLocalTime)),
typeof(DateTimePatcher).GetMethod(nameof(ToLocalTime), BindingFlags.NonPublic | BindingFlags.Static)
);
}
} Then call the Here's a simple application that demonstrates the patch: static class Program {
static Program() {
DateTimePatcher.Patch();
}
static void Main() {
var localTime = new DateTime(1965, 10, 9, 0, 0, 0, 0, DateTimeKind.Local);
var utc = localTime.ToUniversalTime();
Console.WriteLine(utc.ToString("O"));
Console.WriteLine(utc.ToLocalTime());
}
} Note that Good luck! |
Hi,
i have the following sample code:
The result is:
For me the last value looks wrong, the expected value would be:
EDIT:
In the last output pair the UTC value is "1965-10-08T 22 :00:00.000Z" but expected would be "1965-10-08T 23 :00:00.000Z".
Kind regards,
David
The text was updated successfully, but these errors were encountered: