Skip to content

Instantly share code, notes, and snippets.

@aaugustin
Created April 21, 2017 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaugustin/eaeb44d9c4865e503481f985ae0a3bee to your computer and use it in GitHub Desktop.
Save aaugustin/eaeb44d9c4865e503481f985ae0a3bee to your computer and use it in GitHub Desktop.
Date arithmetic in JavaScript
export function addYears(date, numYears) {
// Make a copy to avoid mutating the date argument
date = new Date(date.getTime())
// If adding years to a February 29th ends up in a non-leap year, this
// returns March 1st, which is the expected result.
date.setFullYear(date.getFullYear() + numYears)
// DST can also mess with us. Thanks, JavaScript, for not having dates.
if (date.getHours() === 23) {
date.setHours(24)
} else if (date.getHours() === 1) {
date.setHours(0)
}
return date
}
export function addMonths(date, numMonths) {
// Make a copy to avoid mutating the date argument
date = new Date(date.getTime())
// If adding months to a 29th, 30th, or 31st ends up on a non-existing
// date, JavaScript tries to be smart and returns the 1st, 2nd or 3rd of
// the next month. This is too smart for us. We always want the 1st.
// Track the day number, and if it changes, revert it to 1.
const oldDay = date.getDate()
date.setMonth(date.getMonth() + numMonths)
const newDay = date.getDate()
if (newDay !== oldDay) {
date.setDate(1)
}
// DST can also mess with us. Thanks, JavaScript, for not having dates.
if (date.getHours() === 23) {
date.setHours(24)
} else if (date.getHours() === 1) {
date.setHours(0)
}
return date
}
describe("addMonths", () => {
it("adds months", () => {
expect(addMonths(new Date('2017-04-21 00:00:00'), 5))
.toEqual(new Date('2017-09-21 00:00:00'))
expect(addMonths(new Date('2017-04-21 00:00:00'), 0))
.toEqual(new Date('2017-04-21 00:00:00'))
expect(addMonths(new Date('2017-04-21 00:00:00'), -1))
.toEqual(new Date('2017-03-21 00:00:00'))
})
it("handles varying months length", () => {
expect(addMonths(new Date('2017-01-28 00:00:00'), 1))
.toEqual(new Date('2017-02-28 00:00:00'))
expect(addMonths(new Date('2017-01-29 00:00:00'), 1))
.toEqual(new Date('2017-03-01 00:00:00'))
expect(addMonths(new Date('2017-01-30 00:00:00'), 1))
.toEqual(new Date('2017-03-01 00:00:00'))
expect(addMonths(new Date('2017-01-31 00:00:00'), 1))
.toEqual(new Date('2017-03-01 00:00:00'))
expect(addMonths(new Date('2017-02-01 00:00:00'), 1))
.toEqual(new Date('2017-03-01 00:00:00'))
expect(addMonths(new Date('2017-02-28 00:00:00'), 1))
.toEqual(new Date('2017-03-28 00:00:00'))
expect(addMonths(new Date('2017-03-01 00:00:00'), 1))
.toEqual(new Date('2017-04-01 00:00:00'))
expect(addMonths(new Date('2017-03-30 00:00:00'), 1))
.toEqual(new Date('2017-04-30 00:00:00'))
expect(addMonths(new Date('2017-03-31 00:00:00'), 1))
.toEqual(new Date('2017-05-01 00:00:00'))
expect(addMonths(new Date('2017-08-28 00:00:00'), 6))
.toEqual(new Date('2018-02-28 00:00:00'))
expect(addMonths(new Date('2017-08-29 00:00:00'), 6))
.toEqual(new Date('2018-03-01 00:00:00'))
expect(addMonths(new Date('2017-08-30 00:00:00'), 6))
.toEqual(new Date('2018-03-01 00:00:00'))
expect(addMonths(new Date('2017-08-31 00:00:00'), 6))
.toEqual(new Date('2018-03-01 00:00:00'))
expect(addMonths(new Date('2017-09-01 00:00:00'), 6))
.toEqual(new Date('2018-03-01 00:00:00'))
expect(addMonths(new Date('2017-09-30 00:00:00'), 6))
.toEqual(new Date('2018-03-30 00:00:00'))
expect(addMonths(new Date('2017-10-01 00:00:00'), 6))
.toEqual(new Date('2018-04-01 00:00:00'))
})
it("handles leap years", () => {
expect(addMonths(new Date('2016-01-29 00:00:00'), 1))
.toEqual(new Date('2016-02-29 00:00:00'))
expect(addMonths(new Date('2016-01-30 00:00:00'), 1))
.toEqual(new Date('2016-03-01 00:00:00'))
expect(addMonths(new Date('2016-04-29 00:00:00'), -2))
.toEqual(new Date('2016-02-29 00:00:00'))
expect(addMonths(new Date('2016-04-30 00:00:00'), -2))
.toEqual(new Date('2016-03-01 00:00:00'))
})
it("handles daylight saving time", () => {
// This test only validates what it's supposed to validate
// in the Europe/Paris time zone.
// CET => CEST
expect(addMonths(new Date('2017-03-15 00:00:00'), 1))
.toEqual(new Date('2017-04-15 00:00:00'))
// CEST => CET
expect(addMonths(new Date('2017-10-15 00:00:00'), 1))
.toEqual(new Date('2017-11-15 00:00:00'))
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment