Created
November 3, 2020 06:16
-
-
Save turlockmike/b49792a0235854fcbcf229e29468ec0f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function addMonths(date: Date, months: number): Date { | |
const newDate = new Date(date) | |
const dayOfMonth = newDate.getDate() | |
// The JS Date object supports date math by accepting out-of-bounds values for | |
// month, day, etc. For example, new Date(2020, 1, 0) returns 31 Dec 2019 and | |
// new Date(2020, 13, 1) returns 1 Feb 2021. This is *almost* the behavior we | |
// want except that dates will wrap around the end of a month, meaning that | |
// new Date(2020, 13, 31) will return 3 Mar 2021 not 28 Feb 2021 as desired. So | |
// we'll default to the end of the desired month by adding 1 to the desired | |
// month and using a date of 0 to back up one day to the end of the desired | |
// month. | |
const endOfDesiredMonth = new Date(newDate.getTime()) | |
const monthsPlusOne = Math.abs(months + 1) * (Math.sign(months) !== 0 ? Math.sign(months) : 1) | |
endOfDesiredMonth.setMonth(newDate.getMonth() + monthsPlusOne, 0) | |
const daysInMonth = endOfDesiredMonth.getDate() | |
if (dayOfMonth >= daysInMonth) { | |
// If we're already at the end of the month, then this is the correct date | |
// and we're done. | |
// if we're going back over daylight savings, we find the time-zone difference | |
// and adjust, so that we don't end up being one day off | |
const timezoneDiff = date.getTimezoneOffset() - endOfDesiredMonth.getTimezoneOffset() | |
endOfDesiredMonth.setMinutes(endOfDesiredMonth.getMinutes() + timezoneDiff) | |
return endOfDesiredMonth | |
} else { | |
// Otherwise, we now know that setting the original day-of-month value won't | |
// cause an overflow, so set the desired day-of-month. Note that we can't | |
// just set the date of `endOfDesiredMonth` because that object may have had | |
// its time changed in the unusual case where where a DST transition was on | |
// the last day of the month and its local time was in the hour skipped or | |
// repeated next to a DST transition. So we use `date` instead which is | |
// guaranteed to still have the original time. | |
newDate.setFullYear( | |
endOfDesiredMonth.getFullYear(), | |
endOfDesiredMonth.getMonth(), | |
dayOfMonth | |
) | |
// if we're going back over daylight savings, we find the time-zone difference | |
// and adjust, so that we don't end up being one day off | |
const timezoneDiff = date.getTimezoneOffset() - newDate.getTimezoneOffset() | |
newDate.setMinutes(newDate.getMinutes() + timezoneDiff) | |
return newDate | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment