Search code examples
javascriptc#.netdatedst

Difference between JS and .NET daylight saving behavior


I am trying to get C# and JS generate the same results when adding minutes on JS Date and .NET DateTime objects. My system is using the Greek culture settings, but most likely you will get similar results on your machines (if you play around with the values). I have this JS code using moment.js:

var dt1 = new Date(2020, 0, 19, 0, 0, 0);
var m1 = moment(dt1);
[m1.toString(), m1.add(100980, 'minutes').toString()]

>  ["Sun Jan 19 2020 00:00:00 GMT+0200", "Sun Mar 29 2020 04:00:00 GMT+0300"]

..and this C# code in LinqPad:

var ci = CultureInfo.CreateSpecificCulture("el-GR"); //CultureInfo.InvariantCulture; //CultureInfo.CurrentCulture;
var dt = new DateTime(2020, 1, 19, 0, 0, 0, 0, ci.Calendar, DateTimeKind.Local);
dt.ToString("G", ci).Dump();
var dt2 = dt.AddMinutes(100980);
dt2.ToString("G", ci).Dump();

> 19/1/2020 12:00:00 πμ
> 29/3/2020 3:00:00 πμ

...and when printing with invariant culture:

var ci = CultureInfo.CreateSpecificCulture("el-GR"); //CultureInfo.InvariantCulture; //CultureInfo.CurrentCulture;
var dt = new DateTime(2020, 1, 19, 0, 0, 0, 0, ci.Calendar, DateTimeKind.Local);
dt.ToString("G", CultureInfo.InvariantCulture).Dump();
var dt2 = dt.AddMinutes(100980);
dt2.ToString("G", CultureInfo.InvariantCulture).Dump();

> 01/19/2020 00:00:00
> 03/29/2020 03:00:00

Can someone tell why I get different results? The system settings are the same and I have tried using both CurrentCulture and CreateSpecificCulture("el-GR") without getting any similarity to the JS results.

I have even created my own addMinutes() in JS, but I still have the same issue.

It seems that JS does a daylight saving switch exactly at 100980 minutes after the start date (if you add 100979 minutes you will see that), but C# does not! According to Wikipedia, the JS behavior is correct behavior. But I was pretty sure so far that .NET was correct as well. C# seems to do a switch 70.2 days before, according to a quick test that I have done.

Any ideas what I have to do in order to get the same results between the two languages when adding minutes to dates?

UPDATE:

I have tried NodaTime, and it seems that it agrees with JavaScript:

var dt0 = new DateTime(2020, 1, 19, 0, 0, 0, 0, ci.Calendar, DateTimeKind.Local).AcServerToUtc();
Duration duration = Duration.FromMinutes(100980);
Instant start = Instant.FromDateTimeUtc(dt0.AcMakeKindUtc());
Instant future = start + duration; // Or now.Plus(duration)
var zone =  DateTimeZoneProviders.Tzdb.GetSystemDefault();
future.InZone(zone).LocalDateTime.ToString("G", CultureInfo.InvariantCulture).Dump();

> 03/29/2020 04:00:00

So, that seems a possible solution!


Solution

  • The problem is that the AddMinutes call is being done on a DateTime with DateTimeKind.Local. Despite being marked with this kind, math on a DateTime object doesn't take time zones into account. Even with local time.

    Since you're working with local kind, you can simply convert to UTC, do the math, then convert back.

    var dt2 = dt.ToUniversalTime().AddMinutes(100980).ToLocalTime();
    

    You might also want to look into using the DateTimeOffset type instead, and performing time zone conversions with TimeZoneInfo.ConvertTime.