Skip to content
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

endOf('day') fails on days that don't start at midnight #3132

Closed
mattjohnsonpint opened this issue Apr 20, 2016 · 15 comments · Fixed by #4338
Closed

endOf('day') fails on days that don't start at midnight #3132

mattjohnsonpint opened this issue Apr 20, 2016 · 15 comments · Fixed by #4338

Comments

@mattjohnsonpint
Copy link
Contributor

Consider:

moment("2016-10-16").endOf('day').format("YYYY-MM-DD HH:mm:ss")

In most time zones, this will return "2016-10-16 23:59:59". However, in Brazil, this will return "2016-10-17 00:59:59". This is incorrect, because only the hour from 00:00 to 00:59 on 2016-10-16 is missing. The end of 2016-10-16 is still 23:59:59 on the same date.

This is also reproducible with moment-timezone:

moment.tz("2016-10-16","America/Sao_Paulo").endOf('day').format("YYYY-MM-DD HH:mm:ss")

The problem is in the endOf method. To compute the end of the day, we take the start of the day, add one day, then subtract one millisecond. That logic is flawed when the day doesn't start at midnight. Instead of adding one day, we need to add the exact duration length of the particular day.

@mattjohnsonpint
Copy link
Contributor Author

This was originally reported as moment/moment-timezone#327.

#2749 is related, but slightly different.

@maggiepint
Copy link
Member

As opposed to computing the exact length of time of the day, can't you just do:

moment('2016-10-16').startOf('day').add(1, 'day').startOf('day').subtract(1, 'millisecond').format()
"2016-10-16T23:59:59-02:00"

Works fine on the 15th when pushed into the 16th as well:

moment('2016-10-15').startOf('day').add(1, 'day').startOf('day').subtract(1, 'millisecond').format()
"2016-10-15T23:59:59-03:00"

Is there an edge case that I"m not thinking of?

I suppose there might be a performance benefit to doing it differently, but heck, you can read it!

@mattjohnsonpint
Copy link
Contributor Author

Hmmmm, something else screwy here:

moment.tz("2016-10-16","America/Sao_Paulo").startOf('day').add(1,'day').startOf('day').subtract(1,'ms').format()
// "2016-10-16T23:59:59-02:00"  (ok)

moment.tz("2016-10-15","America/Sao_Paulo").startOf('day').add(1,'day').startOf('day').subtract(1,'ms').format()
// "2016-10-14T23:59:59-03:00"  (wrong date)

moment.tz("2016-10-15","America/Sao_Paulo").startOf('day').add(1,'day').format()
// "2016-10-15T23:00:00-03:00"  should have skipped forward to "2016-10-16T01:00:00-02:00"

Are we just dealing with the whole ambiguous browser behavior here? Or is there something more to it?

@maggiepint
Copy link
Member

maggiepint commented Apr 21, 2016

In chrome with the windows timezone of Brasillia:

moment("2016-10-15").startOf('day').format()
"2016-10-15T00:00:00-03:00"
moment("2016-10-15").startOf('day').add(1, 'day').format()
"2016-10-16T01:00:00-02:00"

In chrome with my timezone using Moment Timezone:

moment.tz("2016-10-15","America/Sao_Paulo").startOf('day').format()
"2016-10-15T00:00:00-03:00"
moment.tz("2016-10-15","America/Sao_Paulo").startOf('day').add(1, 'day').format()
"2016-10-15T23:00:00-03:00"

COOL!

Something up with moment timezone? I don't really know that code so it's hard to say. Doesn't seem like the browser if the first works though.

@maggiepint
Copy link
Member

I think that this is a result of the fact that moment timezone will always push an invalid date back, if it has been 'added' into that date. Whether by design or on accident I am not sure.

//setting moves forward
moment.tz("2016-10-16T00:00:00","America/Sao_Paulo").format()
"2016-10-16T01:00:00-02:00"
//adding moves backward
moment.tz("2016-10-15T00:00:00","America/Sao_Paulo").add(1, 'day').format()
"2016-10-15T23:00:00-03:00"

//setting moves forward
moment.tz("2016-03-13T02:00:00","America/Chicago").format()
"2016-03-13T03:00:00-05:00"
//adding moves backward
moment.tz("2016-03-12T02:00:00","America/Chicago").add(1, 'day').format()
"2016-03-13T01:00:00-06:00"

//one more time for funzies
moment.tz("2016-03-27T01:00:00","Europe/London").format()
"2016-03-27T02:00:00+01:00"

