Inconsistent getTimezoneOffset results - javascript

The documentation seems to suggest getTimezoneOffset() always returns the offset of the current locale, irregardless of the date object. But I'm getting inconsistent results that I can't understand.
new Date().getTimezoneOffset() // -120
new Date("2015-03-10T15:48:05+01:00").getTimezoneOffset() // -60
new Date("2015-03-10T15:48:05-04:00").getTimezoneOffset() // -60
Also, is there a better way to get the timezone off a datetime string (maybe with moment.js)?

getTimezoneOffset returns the offset for the specific moment in time represented by the Date object it is called on, using the time zone setting of the computer that it executing the code.
Since many time zones change their offset for daylight saving time, it is perfectly normal for the value to differ for different dates and times. When you call it on new Date(), you're getting the current offset.
The value returned from getTimezoneOffset is in terms of minutes west of UTC, as compared to the more common offsets returned in [+/-]HH:mm format, which are east of UTC. Therefore, the time zone you gave alternates between UTC+1, and UTC+2. My guess is the computer that gave this output was in one of the zones that uses Central European Time - though it could be one of several others.
Also, when you pass in an offset as part of an ISO8601 formatted string, that offset is indeed taken into account - but only during parsing. The offset is applied, and the Date object holds on to the UTC timestamp internally. It then forgets anything about the offset you supplied. On output, some of the functions will explicitly use UTC, but most of them will convert to the local time zone before emitting their result.
You also asked about how to get the offset of a datetime string using moment.js. Yes, that is quite simple:
// Create a moment object.
// Use the parseZone function to retain the zone provided.
var m = moment.parseZone('2015-03-10T15:48:05-04:00');
// get the offset in minutes EAST of UTC (opposite of getTimezoneOffset)
var offset = m.utcOffset(); // -240
// alternatively, get it as a string in [+/-]HH:mm format
var offsetString = m.format("Z"); // "-04:00"

It's because of Daylight Savings Time. For your timezone, on June 11th it is in UTC+2 and on March 10th it's in UTC+1:
// when in DST (since it's June)
new Date("2015-06-11T00:00:00Z").getTimezoneOffset(); // -120
// when not in DST
new Date("2015-03-10T15:48:05+01:00").getTimezoneOffset(); // -60
For me, since I'm in the Eastern Time Zone, the following will happen:
// when in EST
new Date("2015-03-01T00:00:00Z").getTimezoneOffset(); // 300
// when in EDT
new Date("2015-06-01T00:00:00Z").getTimezoneOffset(); // 240

Related

Working with different timezones in Javascript

