Javascript - subtract two dates in milliseconds considering daylight time saving - javascript

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

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.

Moment.js local relative time

I have dates in my database set to Europe/London time. I am using Moment.js to show relative time e.g. "3 minutes ago". This works fine for me as I am in the same timezone, but for example, someone who is PST timezone would see "in 8 hours". How can I fix this?
My current code is like this:
$('time').text( moment( '2016-01-22 18:00:00' ).fromNow() );
To echo Jon's answer, moment's relative time functionality is strictly UTC based, so the behavior you describe won't actually happen, unless you are interpreting the original timestamp in local time.
It's hard to say if you're doing that or not, as you didn't give a sample value of the input string.
If your times are indeed UTC based, but that's not reflected in the input string, then use moment.utc instead of just moment.
And no, London is not the same as UTC.
I believe that the best approach is to store the date in UTC and then convert this to the local time zone for display. Note that this is not necessarily the same as London time because UTC does away with daylight savings time nonsense. You can do everything that you need with the date class provided the time stamp stored in the database does not have to deal with the vagaries of time zone and DST. The date class maintains its own epoch internally as milliseconds elapsed since midnight 1 January 1970 UTC. You can evaluate the difference between two Date objects as follows:
var agora = Date.now();
var stored = ... // the date that was stored in your database
var diff_msec = agora.getTime() - stored.getTime();
Knowing that the difference and that its units are milliseconds, you can convert the difference to whatever units are best for presentation.

Inconsistent getTimezoneOffset results

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

Get local time representation of Date in arbitrary timezone in arbitrary day - considering Daylight Savings Time

For sure, there is a lot of questions about Date objects and timezones but many of them are about converting the current time to another timezone, and others are not very clear about what they want to do.
I want to display the day, hour, minute etc. in an arbitrary timezone, in an arbitrary day. For example, I would like a function f(t, s) that:
given the timestamp 1357041600 (which is 2013/1/1 12:00:00 UTC) and the string "America/Los Angeles", would satisfy the comparison below:
f(1357041600, "America/Los Angeles") == "2013/01/01 04:00:00"
given the timestamp 1372680000 (2013/07/01 12:00:00 UTC), would satisfy the comparison below:
f(1357041600, "America/Los Angeles") == "2013/07/01 05:00:00"
will always behave this way even if the timezone in the browser is, let us say "Europe/London" or "America/São Paulo".
will always behave this way even if the time in the browser is, let us say 2014/02/05 19:32, or 2002/08/04 07:12; and
as a final restriction, will not request anything from the server side (because I'm almost doing it myself :) )
Is it even possible?
given the timestamp 1357041600 (which is 2013/1/1 12:00:00 UTC)
That appears to be seconds since the UNIX epoch (1970-01-01T00:00:00Z). Javascript uses the same epoch, but in milliseconds so to create a suitable date object:
var d = new Date(timestamp * 1000);
That will create a Date object with a suitable time value. You then need to determine the time zone offset using something like the IANA time zone database. That can be applied to the Date object using UTC methods. E.g. resolve the offset to minutes, then use:
d.setUTCMinutes(d.getUTCMinutes() + offset)
UTC methods can then be used to get the adjusted date and time values to create a string in whatever format you require:
var dateString = d.getUTCFullYear() + '/' + pad(d.getUTCMonth() + 1) + '/' ...
where pad is a function to add a leading zero to single digit values. Using UTC methods avoids any impact of local time zone offsets and daylight saving variances.
There are also libraries like timezone.js that can be used to determine the offset, however I have not used them so no endorsement is implied.
For JavaScript runtime environments that support the ECMAScript Internationalization API, and adhere to its recommendation of supporting the IANA time zone database, you can simply do this:
new Date(1357041600000).toLocaleString("en-US", {timeZone: "America/Los_Angeles"})
For other environments, a library is required. There are several listed here.

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/

Categories