Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DST implementation in JavaScript causing issues when sending to a MVC controller

My client, who is in the insurance field, requires the date of birth of a potential insured. It is entered into a web form using a jQuery datepicker, connected to a model property using Knockout and sent via ajax in JSON to a MVC 4 controller.

Some policyholders have received their insurance documentation with the wrong date of birth. During the ensuing investigating, we found that in addition to data entry errors, which are extremely marginal, wrong dates are concentrated in the following two periods:

  • the last three weeks of March to early April
  • the last week of October to the first week of November.

As our client is in the America/Montreal timezone, I immediately thought of a problem of DST. The DST rules changed in 2007 in this timezone.

Reading several articles and other Stack Overflow questions, I learned that the ECMAScript 5 standard specifies that the implementation must take into account the current DST rules, and need not respect the DST history of changes. ES5 15.9.1.8 The only browser not complying with this specification at the moment is IE10 (more on this later).

Given this specification, browsers will report the dates as follows (tested in Chrome) :

(new Date(2006, 9, 31, 0, 0, 0)).toISOString()
// 2006-10-31T04:00:00.000Z

(new Date(2008, 9, 31, 0, 0, 0)).toISOString()
// 2008-10-31T04:00:00.000Z

While the .NET platform reports dates correctly as follows:

DateTime dt = new DateTime(2006, 10, 31, 0, 0, 0);
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt);
// 10-31-06 0:00:00 -05:00
dt = new DateTime(2008, 10, 31, 0, 0, 0);
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt);
// 10-31-08 0:00:00 -04:00

One cause of this problem is that if an insured person is born in the last week of October before 2007, the UTC time will be reported to my MVC controller incorrectly, for example:

Javascript date object :

new DateTime(2006, 9, 31, 0, 0, 0);

.Net parses JSON data and gets :

10-30-06 23:00:00 GMT

During tests, I found that the internal representation of a Date object in JavaScript is not compatible with that of a .Net DateTime, and I could not roll my own parser using the JavaScript representation:

Javascript :

(new Date(2006, 9, 31, 0, 0, 0)).getTime()
// 1162267200000

.Net :

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(1162267200000);
Console.WriteLine("{0}", dt);
// 2006-10-31 04:00:00

(This is wrong, because it's represented as October 30th 2006 at 23:00 in local time.)

For my client’s use case, since it's the date part that interests me, I might just set the time part of the Date object to noon, which would shield me from all cases of date misinterpretation on JavaScript’s part. Unfortunately this is a Ugly Patch, and it does not solve other potential situations, such as if my client wanted us to implement a functionality asking us to have the precise date and time of an event which happened in the past.

Another approach would be to write an algorithm to detect the potentially problematic DST weeks in my controller and set the correct time if I get a date during the weeks in question. Unfortunately, given that the ECMAScript 6 standard specifies that the DST change history must be respected (so all future browsers should handle that situation correctly), and given that IE10 is already working properly, I'm afraid to create a problematic situation in the future. The approach also seems wrong, architecturally speaking.

Another aspect to consider is that if I have to pass the model from client to server and then back to the client, I will need to have a way to recreate the "bad" JavaScript Date object to be sure not to affect the display on the client-side of the application.

No solution seems correct and extensible. I cannot believe that nobody has ever had to deal with this problem before.

How can I fix this permanently, and in a way that could be applied to all other JavaScript / MVC projects?

EDIT #1 - Additionnal information not directly related to the problem :

As @matt-johnson pointed out, there's rarely a case where the timezone information should be used. In that instance, however, the insured birthdate is relative to the timezone my client is in. Let's take for example a 17-years-old living in Victoria-BC with his birthday being December the 2nd. If he files an insurance quote on December the 1st at 22:00, even though he's still 17, the insurance quote will be accepted, because he's already 18 in my client's timezone. The same rule applies for the insurance pricing. That's a legal requirement.

Just to be clear, I'm looking for an all-around solution, which could be applied to any other projects. The specific problem is : Javascript, for just a few specific weeks for years prior to 2007, reports the time with a 1-hour offset. This offset is always there no matter how I represent time (local timezone or UTC), because it's the underlying data (not the data representation) that is wrong.

I used the "set the time part at noon" workaround to circumvent the bug, but the underlying problem still remains. What would happen if my client ask us to develop a web app requiring that we get the date AND time of a specific event in the past? For example : "Please state the exact date and time the accident happened : October 31st 2005 at 19:32". The MVC controller would set the time at 18:32.

like image 713
MartinVeronneau Avatar asked Nov 01 '13 18:11

MartinVeronneau


1 Answers

You're ignoring part of .Net's DateTime structure. Specifically, you're not considering that the .Kind property of any DateTime is one of three possible DateTimeKind values. More on MSDN.

If you are working with UTC values, then you need to set DateTimeKind.Utc. Since JavaScript's numeric values are UTC based, then you should use this for the conversion:

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                      .AddMilliseconds(1162267200000);

Also, you're doing something a bit strange which is using the zzz formatter on a DateTime that has DateTimeKind.Unspecified. In this scenario, there really isn't any time zone associated with the value, but .Net will assume that you wanted it to behave as if it were DateTimeKind.Local, since the zzz formatter doesn't make sense otherwise.

You really need to be careful to not use DateTimeKind.Local values (such as DateTime.Now) or anything where unspecified kinds might be misinterpreted as local (such as zzz or .ToUniversalTime()). "Local" in this context will be local to your server, which is rarely relevant in a web application. Read more here.

Regarding JavaScript's DST implementation, I have made similar observations regarding the ECMAScript specifications. You can read more on that here.

Now with all of that said, you hit on a very specific point, which is that neither JavaScript or .Net offer a type that represents a date without a time. Both take the approach of setting the time to midnight. If you are not careful, you could run into issues with local dates that do not have a midnight, such as October 20th, 2013 in Brazil. If you are restricted to the USA, then you might not have that particular issue, but it is still relevant from a design perspective.

The best approach would be to use a true "date without time" type. In .Net, you can find this as LocalDate in the Noda Time library. As an ISO8601 string, it would look like "2013-10-31", without any time or time zone.

If you don't want to use Noda Time (although I highly recommend it), you can still use a DateTime type in .Net, just be very careful to never use the time portion for anything. It should have DateTimeKind.Unspecified.

Unfortunately, there isn't currently a native type in JavaScript for a date without a time. (The JavaScript Date class should probably have been named DateTime). So you'll need to build a date-only string yourself. You could assemble it from a Date manually, such as:

var s = dt.getFullYear() + "-" + 
  (dt.getMonth() < 10 ? "0" : "") + dt.getMonth() + "-" + 
  (dt.getDate() < 10 ? "0" : "") + dt.getDate();

But the easier way is to use the moment.js library:

var s = moment(dt).format("YYYY-MM-DD");

I'm just offering my opinion as to the "best approach". But if you just wanted to know how to prevent the failure with what you are currently doing, then you should just be careful not to convert to UTC in your JavaScript response. You're doing that with .toISOString(). Again, you could assemble the full date and time manually, or you could use moment to format a response that is in terms of the local time.

If you really think about it, a Birthdate doesn't really have a time or time zone associated with it. Most people don't track the hour/minute/second of their birth (or time zone of their birth location) when they're figuring out how old they are. You might do this for astrology, but for day-to-day business usage you would just use the start of day at the local time zone where you are evaluating the age. So the date is really the only important piece of data.

like image 71
Matt Johnson-Pint Avatar answered Nov 04 '22 23:11

Matt Johnson-Pint