toTimeString inconsistent time zone name - javascript

I'm noticing the following weird behaviour for the toTimeString method.
const date1 = new Date("2022-09-23T00:00:00.00Z")
const date2 = new Date()
date1.toTimeString()
//17:00:00 GMT-0700 (Pacific Daylight Saving Time)
date2.toTimeString()
// 15:06:43 GMT-0800 (Pacific Standard Time)
I've also noticed that result for date1 even depends on the device region settings (for Mac at least)
date1.toTimeString()
// if region is set to Canada
// 17:00:00 GMT-0700 (Pacific Daylight Saving Time)
// if region is set to United States
// 17:00:00 GMT-0700 (Pacific Daylight Time)
Another observation
date2.toTimeString()
// this is the result for both United States and Canada
// 15:06:43 GMT-0800 (Pacific Standard Time)
A couple of questions
Why does the result depend on how the date is constructed?
(new Date("2022-09-23T00:00:00.00Z") vs new Date)
For new Date("2022-09-23T00:00:00.00Z"), why does the result depend on the device setting?
The result seems consistent when no arguments are passed e.g new Date(). Can we assume that this will produce a consistent timezone name regardless of device settings?
Note: I also noticed a same behaviour with date-fns-tz
EDIT: added one more example above.
Another question:
4.In contrary to question 2, new Date().toTimeString() is not influenced by region setting. Why is that the case
EDIT 2: I figured it out.
For countries with day light savings. The behaviour of toTimeString() will produce different timezone based on when the timestamp falls in.
Leaving this thread here since it might be helpful to others

First case you call the Date constructor with string representation of the date 2022-09-23 with the time 00:00:00 (GMT offset 0) and it returns the Date object instance for the 09/23/2022 00:00:00. Second case you call empty Date constructor which returns the Date object instance with current date and time.
Next, when you call the toTimeString() method it returns the time portion of a Date object interpreted in the local timezone (see more https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString).
If you would like to get the local time zone name better see here How can I get the timezone name in JavaScript?

Why does the result depend on how the date is constructed? (new Date("2022-09-23T00:00:00.00Z") vs new Date)
The timestamp "2022-09-23T00:00:00.00Z" is parsed with zero offset due to the trailing "Z". That effectively makes it UTC.
Calling new Date() creates a Date instance for the current date and time. toTimeString returns the time that a Date instance represents based on system settings. So for a given instance, if you change the location in system settings to a place with a different offset, you'll get a different time.
If regional settings are for a place that observes daylight saving time (DST), then you'll also get a different offset (and civil timezone name) for dates at times of the year when DST is and isn't in effect.
For new Date("2022-09-23T00:00:00.00Z"), why does the result depend on the device setting?
The Date instance created is exactly the same regardless of system settings because the offset is fixed as 0 in the timestamp. The result of toTimeString depends on system settings because that's were it gets its information for the host offset and civil timezone name from.
The result seems consistent when no arguments are passed e.g new Date(). Can we assume that this will produce a consistent timezone name regardless of device settings?
No. Timezone names are not standardised and are implementation dependent per ECMA-262. However, the actual offset should be consistent.
I suggest reading Why does Date.parse give incorrect results? for some background.

Related

Creating Date by iso ''0001-01-01T01:00:00' adding 16 second

new Date("0001-01-01T01:00:00Z") --> Mon Jan 01 0001 02:50:16 GMT+0150 (Moscow Standard Time)
Incorrect GMT: my timezone GMT+3000, but date creates GMT+0150
For dates, you can (and should, in my opinion) define them in UTC ISO 8601 "Z" format ("YYYY-MM-DDTHH:MM:SSZ"), just as you did.
However, to get a user-friendly string representation of those dates, it will depend on your client and on the Javascript engine used. You can constrain the output if you explicitly specify a reference timezone with toLocaleString().
var date = new Date("1990-01-01T01:00:00Z");
console.log(date.toLocaleString("en-US", {timeZone: "Asia/Jerusalem"}));
console.log(date.toLocaleString("en-US", {timeZone: "Europe/Moscow"}));
console.log(date.toLocaleString("en-US", {timeZone: "Africa/Djibouti"}));
// output on my machine, should be the same on yours :
// 1/1/1990, 3:00:00 AM
// 1/1/1990, 4:00:00 AM
// 1/1/1990, 4:00:00 AM
console.log(date.toString());
// output **on my machine**, should **not** be the same on yours
// Mon Jan 01 1990 02:00:00 GMT+0100 (Central European Standard Time)
For the 16 seconds issue, this is linked to the way offset are defined by the rules for those dates before the notion of IANA timezone existed.
They probably don't make sense in your application, and I discourage you to use dates like 1st January of year 0001 for your examples.
Examples :
var date = new Date("0001-01-01T01:00:00Z");
console.log(date.toLocaleString("en-US", {timeZone: "Asia/Jerusalem"}));
console.log(date.toLocaleString("en-US", {timeZone: "Europe/Moscow"}));
console.log(date.toLocaleString("en-US", {timeZone: "Africa/Djibouti"}));
// output on my machine, should be the same on yours :
// 1/1/1, 3:20:54 AM
// 1/1/1, 3:30:17 AM
// 1/1/1, 3:27:16 AM
console.log(date.toString());
// output **on my machine**, should **not** be the same on yours
// Mon Jan 01 0001 01:09:21 GMT+0009 (Central European Standard Time)
More information here (thanks Johan Karlsson for the link) :
https://bugs.chromium.org/p/chromium/issues/detail?id=849404
The most relevant comment from this link is, I think :
This is working as intended and working per spec. The spec says that
we have to follow the IANA timezone database.
In 1880, there's no standard timezone and America/Los_Angeles timezone
offset was based on its longitude. The same is true of other
timezones.
Also note that there are many timezone around the world the zone
offset (and whether or not to have DST or when to start DST) have
changed multiple times even since 2000 (e.g. Europe/Moscow). The
change to make them work correctly also brought in what's reported
here.
Pac0's answer is correct (and you should accept that answer since it came first, not this one). But just to provide a detailed explanation:
Dates from before recorded history of time zones are marked in the time zone database as LMT - which stands for Local Mean Time. The offsets are derived from the latitude and longitude of the city, not by any current political determination.
Since the offset shown is 1:50:16 ahead of UTC, I can derive that your system time zone is Europe/Minsk. This is seen in the tzdb sources here:
# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Europe/Minsk 1:50:16 - LMT 1880
This is just the first line of the zone entry for Europe/Minsk, which says that until 1880, use the LMT entry of UTC+1:50:16.
As to why it says "Moscow Standard Time" - that string comes from the Unicode CLDR data, which in the /common/supplemental/metaZones.xml file we can see:
<timezone type="Europe/Minsk">
<usesMetazone to="1991-03-30 23:00" mzone="Moscow"/>
<usesMetazone to="2011-03-27 00:00" from="1991-03-30 23:00" mzone="Europe_Eastern"/>
<usesMetazone to="2014-10-26 22:00" from="2011-03-27 00:00" mzone="Europe_Further_Eastern"/>
<usesMetazone from="2014-10-26 22:00" mzone="Moscow"/>
</timezone>
So Europe/Minsk uses the Moscow metazone up until 1991. Then, using one of the language files such as /common/main/en.xml for English, we can see the actual text assigned to this metazone:
<metazone type="Moscow">
<long>
<generic>Moscow Time</generic>
<standard>Moscow Standard Time</standard>
<daylight>Moscow Summer Time</daylight>
</long>
</metazone>
And now you have a complete picture for how the string Mon Jan 01 0001 02:50:16 GMT+0150 (Moscow Standard Time) was derived from 0001-01-01T01:00:00Z.
A timezone is an offset plus a date range. To format your date, javascript wants to know what the timezone offset was for Moscow in the year zero. This is hard information to come by, and might not be accurate ! You think you're asking something simple, but it's actually pretty extreme. If you want to use the date object to represent durations, you should take the epoch as your starting point.

Javascript Date, setFullYear changes timezone

Javascript Dates work with timezones. If I create a date, it sets the timezone. When I update the year of that date, I don't expect the timezone to change. It does, however. The worst thing is that it changes the timezone but not the time, causing the actual time to shift by one hour!
This causes the issue that if I have the person's birth date, and want to know his birthday this year, I cannot simply set the year to the current year using birthdate.setFullYear(2018), because it will return the birthday minus one hour. That means that it occurs one day before the actual birthday, at eleven 'o clock.
let test = new Date('1990-10-20');
console.log(test);
console.log(test.toISOString().substring(0, 10));
// 1990-10-20 ( Sat Oct 20 1990 01:00:00 GMT+0100 (Central European Standard Time) )
test.setFullYear(2000);
console.log(test);
console.log(test.toISOString().substring(0, 10));
// 2000-10-19 ( Fri Oct 20 2000 01:00:00 GMT+0200 (Central European Summer Time) === one hour too soon!! )
It might be that your timezone does not reproduce, here is my output:
"1990-10-20T00:00:00.000Z"
1990-10-20
"2000-10-19T23:00:00.000Z"
2000-10-19
The only workaround I found is substring the date and replace it as string values. How can I do it better using the Date object?
Hm, maybe it would be better to use setUTCFullYear(...) (MDN Docs) instead? At least in that case, it won't mess up the time.
let test = new Date('1990-10-20');
console.log(test);
console.log(test.toISOString().substring(0, 10));
// "1990-10-20T00:00:00.000Z")
test.setUTCFullYear(2000);
console.log(test);
console.log(test.toISOString().substring(0, 10));
// "2000-10-20T00:00:00.000Z"
BEWARE THE TIMEZONE If you want to work with just-a-date using the date object (and you more or less have to) your date objects should represent UTC midnight at the start of the date in question. In your case, to indicate a year, use UTC midnight at the start of the 1st January. This is a common and necessary convention, but it requires a lot of attention to make sure that the timezone doesn't creep back in, as you've discovered.
When you say "javascript dates work with timezones", a javascript date is a moment in time (ticks since the epoch) with handy static functions for converting that moment into a meaningful string in the local timezone (or a specified timezone). The date object itself does not have a timezone property.
So, you can create your UTC midnight year with something like...
var myDate = new Date(Date.UTC(2018,0,1)); // months are zero-indexed !
Then serialize it specifying UTC...
myDate.toISOString();
myDate.toLocaleDateString("en",{timezone:"UTC"});

Why is JavaScript guessing two different timezones based on strings that are identical in structure?

I am providing dates two to my script in this format: 2016-05-25 12:30:02. However, JavaScript is setting the each date to a different GMT offset. I formatted the dates to match the suggested answer here and added the function provided, but I got the same results as shown below so I reverted back to my original script shown below.
var response = JSON.parse(jsonResponse);
var lastExportedOrderData = response.lastExportedOrder;
/* currentDateTime = '2016-05-25 12:30:02'; */
var currentDateTime = new Date(response.scriptExecutionTime);
/* lastOrderExportedAt = '2016-01-12 16:53:56'; */
var lastOrderExportedAt = new Date(lastExportedOrderData.exported_at);
currentDateTime Results: 2016-05-25 12:30:02 -> Wed May 25 2016 12:30:02 GMT-0400 (EDT)
lastOrderExportedAt Results: 2016-01-12 16:53:56 > Tue Jan 12 2016 16:53:56 GMT-0500 (EST)
I don't really care about the timezone so long as the dates are in the same timezone.
Update
My dates are now being input using the ISO-8601 Standard format but my the problem remains. 2016-05-25T14:04:00-05:00 results as GMT-0400 where 2016-01-12T16:53:56-05:00 results as GMT -0500
They're in the same timezone (U.S. Eastern). The difference is that the one in May is during Daylight Savings Time and thus is Eastern Daylight Time (EDT), and the one in January isn't, and thus is in Eastern Standard Time.
It's important to note that their underlying time values (e.g., as from dt.getTime()) are unaffected by timezone, and as both dates have been parsed (in your example) as local time, they can reliably be compared. You'd only run into an issue comparing them if one were parsed as local time and the other as UTC.
Side note: You're relying on new Date to parse those strings, which means you're relying on unspecified behavior that may vary from JavaScript engine to JavaScript engine. The only string format engines are required to support is a simplified version of ISO-8601, which those strings are not in. In particular, it's entirely possible that one browser's JavaScript engine will decide to parse them as though they were in UTC and another engine's browser will decide (as yours seems to have done) to parse them as local time.
You haven't said what timezone the strings are meant to be interpreted in, but if (for instance) they were meant to be UTC, you can fairly easily change them to be in ISO-8601 format by replacing the space between the date and time with a T, and adding a Z to the end:
str = str.replace(" ", "T") + "Z";
The resulting string, for instance "2016-05-25T12:30:02Z", is valid for new Date on any modern JavaScript engine (so, not IE8).
Alternately, if you know what timezone offset they should be interpreted in (say, GMT-05:00), you can replace the space with T and add a timezone offset to the end:
str = str.replace(" ", "T") + "-05:00";
That string (for instance, "2016-05-25T12:30:02-05:00") is also valid for new Date.
Alternately, get the parts of them:
var parts = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(str);
...and build the date/time yourself
// As local time
var dt = new Date(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
);
or
// As UTC
var dt = new Date(Date.UTC(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
));
The unary + on the above coerces from string to number. (It isn't strictly necessary, though, both new Date and Date.UTC do it for you.)
Just to add to TJ's answer, in implementations consistent with ES5 and later, the string 2016-05-25T14:04:00 will be parsed as a "local" time as it doesn't have a time zone offset. (In an ES2015-compliant browser. Sadly in the previous specification, they got that wrong and made it default to UTC, and currently Chrome implements the ES5 spec [UTC] but Firefox implements the ES2015 spec [local time].) But don't rely on that, parse the string per TJ's answer.
When creating a local time value, the host time zone settings are taking in to consideration. It seems that the host system is set for EST/EDT.
EDT starts at 02:00 on the second Sunday in March, so on 2016-01-12 US Eastern Standard Time (EST) applies which has an offset of -0500. For 2016-05-25, US Eastern Daylight Time (EDT) is used which has an offset of -0400.
Note that the actual time value of the Date created for 2016-05-25T12:30:02-0400 is for 2016-05-25T16:30:02Z (i.e. the equivalent UTC time). When displayed, the date's toString method is called which applies the host system offset for that time and date to adjust the displayed value to "local" time.
Date's use UTC internally so that they can be displayed in any time zone and still represent the same instant in time.

Why isn't "2016-02-16" equal to "2016-02-16 00:00"?

I'm trying to pass both date strings to new Date(t).
I expect both strings represent the same time, after all, if I omit the time, shouldn't it be midnight of that day?
But while,
new Date("2016-02-16 00:00")
returns 2016-02-16, midnight, local time as expected,
new Date("2016-02-16")
returns 2016-02-16, midnight UTC, which is wrong, or at least not what I expected given what the other string parses as.
I would understand it if they would both have the same behavior, whether it is to return the time as local time, or as UTC, but it seems very inconsistent why they return different things like this.
As a workaround, whenever I encounter a date that has no corresponding timestamp I can append " 00:00" to get consistent behavior, but it seems like this is rather fragile.
I am getting this value from an INPUT element, of type 'datetime-local' so it seems especially inconsistent that I have to work around a value returned by a page element.
Am I doing something wrong, or should I be doing something differently?
It's what the ES5.1 specification says to do:
The value of an absent time zone offset is “Z”.
It also says:
The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
Since the format requires a T separator between date and time, the valid times go to UTC:
> new Date("2016-02-16T00:00:00")
Tue Feb 16 2016 01:00:00 GMT+0100 (CET)
> new Date("2016-02-16")
Tue Feb 16 2016 01:00:00 GMT+0100 (CET)
...while in node.js, an invalid time (without the T separator) seems to go to the implementation specific localtime:
> new Date("2016-02-16 00:00:00")
Tue Feb 16 2016 00:00:00 GMT+0100 (CET)
Note that ES6 changed this, in the same part of the documentation it changes to:
If the time zone offset is absent, the date-time is interpreted as a local time.
The joy of breaking changes.
Edit
According to TC39, the specification is meant to be interpreted as date and time strings without a time zone (e.g. "2016-02-16T00:00:00") are treated as local (per ISO 8601), but date only strings (e.g. "2016-02-16") as UTC (which is inconsistent with ISO 8601).
According to the specifications:
The function first attempts to parse the format of the String
according to the rules called out in Date Time String Format
(15.9.1.15). If the String does not conform to that format the
function may fall back to any implementation-specific heuristics or
implementation-specific date formats.
And Date Time String Formats accept 2016-02-16 as a valid date
This format includes date-only forms:
YYYY
YYYY-MM
YYYY-MM-DD
[...] If the HH, mm, or ss fields are absent “00” is used as the value
and the value of an absent sss field is “000”. The value of an absent
time zone offset is “Z”.
Thus 2016-02-16 translates to 2016-02-16T00:00:00.000Z.
The other date 2016-02-16 00:00 does not conform to the format and therefore its parsing is implementation specific. Apparently, such dates are treated as having local time zone and your example date will return different values depending on time zone:
/* tz = +05:00 */ new Date("2016-02-16 00:00").toISOString() // 2016-02-15T19:00:00.000Z
/* tz = -08:00 */ new Date("2016-02-16 00:00").toISOString() // 2016-02-16T08:00:00.000Z
Summary:
For conforming date time formats the behavior is well defined — in the absence of time zone offset the date string is treated as UTC (ES5) or local (ES6).
For non-conforming date time formats the behavior is implementation specific — in the absence of time zone offset the usual behavior is to treat the date as local.
As a matter of fact, the implementation could choose to return NaN instead of trying to parse non-conforming dates. Just test your code in Internet Explorer 11 ;)
You are perhaps running into a differences between ES5, ES6 implementations and your expected result. Per Date.parse at MDN, "especially across different ECMAScript implementations where strings like "2015-10-12 12:00:00" may be parsed to as NaN, UTC or local timezone" is significant.
Additional testing in Firefox 44 and IE 11 revealed they both return a date object for new Date("2016-02-16 00:00"), which object returns NaN when atttempting to get a date component value, and whose toString value is
"Invalid Date" (not "NaN"). Hence appending " 00:00 to get consistent behavior" can easily break in different browsers.
As noted in other answers new Date("2016-02-16") uses a timezone offset of zero by default, producing midnight UTC instead of local.
Per DateParser::Parse() of V8 source codes for Chrome.
ES5 ISO 8601 dates:
[('-'|'+')yy]yyyy[-MM[-DD]][THH:mm[:ss[.sss]][Z|(+|-)hh:mm]]
An unsigned number followed by ':' is a time value, and is added to the TimeComposer.
timezone defaults to Z if missing
> new Date("2016-02-16 00:00")
Tue Feb 16 2016 00:00:00 GMT+0800 (China Standard Time)
A string that matches both formats (e.g. 1970-01-01) will be parsed as an ES5 date-time string - which means it will default to UTC time-zone. That's unavoidable if following the ES5 specification.
> new Date("2016-02-16")
Tue Feb 16 2016 08:00:00 GMT+0800 (China Standard Time)
returns 2016-02-16, midnight UTC, which is wrong, or at least not what I expected given what the other string parses as.
It adds the timezone offset to the 00:00
new Date("2016-02-16") outputs Tue Feb 16 2016 05:30:00 GMT+0530 (India Standard Time)
My timezone being IST with an offset value (in minutes) +330, so it added 330 minutes to 00:00.
As per ecma-262, section 20.3.3.2 Date.parse ( string )
If ToString results in an abrupt completion the Completion Record is
immediately returned. Otherwise, parse interprets the resulting String
as a date and time; it returns a Number, the UTC time value
corresponding to the date and time. 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.
When you explicitly set the time-units new Date("2016-02-16 00:00") it wll use set that as hours and minutes,
Otherwise as stated here in 20.3.1.16
If the time zone offset is absent, the date-time is interpreted as a
local time.

