I've an API that returns birthdate as 2020-11-24T00:00:00 to a React app. That react app uses that portion to display it:
new Date("2020-11-24T00:00:00").toLocaleDateString();
The issue is that on my browser and all browsers I have seems to give no issue: date is correclty shown. One or two customer complains about ir because they see the date 23/11/2020 (one dat before). I cannot reproduce the bug.
As I understood, Dates can be interpreted by browser as Zulu date so browser can translate the date from GMT+0 to browser's region. Right. Now I have to try to reproduce the bug in order to fix it and And I simply cannot because of misunderstanding.
First postulate: Date("2020-11-24T00:00:00") is going wrong, let's try that: fiddle => no, I cannot reproduce with my browser
Second postulate: .toLocaleDateString() is going wrong, let's try that: [fiddle][2] => no, I cannot reproduce with my browser when changing Location in Chrome.
How can I reproduce the issue in order to fix it?
The current ECMAScript standard obliges your example date string to be interpreted as a local date/time, because the time part is included and the timezone part is not.
Through time the rules for parsing strings have become a bit more standardized, but older JavaScript engines may behave differently. See for instance a post from 2013 or 2017 where a difference was reported (at that time). It is likely that some of your users run that JavaScript on much older engines which interpret this type of date string as UTC (as if suffixed by "Z").
Mozilla Contributors write about using Date.parse (which uses the same parser as when the string is passed to the constructor):
Note: Parsing of strings with Date.parse is strongly discouraged due to browser differences and inconsistencies.
To remove all ambiguity also for older browsers, parse the string yourself:
var dateStr = "2020-11-24T00:00:00";
var dateArr = dateStr.match(/\d+/g);
var date = new Date(dateArr[0], dateArr[1]-1, dateArr[2]); // Add time parts if needed
// Guaranteed to be reporting on the 24th of November 2020 (in locale format)
console.log(date.toLocaleDateString());
Related
I am working on a project which need to deal with a lot of "dates". I notice that sometimes javascript behaves differently in different browsers:
code:
new Date("Mar 30, 2017".replace(',', '').replace(/ /g, '/'))
I know I don't need to use replace to create date, but this code gives me interesting different result from Chrome and Safari.
While I run:
"Mar 30, 2017".replace(',', '').replace(/ /g, '/')
Both Chrome and Safari will gives me: "Mar/30/2017". But when I try to turn the result into a Date object, the interesting result will be:
Chrome: Thu Mar 30 2017 00:00:00 GMT-0700 (PDT)
Firefox: Date 2017-03-30T07:00:00.000Z
Safari: Invalid Date
I have already checked Javascript version of both browsers, and they are using the same version (1.7). Can anyone explain why Chrome behaves differently than Safari in this regard?
UPDATE
I know "Mar/30/2017" is not a valid Date format. But my question is why javascript behaves differently in different browsers. For those answers complaining about my Date format. Please read the question before answer it. I will take #Felix Kling 's answer, and thanks for all answers all the same.
Can anyone explain why Chrome behaves differently than Safari in this regard?
The specification says that handling unknown date formats is implementation dependent:
[...] The String may be interpreted as a local time, a UTC time, or a time in some other time zone, depending on the contents of the String. The function first attempts to parse the format of the String according to the rules (including extended years) called out in Date Time String Format (20.3.1.16). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats. [...]
Safari and Chrome use different JavaScript engines and therefore their behavior can (and do) differ in this case.
I've started using momentJs in an Angular/Typescript project. (Included incase it's relevant in any way although I very much doubt it)
In the run method of my module I call
moment.locale(window.navigator.language);
which correctly sets the locale to en-GB in my instance. Further down the line I use moment to parse a GB time.
when doing the following:
var mom = moment("24/11/2015 00:00:00");
for example. This populates a new moment object using the defaults set on the moment global (If i understand how it should work correctly). moms date is set to 2016-12-11T00:00:00.000Z. This clearly means it's parsed the given string in en-US instead of en-GB which was set via Locale in a default setting prior to this call. Is there anything I've missed in configuration/setup of moment which would make this not work?
I've also inspected the _locale property of my variable. mom._locale is set to en-gb and I can see the L,LL,LLL etc etc formats are all en-GB formatted values (as they should be).
running mom.toDate(); unsurprizingly returns the 2016 date stored internally by the moment object.
Some misc information I forgot to include:
I am using the latest release of momentjs from NuGet (Version 2.10.6 at time of writing) and I've included moment-with-locales.js in my HTML
Using any recent version of MomentJS, you should see why in the console:
Deprecation warning: moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.
Unless you specify a format string, MomentJS relies on the Date object's parsing, and unfortunately, regardless of locale the Date object will, with a string using /, assume U.S. format. One of the many, many things that aren't quite right with Date.
You'll need to use a format string, or supply the string in the simplified ISO-8601 format used by Date. From Parse > String:
When creating a moment from a string, we first check if the string matches known ISO 8601 formats, then fall back to new Date(string) if a known format is not found.
var day = moment("1995-12-25");
Warning: Browser support for parsing strings is inconsistent. Because there is no specification on which formats should be supported, what works in some browsers will not work in other browsers.
For consistent results parsing anything other than ISO 8601 strings, you should use String + Format.
So I got around this by fetching the locale data from moment and just passing it into the format parameter. Considering the example input of "24/11/2015 00:00:00" I would structure my format as below:
var format = moment.localeData().longDateFormat('L') + " " + moment.localeData().longDateFormat("LTS");
this generates the format mask of "DD/MM/YYYY HH:mm:ss".
You can mix and match whatever formats you want and this will be locale specific to whatever you set moment.locale("") to be (presuming you have the locale information setup in moment already)
This is a crazy workaround and I'm surprised that moment doesn't presume locale information as default when parsing. TJCrowder has raised an issue on Github with the moment guys which I suggest anyone who cares should comment on. https://github.com/moment/moment/issues/2770
You're probably better off passing the format to moment directly and validating the string before hand. This will ultimately reduce the amount of debugging you'll need to do and get you up and running straight away.
var mom = moment("24/11/2015 00:00:00", "DD/MM/YYYY HH:mm:ss");
You could try the new(ish) Intl API but browser support is limited (IE11+), so I would recommend having a user select the month in a dropdown or something to force them to input a certain way.
Why do IE/FF and Chrome javascript engines differ on how to interpret this Date format (YYYY-MM-DDTHH:mm:ss.fff) without the timezone designator?
new Date("2015-02-18T15:43:57.803").getUTCHours()
UTC Hours
Chrome: 15
IE11/FF: 21
I don't understand this - is it because Chrome assumes it's local whereas IE/FF assume it's UTC? This seems like a Chrome bug.
Interestingly - appending a "Z" to the end of the string tells both Chrome and IE/FF that the time is UTC and they can agree. Has anyone else noticed this javascript implementation discrepancy with Date?
new Date("2015-02-18T15:43:57.803Z").getUTCHours()
UTC Hours
Chrome: 15
IE11/FF: 15
Ultimately - this is the result of the out-of-box serializer for ASP.NET Web API, which I thought used JSON.NET, but now appears to be internal where JSON.NET uses IsoDateTimeConverter.
Checking GlobalConfiguration.Configuration.Formatters.JsonFormatter tells me we're using JsonMediaTypeFormatter. Is Web API not using JSON.NET serializer out of the box?
This is a boon for Web API people - at least back in ASP.NET MVC we had a consistent date format (albeit proprietary - /Date(number of ticks)/) via the JavascriptSerializer
ES5 says that ISO 8601 format dates without a time zone should be treated as local(that interpretation has since been revised), but the ed. 6 draft says to treat them as UTC. Some script engines have implemented ed. 6, some ES5 and the rest neither.
The ed. 6 (and later) aren't consistent with the ISO 8601 specification.
The bottom line is don't use Date.parse (or pass strings to the Date constructor), manually parse date strings.
For us, the crux of this issue is that DateTimeStyles.RoundtripKind only works if your DateTime properties set the DateTime.DateTimeKind (other than DateTimeKind.Unspecified - the default) or better yet - using DateTimeOffset which enforces use of the TimeZone specificity.
Since we already had DateTime class properties, we worked around this for now by assigning the JsonSerializerSettings.DateTimeZoneHandling from DateTimeZoneHandling.RoundtripKind (DateTime default) to DateTimeZoneHandling.Utc in our Global.asax.cs. This change essentially appends the "Z" to the end of the DateTime - however there is one more step to convert the Local time to UTC.
The second step is to provide the offset by assigning IsoDateTimeConverter.DateTimeStyles which JSON.NET JsonSerializer will pickup from a SerializerSettings.Converters and automatically convert from Local time to UTC - much like the out-of-the-box ASP.NET MVC does.
Obviously - there are other options, but this is solution worked for us.
Global.asax.cs
protected void Application_Start() {
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AdjustToUniversal });
}
The reason this works is because RoundtripKind honors DateTime's DateTimeKind - which is Unspecified by default. We want to explicitly convert this to UTC - which JavaScriptSerializer used to do for us out of the box for ASP.NET MVC. The regional offset is provided by DateTimeStyles.AdjustToUniversal which converts your Local DateTime to UTC.
I have recently discovered that there is a new extension to JavaScript. This adds several features to the Date object in the toLocaleString, toLocaleDateString and toLocaleTimeString functions. Reference here.
I am particularly interested in the timeZone option, that supports IANA/Olson time zones, such as America/New_York or Europe/London. This is currently only supported in Google Chrome.
Previous advice was that to work in JavaScript with any other time zone than UTC or your own local time zone, one had to use a library. But now, it appears that this is starting to be incorporated directly into the browser. So now you can do this:
new Date().toLocaleString("en-US", {timeZone: "America/New_York"})
// output: "7/4/2013 5:15:45 PM"
Or:
new Date().toLocaleString("en-NZ", {timeZone: "Pacific/Chatham",
timeZoneName: "long"})
// output: "7/5/2013 9:59:52 AM GMT+12:45"
Or:
new Date().toLocaleString("en-GB", {timeZone: "Europe/London",
timeZoneName: "short"})
// output: "4/7/2013 22:18:57 United Kingdom Time"
// (strange time zone name, but ok)
This is very cool, but I have a few questions:
Is this part of a new standard? Perhaps buried somewhere in ECMAScript 6? Or is it just something custom to Chrome?
Why just Google Chrome? Is it supported anywhere else? Are there plans to supported it anywhere else?
I checked node.js, which uses Chrome's JavaScript runtime, but it doesn't work there. Why not?
Is the time zone data accessible in any other way than the functions I listed? If only available when formatting strings, then doing any calculations based on the results may be difficult.
This is focused on output, but how would I use it for input? Is there a way to pass the time zone in the constructor to the Date object? I tried the following:
// parsing it with a date and time
new Date("2013-01-01 12:34:56 America/New_York")
// passing it as a named option
new Date(2013,0,1,12,34,56,{timeZone:"America/New_York"})
Neither worked. I couldn't find anything in the specs, so I don't think this exists (yet), but please tell me if I am wrong.
The issue described in this post, created by a flaw in the ECMAScript 5 spec, still affects the output, even when the correct data is in the TZDB. How is it that both old and new implementations are coexisting? One would think it would be all the old way, or all the new way. For example, with my computer's time zone set to US Eastern Time:
new Date(2004,10,7,0,0).toLocaleString("en-US",{timeZone:"America/New_York"})
returns "11/6/2004 11:00:00 PM". It should return midnight, since I started at midnight and my local time zone matches the output time zone. But it places the provided input date at the wrong UTC point due to the ES5 issue.
Can I expect that as IANA releases updates to the TZDB that Google will push Chrome updates that contain the changes?
update
There is pretty extensive write-up about the API here
Is this part of a new standard? Perhaps buried somewhere in ECMAScript
6? Or is it just something custom to Chrome?
Yes, these are part of the ECMAScript Internationalization API. It is implemented separate from ECMAScript, but the requirement of implementing ECMAScript Internationalization API is to first have correct implementation of ECMAScript 5.1
Why just Google Chrome? Is it supported anywhere else? Are there plans
to supported it anywhere else?
For the recent years, Google Chrome has mostly been first to implement new features. Mozilla is more conservative, still for example discussing whether to implement the download attribute of a elements. It is now available in IE11 Beta and Opera too. It will be available in Firefox 25.
I checked node.js, which uses Chrome's JavaScript runtime, but it
doesn't work there. Why not?
node.js just uses the same engine, which is a separate project from the Google Chrome browser. The engine just implements Ecmascript 5.1. This is an extension node.js would have to implement separately right now. It will become available in V8 in Q3 so probably a bit after that you can use it in node.js.
This is focused on output, but how would I use it for input? Is there
a way to pass the time zone in the constructor to the Date object? I
tried the following:
There is nothing about inputting dates in the specification. I personally cannot see how this would be useful, you are doing it wrong if you are not transmitting UTC timestamps because something like "2013-01-01 12:34:56 America/New_York" is ambiguous during transitions from DST to standard time.
The issue described in this post, created by a flaw in the ECMAScript
5 spec, still affects the output, even when the correct data is in the
TZDB.
This is input issue, not output. Again, constructing a date with local timezone that you cannot influence or detect is doing it wrong. Use the timestamp constructor overload or Date.UTC.
Can I expect that as IANA releases updates to the TZDB that Google
will push Chrome updates that contain the changes?
Nothing in the spec but I think it will be reasonable to expect that the rules are not too much behind.
Is this part of a new standard? Perhaps buried somewhere in ECMAScript
6? Or is it just something custom to Chrome?
Indeed it is part of the new ECMA-402 standard. The standard is very difficult to read, but there is this friendly introduction.
Why just Google Chrome? Is it supported anywhere else? Are there plans
to supported it anywhere else?
MDN has a list of supporting browsers. According to Bug 853301 it will be available in Firefox 25.
I checked node.js, which uses Chrome's JavaScript runtime, but it doesn't work there. Why not?
Possible reasons are many; it is not up to the current code base, or it would make the node.js bigger and slower (the previous bug tracker entry from Mozilla indicates that the timezone data increased the download size of Firefox by 10 %, and caused the I/O to increase substantially during browser start up.
Is the time zone data accessible in any other way than the functions I listed? If only
available when formatting strings, then doing any calculations based on the results may be
difficult.
Seems that it is not available. Furthermore, the Intl API primer talks that only UTC and local time zone are absolutely required to be supported.
This is focused on output, but how would I use it for input? Is there a way to pass
the time zone in the constructor to the Date object? I tried the following:
The Intl API only speaks about date/time formatting, string collations and number formatting. The datetime formatting not only supports the Gregorian calendar but also many kinds of other calendars, lunar, lunisolar and so forth.
The issue described in this post, created by a flaw in the ECMAScript 5 spec, still affects
the output, even when the correct data is in the TZDB. How is it that both old and new
implementations are coexisting? One would think it would be all the old way, or all the new
way. For example, with my computer's time zone set to US Eastern Time:
new Date(2004,10,7,0,0).toLocaleString("en-US",{timeZone:"America/New_York"})
returns "11/6/2004 11:00:00 PM". It should return midnight, since I started at midnight and > my local time zone matches the output time zone. But it places the provided input date at the > wrong UTC point due to the ES5 issue.
The reason for that is that ES5 mandates that the input to new Date be calculated using the current DST and offset, that is it is America/New York but with EDT timezone, even though Nov 6 is not in EDT. Obviously as this is so specified, then it cannot be changed. However, as Chrome is using the TZDB to do the conversion from the bare UTC point-in-time value to the America/New York tz, it then does consider the time as being in EST.
Can I expect that as IANA releases updates to the TZDB that Google will push Chrome updates
that contain the changes?
I'd believe so
Firstly, when I say other browsers I really only mean Firefox because that's all I tested in.
Internet Explorer can parse a date followed by a single character as a proper date. Whereas Firefox behaves as I'd expect.
For example...
var dateString = new Date("1/1/2010f");
alert(dateString);
In IE it will alert...
Thu Dec 31 21:00:00 UTC-0900 2009
Whereas in FF is will spit out...
"Invalid Date"
I first noticed this using the jquery validation plug in.
http://docs.jquery.com/Plugins/Validation/Methods/date
It seems like it just subtracts some amount of hours off the actual date in IE when a character is appended. I've tested in IE6 and IE8.
Am I missing something?
It is implementation dependent.
When Date as a "constructor" is passed a string object, it tries to build a date with its parse method (Date.parse).
That method shall parse strings returned by Date.toString() and Date.toUTCString(), which both return formats varying with the implementation. And this is how the ECMAScript standard says it should be.
I remember reading this in "the Rhino book" (Tommy Flanagan's great javascript book).
In practice, it might very well turn out everyone except IE does it the same way, but I haven't tested thoroughly.
Also, it is a bit much if IE tries to parse whatever one throws at it and produces a Date object out of ascii soup.
Internet Explorer uses a different parsing mechanism, which may or may not be documented, and may be very broken in some or many cases.