New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding and subtracting from dates does not preserve hours when the timezone has midnight DST rules #4743
Comments
I tried to reproduce this in Europe/Rome timezone, and was unable to: Europe/Rome - https://www.timeanddate.com/time/change/italy/rome
There is no bug in this code. d => d.format() + ' ' + d.tz()
x = moment.tz(new Date('10/27/2018 00:00:00'), 'Europe/Rome');
fmt(x) // "2018-10-27T06:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'day')) // "2018-10-28T06:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'day')) // "2018-10-29T06:00:00+01:00 Europe/Rome" You can see that the TZ changes, but the hours remain constant. |
Based on the reproduction steps, it looks like there is a discrepancy in the code when the date added lands perfectly onto the DST cutoff. In the Santiago timezone, the DST rule is that at midnight the clocks go back an hour. It appears that this edge case may be specifically to timezones where the DST cutoff is at midnight, because I was unable to reproduce with a timezone where the cutoff is at 3am, with Europe/Rome - https://www.timeanddate.com/time/change/italy/rome
There is no bug in this code. fmt = d => d.format() + ' ' + d.tz()
x = moment.tz(new Date('October 27, 2018 03:00:00'), 'Europe/Rome');
fmt(x); // "2018-10-27T09:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'days')) // "2018-10-28T09:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'days')) // "2018-10-29T09:00:00+01:00 Europe/Rome" |
I was able to confirm that this bug only exists for timezones when the DST cutoff is at midnight, and you are adding time to a date that causes the date to land exactly at the cutoff. America/Punta_Arenas - https://www.timeanddate.com/time/zone/chile/punta-arenas This code appears to have a bug. See comment after each line fmt = d => d.format() + ' ' + d.tz()
x = moment.tz(new Date('08/13/2016 00:00:00'), 'America/Punta_Arenas')
fmt(x); // "2016-08-13T00:00:00-04:00 America/Punta_Arenas"
fmt(x.clone().add(1, 'days')); // "2016-08-13T23:00:00-04:00 America/Punta_Arenas" - 23 hours added, not 1 day, no tz offset change
fmt(x.clone().add(2, 'days')); // "2016-08-15T00:00:00-03:00 America/Punta_Arenas" |
I found what appears to be a similar issue when subtracting over the same kinds of timezones (DST at midnight). When subtracting the dates exactly onto a DST shift, the hour is not preserved on just that day, but the date is changed. fmt = d => d.format() + ' ' + d.tz()
x = moment.tz(new Date('08/13/2018 23:00:00'), 'America/Santiago');
fmt(x); // "2018-08-14T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(1, 'days')); // "2018-08-13T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(2, 'days')); // "2018-08-12T01:00:00-03:00 America/Santiago" - hour not preserved, but date changed
fmt(x.clone().subtract(3, 'days')); // "2018-08-11T00:00:00-04:00 America/Santiago" - original hour preserved now |
We received another report of this issue, from a user with The function that produces the bug in our application looks like this: function generateDayRange(start, end) {
const days = [];
let current = start.clone();
while (current <= end) {
days.push(current.clone());
current = current.add(1, 'days');
}
return days;
} When we use this function, we pass in a start date that is at the beginning of the day, and an end date where the time is not as significant: generateDayRange(startDate.clone().startOf('week'), startDate.clone().endOf('week'));
generateDayRange(getAppDate(startDate), getAppDate(endDate));
generateDayRange(startRange, startRange.clone().add(27, 'days')); The issue is that the resulting array of moment dates contains a duplicate day, which is reflected in the UI of our application (a calendar has a duplicate day). My larger concern is that since the library does not behave as expected, there may be more subtle issues going unnoticed, like a perceived loss of data, bad requests, etc. I'll work to get a set of tests up in a codepen or something, so that curious readers can take a stab at solving this issue. |
Related issue #4785 has links to code: I know there was recent DST changes, so we should check against the code currently on |
The behavior here looks like it was changed between moment-timezone v0.5.4 and v0.5.26. Old version, behaves as described in this issue: New version, changed behavior, but still wrong I think? Here is a workaround:
|
We are running into the same issue this month (Sept 2019) where our Santiago users using our date picker are seeing a calendar with dates going 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, for the month of September... Under the hood, we're also using @mblandfo workaround seems to do the trick for us, too. Thanks! |
Having this problem as well. Adding the time zone for Australia/Sydney and then adding days make the hours shift (i.e. not preserving hour correctly). |
We (h/t @trumb1mj) may have resolved this issue for our use case. We have written a few tests in various timezones that replicate the issue, but were not so exhaustive that I can confidently say that we won't see an issue again NOTE: This method only increments days, if you need something that can add and subtract dates in this fashion, you'll need to make further changes/testing. // This method is used to generate a range of dates where each date starts at the beginning of the day
// If there is a TZ switch at midnight, it's possible that a date may start at 1am instead of midnight
generateDayRange(start, end) {
const days = [];
let current = start.clone();
while (current <= end) {
days.push(current.clone());
const prev = current.clone();
current = current.add(1, 'days');
// If there is a TZ change at midnight, adding 1 day may only increase the date by 23 hours to 11pm
// To fix, bump the date into the next day (add 12 hours) and then revert to the start of the day
const diff = current.diff(prev, 'hours');
if (diff === 23 && prev.get('h') === 0) {
current = current.add(12, 'h').startOf('day');
}
}
return days;
}, The critical part there is the check for When testing against the '2021-03-24T00:00:00+02:00',
'2021-03-25T00:00:00+02:00',
'2021-03-26T01:00:00+03:00', // TZ Switch from +2 to +03:00 - 1am is the start of the day
'2021-03-27T00:00:00+03:00', // The following dates are at 12am/0
'2021-03-28T00:00:00+03:00',
'2021-03-29T00:00:00+03:00', Prior to this fix, the date range would be generated like this '2021-03-24T00:00:00+02:00',
'2021-03-25T00:00:00+02:00',
'2021-03-25T23:00:00+02:00', // duplicated date! not the 26th
'2021-03-27T00:00:00+03:00', // 27th... 26th was skipped
'2021-03-28T00:00:00+03:00',
'2021-03-29T00:00:00+03:00', |
Description of the Issue:
I'm trying to create a date range in (array of moment dates) where each date is 1 day after the previous. This works as expected for all users, but we've gotten a bug report for a user in the America/Santiago timezone who is seeing duplicated dates in the UI. What I noticed is that adding 1 day to a particular date is actually only adding 23 hours.
With a date on
8/11/2018 00:00:00
in America/Santiago timezone, adding 1 day increases the time to8/11/2018 23:00:00
instead of8/12/2018 00:00:00
.The docs explain that the hours should be preserved when shifting across a DST while using days, but that does not appear to be true here.
Steps to Reproduce
In the following code, the timezone being used has it's DST rule on midnight. I have a date that is on midnight the day before this rule kicks in. I added 1 day to this date, and expect that it should be the following day at midnight/0 hours (hours should be preserved), but it's actually only adding 23 hours.
Here, you can see that adding 24 hours shifted it up by 25 hours and the timezone offset was changed.
Environment:
Other information that may be helpful:
JS Date Output
(new Date()).toString()
(new Date()).toLocaleString()
(new Date()).getTimezoneOffset()
navigator.userAgent
moment.version
The text was updated successfully, but these errors were encountered: