Created
April 21, 2017 09:31
-
-
Save aaugustin/eaeb44d9c4865e503481f985ae0a3bee to your computer and use it in GitHub Desktop.
Date arithmetic in JavaScript
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
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 | |
} |
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
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