I am working on a cloud based application which deals extensively with date and time values, for users across the world.
Consider a scenario, in JavaScript, where my machine is in India (GMT+05:30), and I have to display a clock running in California's timezone (GMT-08:00).
In this case I have to get a new date object,
let india_date = new Date()
add it's time zone offset value,
let uts_ms = india_date.getTime() + india_date.getTimezoneOffset()
add california's timezone offset value,
let california_ms = utc_ms + getCaliforniaTimezoneOffsetMS()
and finally the date object.
let california_date: Date = new Date(california_ms)
Is there any way to directly deal with these kinds of time zones without having to convert the values again and again?
First, let's talk about the code in your question.
let india_date = new Date()
You have named this variable india_date, but the Date object will only reflect India if the code is run on a computer set to India's time zone. If it is run on a computer with a different time zone, it will reflect that time zone instead. Keep in mind that internally, the Date object only tracks a UTC based timestamp. The local time zone is applied when functions and properties that need local time are called - not when the Date object is created.
add it's timezone offset value
let uts_ms = india_date.getTime() + india_date.getTimezoneOffset()
This approach is incorrect. getTime() already returns a UTC based timestamp. You don't need to add your local offset. (also, the abbreviation is UTC, not UTS.)
Now add california's timezone offset value
let california_ms = utc_ms + getCaliforniaTimezoneOffsetMS()
Again, adding an offset is incorrect. Also, unlike India, California observes daylight saving time, so part of the year the offset will be 480 (UTC-8), and part of the year the offset will be 420 (UTC-7). Any function such as your getCaliforniatimezoneOffsetMS would need to have the timestamp passed in as a parameter to be effective.
and finally the date object
let california_date: Date = new Date(california_ms)
When the Date constructor is passed a numeric timestamp, it must be in terms of UTC. Passing it this california_ms timestamp is actually just picking a different point in time. You can't change the Date object's behavior to get it to use a different time zone just by adding or subtracting an offset. It will still use the local time zone of where it runs, for any function that requires a local time, such as .toString() and others.
There is only one scenario where this sort of adjustment makes sense, which is a technique known as "epoch shifting". The timestamp is adjusted to shift the base epoch away from the normal 1970-01-01T00:00:00Z, thus allowing one to take advantage of the Date object's UTC functions (such as getUTCHours and others). The catch is: once shifted, you can't ever use any of the local time functions on that Date object, or pass it to anything else that expects the Date object to be a normal one. Epoch shifting done right is what powers libraries like Moment.js. Here is another example of epoch shifting done correctly.
But in your example, you are shifting (twice in error) and then using the Date object as if it were normal and not shifted. This can only lead to errors, evident by the time zone shown in toString output, and will arise mathematically near any DST transitions of the local time zone and the intended target time zone. In general, you don't want to take this approach.
Instead, read my answer on How to initialize a JavaScript Date to a particular time zone. Your options are listed there. Thanks.
JavaScript Date objects store date and time in UTC but the toString() method is automatically called when the date is represented as a text value which displays the date and time in the browser's local time zone. So, when you want to convert a datetime to a time zone other than your local time, you are really converting from UTC to that time zone (not from your local time zone to another time zone).
If your use case is limited to specific browsers and you are flexible on formatting (since browsers may differ in how they display date string formats), then you may be able to use toLocaleString(), but browsers like Edge, Android webview, etc do not fully support the locales and options parameters.
Following example sets both the locale and timezone to output the date in a local format that may vary from browser to browser.
const dt = new Date();
const kolkata = dt.toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' });
const la = dt.toLocaleString('en-US', { timeZone: 'America/Los_Angeles' });
console.log('Kolkata:', kolkata);
// example output: Kolkata: 19/3/2019, 7:36:26 pm
console.log('Los Angeles:', la);
// example output: Los Angeles: 3/19/2019, 7:06:26 AM
You could also use Moment.js and Moment Timezone to convert date and time to a time zone other than your local time zone. For example:
const dt = moment();
const kolkata = dt.tz('Asia/Kolkata').format();
const la = dt.tz('America/Los_Angeles').format();
console.log(kolkata);
// example output: 2019-03-19T19:37:11+05:30
console.log(la);
// example output: 2019-03-19T07:07:11-07:00
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.23/moment-timezone-with-data.min.js"></script>
Well, you really do kind of have to convert anytime you want to change the display, but it's not as bad as you think.
First, store all time as UTC. Probably using the milliseconds format, e.g. Date.UTC().
Second, do all manipulation / comparison using that stored info.
Third, if your cloud-based application has an API that API should only talk in terms of UTC as well, though you could provide the ISO string if you prefer that to the MS, or if you expect clients to handle that better.
Fourth and finally, only in the UI should you do the final conversion to the local date/time string, either with the method you're describing or using a library such as momentjs
new Date creates a Date object with a time value that is UTC. If you can guarantee support for the timeZone option of toLocaleString (e.g. corporate environment with a controlled SOE), you can use it to construct a timestamp in any time zone and any format, but it can be a bit tedious. Support on the general web may be lacking. A library would be preferred in that case if you need it to work reliably.
E.g. to get values for California, you can use toLocaleString and "America/Los_Angeles" for the timeZone option:
var d = new Date();
// Use the default implementation format
console.log(d.toLocaleString(undefined, {timeZone:'America/Los_Angeles'}));
// Customised format
var weekday = d.toLocaleString(undefined, {weekday:'long', timeZone:'America/Los_Angeles'});
var day = d.toLocaleString(undefined, {day:'numeric', timeZone:'America/Los_Angeles'});
var month = d.toLocaleString(undefined, {month:'long', timeZone:'America/Los_Angeles'});
var year = d.toLocaleString(undefined, {year:'numeric', timeZone:'America/Los_Angeles'});
var hour = d.toLocaleString(undefined, {hour:'numeric',hour12: false, timeZone:'America/Los_Angeles'});
var minute = d.toLocaleString(undefined, {minute:'2-digit', timeZone:'America/Los_Angeles'});
var ap = hour > 11? 'pm' : 'am';
hour = ('0' + (hour % 12 || 12)).slice(-2);
console.log(`The time in Los Angeles is ${hour}:${minute} ${ap} on ${weekday}, ${day} ${month}, ${year}`);
Getting the timezone name is a little more difficult, it's difficult to get it without other information.

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.

Javascript - subtract two dates in milliseconds considering daylight time saving

I have the following function:
function subtractDatesMs(date1, date2) {
var departureDate = new Date(date1);
var arriveDate = new Date(date2);
var diff = (arriveDate.getTime() - departureDate.getTime());
return diff;
}
The dates sent as parameters are like this:
"2015-11-09T15:50:00-04:00",
"2015-11-09T12:40:00-05:00"
including timezone offset value.
It works fine in most of the cases, however when I subtract some dates from a region/area where are using daylight time saving the difference is incorrect (it is delayed/ahead 1 hour for example).
What is the best way to subtract these dates and get the correct difference in milliseconds considering daylight time savings from the region/area of the timezone it comes?
Any sample is welcomed. You can use jquery if you want or any other library to get the correct values.
I think the problem is in the date strings that your code is receiving. The UTC offset should properly reflect the additional DST offset. The example time you gave "2015-11-09T12:40:00-05:00" implies Eastern Standard Time* because of the -05:00. The other example time "2015-11-09T15:50:00-04:00" implies Atlantic Standard Time or Eastern Daylight Time (i.e. Eastern Standard plus DST)
JavaScript's Date object will properly convert the network time formats that you gave in the example.
If it is not possible to correct the input times, you will then need to also know the country since DST is specific to a country or region of a country. You can then use a solution like in the answer given for How to know whether a country is using Daylight Saving Time using JavaScript?, which retrieves the correct timezone offset for a given place or latitude & longitude.
* = This assumes North America

Timezone offset of any time using using JavaScript Date

I am using var offset = new Date().getTimezoneOffset() to get the current timezone offset of user.
But, how can I get the timezone offset for any future time?
This is required because the timezone offset is different when DST is enabled/disabled.So I cant assume the same offset for future time.
This question confused me into thinking that perhaps getTimezoneOffset() wouldn't correctly get the timezone offset for future dates already. It does.
As far as JavaScript is concerned, you give it a milliseconds since the epoch date, which is ubiquitous, and it gives you the offset in local time as it will be on that date, because all the Date functionality cares about is taking a universal date/time and passing back a date that will make sense to the local user, unless you specifically ask it for the universal equivalent, with methods like getUTCDate().
So running
var summer = new Date(1403170847000);
alert('Summer offset=' + summer.getTimezoneOffset());
var winter = new Date(1419500175000);
alert('Winter offset=' + winter.getTimezoneOffset());
in a console where the local time includes a DST period, reports for me in the UK an offset of -60 (i.e. GMT+1) for the date in the summer, and 0 (i.e. GMT) for the date in the winter.
http://jsfiddle.net/SX9SN/

What format is this timestamp, and how can I format it in its own time

I have a problem converting a timestamp in javascript.
I have this timestamp:
2011-10-26T12:00:00-04:00
I have been trying to format it to be readable. So far, it converts this using the local time of my system instead of the GMT offset in the timestamp. I know the timezone that this was created in is EST. I'm in PST, so the times are being offset by 3 hours.
Instead of this showing as:
Wednesday October 26, 2011 12:00 pm
It shows as:
Wednesday October 26, 2011 9:00 am
I have tried a few different solutions, but the latest one is found here: http://blog.stevenlevithan.com/archives/date-time-format
I am less concerned with the formatting part as I am with figuring out how to handle the GMT offsets. Would appreciate any help and insight anyone can provide.
Date objects are created in the local zone. If the date string was created in a different time zone, then you need to adjust the date object to allow for the difference.
The abbreviations PST and EST are ambiguous on the web, there is no standard for time zone abbreviations and some represent two or zones. You should express your zone only in terms of +/- UTC or GMT (which are the same thing, more or less).
You can get the local time zone offset using Date.prototype.getTimezoneOffset, which returns the offset in minutes that must be added to a local time to get UTC. Calculate the offset for where the time string was created and apply it to the created date object (simply add or subtract the difference in minutes as appropriate).
If your time zone is -3hrs, getTimezoneOffset will return +180 for a date object created in that zone. If the string is from a zone -4hrs, its offset is +240. So you can do:
var localDate = new Date('2011-10-26T12:00:00') // date from string;
var originOffset = 240;
var localOffset = localDate.getTimezoneOffset();
localDate.setMinutes( localDate.getMinutes() + originOffset - localOffset );
Adding the origin offset sets it to UTC, subracting the local offset sets it to local time.
It would be much easier if the time string that was sent by the server was in UTC, that way you just apply the local offset.
Edit
IE will not parse a time string with an offset, and Chrome thinks that the above time string is UTC and adjusts for local offset. So don't let Date parse the string, do it manually.
It doesn't matter what time zone you are- the time stamp will result in a different local time for every different time-zone, but they all will be correct, and anyone checking the UTC time of the date will get the same time-stamp:
new Date('2011-10-26T12:00:00-04:00').toUTCString()
returns
Wed, 26 Oct 2011 16:00:00 GMT
and getTime() anywhere returns the same milliseconds universal timestamp:1319644800000

Categories