Retrieving the timezone offset that was already input

I create a date like this:
var date = new Date('Wed, 19 Mar 2014 18:17:00 +0200');
This resolves to:
Wed Mar 19 2014 17:17:00 GMT+0100 (Central European Standard Time)
Is there a way to retrieve the "+0200" portion from the date object once it is created? I am trying to get this without parsing the input string and without the use of external libraries.
EDIT:
When I use
date.getTimezoneOffset();
It returns "-60", which corresponds to the local timezone offset, which in my case is GMT+0100. The question I am asking is whether the "+0200" from the input is lost in the Date object upon creation, or is it stored somewhere?
You can retrieve the timezoneoffset with date.getTimezoneOffset(); and use that to calculate it
From the Mozilla MSDN
The getTimezoneOffset() method returns the time-zone offset from UTC, in minutes, for the current locale.
date.getTimezoneOffset()
The time-zone offset is the difference, in minutes, between UTC and local time. Note that this means that the offset is positive if the local timezone is behind UTC and negative if it is ahead. For example, if your time zone is UTC+10 (Australian Eastern Standard Time), -600 will be returned. Daylight saving time prevents this value from being a constant even for a given locale.
A date object is stored as the number of milliseconds since the Unix epoch. So your input string is parsed and stored as a primitive number. So no, you can't retrieve aspects of your original input after it has been converted to a date object.

Categories