JavaScript date.setMilliseconds strange behavior? - javascript

Why does this happen -
var date = new Date('2015-10-24T23:31:04.181Z');
date.toISOString(); // "2015-10-24T23:31:04.181Z"
date.setMilliseconds(date.getMilliseconds() + 1);
date.toISOString(); // "2015-10-24T22:31:04.182Z"
The HOUR has moved one back (23 -> 22)
Works fine with other dates
Is it a time zone issue ? Why doesn't it happen with all date values ?
Thanks

It's daylight savings time, in a really non-obvious way. :-)
2015-10-24T23:31:04.181Z in Jerusalem is Oct 25 2015 01:31:04 GMT+0200 (Jerusalem Standard Time) on the date/time when DST ends and the clocks go back (reference). Specifically, it's within the Groundhog Hour: There are two 01:31:04s that day, first the one in summer time, then the one an hour later in standard time.
You're using setMilliseconds, which is a local time function. As you probably know, JavaScript's Date is smart about handling rollover between date fields (although there is no rollover in this particular case), and so the logic of setMilliseconds has to handle allowing for possible rollover. This is detailed in the specification, but fundamentally it takes the current time value (milliseconds since The Epoch), makes a local date/time out of it, does the work, and then has to turn that local date/time back into a new time value.
But here's where the problem kicks in: Those two 01:31:04s that day. V8 has to choose one when determining the real date/time. It chooses the one in summer time, which is an hour earlier than your original date/time.
If you use setUTCMilliseconds, this doesn't happen, because there's no round-trip to local time.
Moral of the story: If you're working in UTC, work exclusively in UTC. :-)

Related

Add a duration to a repeating event's start time so that it's end is always the same time (i.e 2pm to 4 pm)