moment.tz("2016-03-26T01:00:00","Europe/London").add(1, 'day').format()
"2016-03-27T00:00:00Z"

I think this has something to do with trying to use the keepTime setting when you can't keep the time. I don't know this code well enough to tell you why the code is the way it is, but I know for sure that if the keepTime call in this code were 0 instead of 1, everything would go forward as expected. It's because keep time is on that it's pushed back. And keep time should be on, it just is funky with this edge case.

    moment.updateOffset = function (mom, keepTime) {
        var zone = moment.defaultZone,
            offset;

        if (mom._z === undefined) {
            if (zone && needsOffset(mom) && !mom._isUTC) {
                mom._d = moment.utc(mom._a)._d;
                mom.utc().add(zone.parse(mom), 'minutes');
            }
            mom._z = zone;
        }
        if (mom._z) {
            offset = mom._z.offset(mom);
            if (Math.abs(offset) < 16) {
                offset = offset / 60;
            }
            if (mom.utcOffset !== undefined) {
                mom.utcOffset(-offset, keepTime);
            } else {
                mom.zone(offset, keepTime);
            }
        }
    };

@icambron
Copy link
Member

icambron commented May 5, 2016

I don't know enough about moment-timezone to be helpful here, but perhaps the ticket on why keepTime is used will be helpful to anyone investigating: moment/moment-timezone#28

@ichernev
Copy link
Contributor

#1564

This is the last big change in handling zones in moment. Not sure if its related to this issue, but it should help.

@maggiepint can you explain a bit the

will always push an invalid date back

part. I mean -- do we have an example that exposes a problem with moment-timezone, or we just need some smart code in moment to detect DST at the start/end of the day and handle it appropriately. The second is medium-to-easy. If we have to refactor again the zone passing between the moment-tz and moment, then its hell -- I'd rather wait for 3.0 and hope the new interface won't have this issue.

@maggiepint
Copy link
Member

@ichernev the way the code stands today, we have two issues in this one issue. One is the issue that @mj1856 originally raised, which is that days that don't start at midnight end up with a funky end of day value. This is a moment issue.

The other is that moment timezone, when presented with an invalid date, goes forward if that invalid date is used in the constructor, but it goes backward if it is 'added' into that date. This is, as it stands, an issue with moment timezone and not moment. I'm not sure how the timezone interface changes that - I would have to think about it a bit.

@ichernev
Copy link
Contributor

@maggiepint thank you for the clear explanation.

So to fix the moment problem : endOf('day') being inaccurate -- just "aim" for the end of the day, go there, if its 00:00, finish, otherwise aim again, if the second aim reaches 00:00 we're done otherwise use the first shot. The second aim will help if we jump across DST and are "distorted" by an hour. If the end of day is an invalid time because of DST, the second aim won't hurt.

We should use this algo for all endOf, maybe even startOf, but that is harder to reason with the set-to-zero we currently do.

@MarcosEich
Copy link

Hi guys, I am also being affected by this issue on the current version of moment (2.14.1).

@keithwillcode
Copy link

I have #3716 submitted to fix this, but am currently seeing issues with Travis CI for transpile reasons. Will be looking into it more soon.

@marwahaha marwahaha added the DST label Mar 22, 2017
kkopanidis added a commit to kkopanidis/moment that referenced this issue May 9, 2017
kkopanidis added a commit to kkopanidis/moment that referenced this issue May 9, 2017
kkopanidis added a commit to kkopanidis/moment that referenced this issue May 9, 2017
G1NA added a commit to kkopanidis/moment that referenced this issue May 23, 2017
G1NA added a commit to kkopanidis/moment that referenced this issue May 23, 2017
@m0nzderr
Copy link

m0nzderr commented Jun 28, 2017

This one is also related (default Brazilian locale).

Operation results in the same date:

moment("2017-10-16T00:59:59.999").subtract(1,'day').endOf('day')
=> "2017-10-16T00:59:59.999"

@Seldaek
Copy link
Contributor

Seldaek commented Sep 8, 2017

Hey - I just submitted #4164 which fixes this for startOf as well as endOf. Would love to get feedback there so we can hopefully finally move this forward.

@darakeon
Copy link

@mj1856 , I closed my PR because the problem with endOf in DST edge seems to be fixed. Could this be fixed too?

@lourenci
Copy link

lourenci commented Mar 20, 2018

I have tested the #4164 and this looks like be fixed on it. Waiting for merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.