A function that takes a simple struct (containing each component) can also handle the conundrum. The function could of course even take a string representation.
targetEndDate = new Date();
targetEndDate.setFullYear(endDate.getFullYear());
targetEndDate.setMonth(endDate.getMonth());
targetEndDate.setDate(endDate.getDate());
Even if the endDate is valid, this can fail, because today is the 31st, which doesn't exist in endDate's month.If you just did:
targetEndDate = createDate(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
// createDate is an intentional placeholder, I didn't want to double-check the actual syntax
you wouldn't pass through the transitional stage where you have the new month but today's day of the month, which is what causes the bug.In this case, using mutation on the individual fields makes you have to transition through an invalid state to get back to a valid one, and the JS date object does something unexpected (though I think there's nothing good to do here, an exception is probably the best you could do). So mutation really is at issue here. In general, mutation creates room for these kind of counter-intuitive state transitions to arise.
"Hey, are you free for dinner on Wed 23 Aug?"
ISO is great for records and filename, but it's clumsy in conversation:
"Hey, are you free for dinner on 2023-08-20?"
The bug exists every time today's date is greater than the lowest total number of days in a month (28). So it's a bug for 3 days in January, (sometimes 1 day in February,) 3 days in March, 2 days in April, etc.
of those, however, there's more than one date in january which would error, so the total is more than 5. 6 or 7 depending on if it's a leap year
There is no limitation in setMonth() such that you can only set the month to "the month after."
I'm sorry, but I think you misunderstand this bug.
var newDate = new Date(GetDate().Year, GetDate().Month + 1, GetDate().Day);
but with extra logic to ensure that the Month component wrapped around.Extra unnecessary logic. The functionality that causes this bug is so you don't need to handle wraparound yourself:
>> new Date(2023, 12, 1)
Date Mon Jan 01 2024 00:00:00 GMT-0600 (Central Standard Time)
(Note months are 0-indexed so 12 is one past the last month of the year)It works with negative numbers too, to wrap backwards:
>> new Date(2024, -1, 1)
Date Thu Dec 01 2023 00:00:00 GMT-0600 (Central Standard Time) // Important: Important to set these in orderThey just didn't realize they have a different bug now.
It's possible then that after changing the month programmatically, the time component will jump to 23:00 due to timezone DST changes, and the date will jump back one day as well due to this (and in an unlucky case, the month will jump back as well!)
Example bug I had in 2014 when Russian timezone definitions have changed:
https://github.com/yui/yui2/pull/15
A workaround is to initialize dates with fake "midday" time component so that you avoid the day jumping back.
targetEndDate = new Date();
targetEndDate.setFullYear( endDate.getFullYear() );
targetEndDate.setMonth( endDate.getMonth() );
targetEndDate.setDate( endDate.getDate() );
to targetEndDate = new Date();
targetEndDate.setFullYear(
endDate.getFullYear(),
endDate.getMonth(),
endDate.getDate());
That works because setFullYear has a three argument form that takes the year, month, and date avoiding the inconsistency that can arise by setting those one at a time.But you know what else has a three argument for that take the year, month, and date? The Date constructor.
So why not fix it like this?
targetEndDate = new Date(
endDate.getFullYear(),
endDate.getMonth(),
endDate.getDate());
Using the empty constructor would initial the object with the current date and time, but they promptly overwrite those.The Date construction also has a form that takes another Date object, so I wonder if they could have simple used:
targetEndDate = new Date(endDate);In fact I would strongly argue you should never use the JS Date built-ins at all because they are terrible. Use a library like Luxon or date-fns. As a frontend dev, this is the most common category of bugs I've dealt with in my career, and I've spent dozens of hours fixing other people's datetime handling because of how poorly implemented the JS Date API is. It's full of minefields and gotchas.
The Temporal API is supposed to fix some issues, but that's been in development for like a decade now.
Correct API would be to set the components D,M,Y all at once and have them validated on the spot before returning a valid Date value.
I must say I'd prefer getting an exception rather than a silent roll-over any day of the week – pun intended.