I have a bunch of rrules (implemented in rrule.js) that gives me an array of event start times (see the demo). rrule.js doesn't actually provide the concept of an event duration or endtime... So it can tell me the precise date when the millionth occurrence of a repeating event will start but not when it will end. Turns out I actually want to know when an event ends so I'll have to get creative. As far as I see it I've got two options
DB SIDE: Store an rrule string + an event duration.
CLIENT SIDE: Reconstitute events start date array from rrule string. Only start times would be known and end times would be calculated by adding the duration as an offset to each start time in the array.
DB SIDE: Store a modified rrule string which encodes an endtime.
CLIENT SIDE: A special wrapper function reads the modified rrule string and reconstitutes it as two date arrays; one representing event start times and the other end times.
Option 1 seems easier but I suspect it will run into problems with daylight savings. For example, say I've an event that is every Tuesday from 6pm to 2 am Wednesday. In that case I'd store a duration of 8 hours in my database alongside that stringified rrule. Now let's fast forward to any 6pm Tuesday in the future. Does my event always end on Wednesday at 2am (or does that 8 hour duration sometimes make my event end at 1am or 3am)? How do I get it to always end at 2am?
... If you know the answer then just stop reading here.
How I've seen others handle duration offset
According to Kip in How to add 30 minutes to a JavaScript Date object? the smart way to offset a date time is to use a fancy library like moment.js.
He emphasizes that point by showing how easily things go wrong using non fancy date time libraries (showing how a naive minute offset function fails due to daylight savings)
function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes*60000);
}
addMinutes(new Date('2014-11-02'), 60*24) //In USA, prints 11pm on Nov 2, not 12am Nov 3!
But something weird happens for me. The function above was supposed to output 11pm on Nov 2 - which is the wrong answer i.e. it was supposed to fail because of daylight savings. When I run it, it actually outputs the right time 12am on Nov 3 (note: I'm in Chicago/Central time).
When I compare the output of his naive function to the output of moment.js and luxon.js, I get the same answer as you can see in this observable notebook.
Scratching my head
What's more, if using luxon or moment, when you add a days worth of minutes to 2014-11-02 you get2014-11-03T00:00:00.000Z but if you just directly add a day to 2014-11-02 you get 2014-11-03T01:00:00.000Z - it's an hour off.
So am I better off pursuing option 2?
Now let's fast forward to any 6pm Tuesday in the future. Does my event always end on Wednesday at 2am (or does that 8 hour duration sometimes make my event end at 1am or 3am)? How do I get it to always end at 2am?
The standard Javascript Date object automatically handles the daylight savings shift for you. Even if you add 8 hours to a date at 6pm the day before daylight savings, the new date will still end at 2am the next day.
Incidently, I implemented duration support in rSchedule and since it supports both the standard javascript Date as well as moment/luxon dates, you can test a recurring event with a duration using either library and see that they both produce the same result.
This example can be seen on stackblitz.
import { Schedule } from '#rschedule/rschedule';
import { StandardDateAdapter } from '#rschedule/standard-date-adapter';
// This example will also work with `moment`, `moment-timezone`, and `luxon`
// (assuming you import the proper date adapter -- see rSchedule docs)
const schedule = new Schedule({
rrules: [
{
start: new Date(2019,9,10,18),
frequency: "DAILY",
duration: 1000 * 60 * 60 * 8,
count: 30
}
],
dateAdapter: StandardDateAdapter,
});
schedule.occurrences().toArray().forEach(adapter => {
console.log(
{
start: adapter.date.toLocaleString(),
end: adapter.end.toLocaleString(),
}
)
})
Turns out I actually want to know when an event ends
To find out when this event ends, you could do:
const iterator = schedule.occurrences({ reverse: true })
const { end } = iterator.next().value
This trick would only work with an event that actually has an end date (so not an event with infinite occurrences).
I wrote the original answer you are referring to about a decade ago. Seven years later, I made an edit, changing new Date(2014, 10, 2) to new Date('2014-11-02'). I thought this would be easier to read (because you don't have to explain that the months in that version of the constructor start at 0 instead of 1). But as #RobG pointed out, formatting in this way causes it to be parsed as UTC. I've gone back and fixed this now (thanks for pointing it out).
To get to your "scratching my head" part of your question:
What's more, if using luxon or moment, when you add a days worth of minutes to 2014-11-02 you get 2014-11-03T00:00:00.000Z
The Z at the end of that timestamp means it is in UTC, and UTC does not observe daylight savings time. So if you start with 2014-11-02T00:00:00.000Z, and add 24 hours, you get 2014-11-03T00:00:00.000Z. When you add hours/minutes/seconds, there's no need to worry about daylight saving time.
but if you just directly add a day to 2014-11-02 you get 2014-11-03T01:00:00.000Z - it's an hour off.
In this case what is happening is you are starting with 2014-11-02T00:00:00.000Z, but when you tell the library to add one day, and you don't specify a time zone, the library is assuming you are in your local time zone, so it adds one local day. Because you cross a DST boundary, that day is 25 hours long, and when you print it as an ISO timestamp in UTC, you end up with 2014-11-03T01:00:00.000Z (25 hours later).
Time zone stuff is hard, even if you are using a library. Most people can get by for a long time not knowing or caring that for many users one day a year is 25 hours long. But if these edge cases will matter to you, the best approach is to play around with them like you're doing, and make sure you really understand what is happening and why.

How to convert any time zone 8am into utc time

How can I convert any time zone 8 AM into UTC time with moment or javascript
moment("8:00:00","h:mm:ss").tz('time_zone).utc().toString()
I tied this but now working. Any solution?
If you meant the user's local time zone, you should use this:
moment("8:00:00","H:mm:ss").utc().format()
H is for a 24 hour clock, h is for a 12 hour clock. Though it wouldn't necessarily matter for 8am, there should be some way to know you didn't mean 8pm. (This assumes 8pm would be 20:00:00).
You don't need moment-timezone's tz function. You don't really need moment-timezone at all unless your source data is in some arbitrary time zone rather than the user's local time zone.
Use format instead of toString. If you want the output in a particular format, refer to the documentation for arguments you can pass to that function.
If you actually meant an arbitrary time zone with an IANA time zone identifier such as America/New_York, then instead it should be like this:
moment.tz("8:00:00","H:mm:ss","America/New_York").utc().format()
With this one, you do need moment-timezone.
Lastly, if this is a new project targeting modern environments, the Moment team recommends you consider using Luxon instead. Luxon takes its time zone support from the environment itself, and thus is much smaller in size as well.
luxon.DateTime.fromFormat('8:00:00', 'H:mm:ss', { zone: 'America/New_York'})
.toUTC().toString()
Initialize a variable with the current date value you have for other regions (must be in date format) and use .toUTCString() method.
Sample code for IST to UTC is below:
var curdate = new Date("Fri Mar 29 2019 08:00:00 GMT+0530");
var UTCdate = curdate.toUTCString();

momentjs days difference between two GMT dates

I'm trying to get difference of days between two GMT dates using moment
but I couldn't find it.
I'm on IST(+05:30) and I have some GMT dates(-05:00) in db,
I tried using following command
temp2.diff(temp1, "days")
here is a screenshot of all the commands tried in console
there we can clears see that dates are different and still shows the difference is 0
here is how I'm initializing moment objects of 'America/New_York'
var temp1 = moment.tz(new Date('Mon Jan 25 2016 22:00:00 GMT-0600'), 'America/New_York');
var temp2 = moment.tz(new Date('Tue Jan 26 2016 00:00:00 GMT-0600'), 'America/New_York');
any help appreaciated, thanks.
Well, there is less than 24 hours difference between those dates, so it's correct. The documentation says:
By default, moment#diff will return number rounded down. If you want the floating point number, pass true as the third argument.
> temp2.diff(temp1, "days", true)
0.08333333333333333
If you don't care about the hours at all, set them to 0 before you do the comparison
> temp2.hours(0).diff(temp1.hours(0), "days")
1
A few things:
You say that you are retrieving these values from a database, but then you show us loading them via the Date constructor from a string value. If you are really storing a string in your database, especially in that particular format, then you have much larger problems than the one you asked about! Please show us precisely how you load the values from your database to begin with.
You shouldn't rely on the Date object for parsing, especially when you are already using moment, which has much better parsing routines of its own.
You said these values where in America/New_York, but then you show an offset of -0600. That's never used in that time zone. The offset for the value you showed would be -0500.
You also said "I have some GMT dates(-05:00)" - which doesn't make any sense. GMT is +00:00. GMT-0500 means "5 hours behind GMT". Thus, you no longer have a "GMT date".
Be aware that the JavaScript Date object can only use the time zone of where the code is running. You cannot run it in any other time zone.
While Felix is correct in how you can show decimals with the diff function, you should realize that diff is giving you the actual elapsed time between the two moments in time you asked about. However, you seem to be wanting to know the total number of calendar days separating the two days that the moments fall into within the named time zone. To do that, you'd need to ignore the time portion. Using startOf('day') is an easy way to do that. Consider:
var a = moment.parseZone("2016-01-25T23:00:00-05:00");
var b = moment.parseZone("2016-01-26T01:00:00-05:00");
b.diff(a, 'days', true) // 0.08333333333333333 (not what you want)
b.startOf('day').diff(a.startOf('day'), 'days') // 1 (that's better!)
moment(b).startOf('day').diff(moment(a).startOf('day'),'days') // 1 (best approach)
Note a few things with this code:
The code in the last line is the best approach, as it leaves the original values of a and b alone. Otherwise, they would be modified. (Moments are mutable.)
You seem to already have the correct local time and offset, and thus there's no need to use moment-timezone's tz function. You can just use parseZone. Of course if this was just a side effect of your example, then you could still use moment-timezone, but I'd strongly recommend against using the Date constructor still.

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.

Javascript countdown using absolute timezone?

I have a javascript countdown timer that works by taking a specified date and time, and comparing it to the current date and time. The issue is, the current time is relative to the users timezone, so the time remaining is different between users.
How can I have the timer countdown till a time in a specific timezone, in my case GMT -5 hours?
I understand i can use the below code to get the users timezone, but I am lost as how to use this.
myDateObj.getTimezoneOffset( ) / 60
You can use Date.UTC(year,month,day,hours,minutes,seconds,msec)
It operates just like the Date constructor, but returns the timestamp of the arguments at Greenwich time (offset=0) instead of local time.
var localtime=new Date(Date.UTC(year,month,day,hours,minutes,seconds,msec))
returns the local time for the UTC time specified.
Everyone (whose clock is set correctly) will end the countdown together.
A quick search reveals: convert-the-local-time-to-another-time-zone-with-this-javascript
Following the article verbatim gets you this example:
var d = new Date();
var localTime = d.getTime();
var localOffset = d.getTimezoneOffset() * 60000;
var utc = localTime + localOffset;
// obtain and add destination's UTC time offset
// for example, Bombay
// which is UTC + 5.5 hours
var offset = 5.5;
var bombay = utc + (3600000*offset);
var nd = new Date(bombay);
alert("Bombay time is " + nd.toLocaleString() + "<br>");
jsFiddle: http://jsfiddle.net/GEpaH/
Just update with your desired offset and you should be all set.
Just create a Date with an RFC 2822-timestamp with timezone. That time will then be converted to the users current location (based on OS settings). Even with corrections for daylight savings time!
I'm in Norway, which currently is in daylight savings time, so it's GMT+2. Here is what happens when I create a Date object using GMT-0500:
var myDateObj = new Date("Fri Apr 17 2015 12:00:00 GMT-0500 (CDT)");
myDateObj.toString();
Fri Apr 17 2015 19:00:00 GMT+0200 (CEST)
How to get the correct date string for your location? If it's the timezone you're currently in; just do myDateObj.toString() in your browsers dev-tools console. For a different timezone; change the timezone in your operating system first. (Or read the RFC)
new Date().toString();
Fri Apr 17 2015 12:36:57 GMT+0200 (CEST)
You don't really say exactly what your are trying to accomplish. The javascript date object retrieves the local time and the "timezone offset" (relative to GMT (UTC)). These are of course "set" by the user so even if in the same time zone, two users are very unlikely to have the same "time".
If you are trying to time between different users you need to be referencing some centralized time authority.
I would use an AJAX type call (XMLHttpRequest) to a page own my own server that returns my server's time. That way each user is referencing the same time.
Google XMLHttpRequest to find examples of the JS code you need (and oftentimes the corresponding server-side code for a simple service such as this).
PS: I would also install some simple client software to keep the time on my server accurate by synching with an atomic clock every 10 minutes.
You can't get an accurate date with JavaScript as it is client-side and it is based on the user's operating system clock. You can use jCounter to display countdowns based on server-side timezones.
But hey, if you really want to do it yourself, download jCounter and you'll find a dateandtime.php file as well which retrieves the current date server-side, as timestamp (it will have to be placed on a server btw, not on your desktop :P)
Check how that script uses that file and retrieves the real current date to operate against it and calculate accurate countdowns.
Cheers
To revise the approach Brandon has taken to calculate a UTC-shifted time, we can slim the code down into a two-line extension of the Date object:
/* getOffsetDate - Returns a Date shifted by a certain offset
* #param offset the UTC offset to shift, in hours
* #return new date object shifted by UTC offset
*/
Date.prototype.getOffsetDate = function( offset ) {
utc = this.getTime() + (this.getTimezoneOffset() * 60000);
return new Date(utc + (3600000*offset));
}
You can then calculate the UTC-5 shifted date as follows:
myDate = new Date().getOffsetDate(-5);
It should be noted that extending native prototypes in this manner is generally considered a bad practice since it muddles core objects that other libraries depend upon. To justify it, you'd have to argue this functionality should be a native part of the Date object.

Categories