Birthday maths
Have you ever wondered how old you are…in milliseconds? Or exactly how many weeks there are between two moments in time? Don't worry, in this post you can get the answer to all your "How many [unit] is it between [moment] and [moment]?" questions!
TL;DR
- Milliseconds, seconds, minutes, hours, days and weeks between two moments are trivial to compute — just divide the millisecond difference by the right constant.
- Months and years are trickier because their length varies. The trick is to count full periods forward from the start date and add the fraction of the next period we've reached — the same anchor-based model the Temporal API uses.
- If you just want the answer, call
Temporal.Duration.prototype.totaldirectly instead of writing the math yourself.
Try the model here:
For example your birthdayFor example today's date
31.841324544077
Months382.102781952932
Weeks1661.440494084
Days11630.083458588
Hours279122.003006
Minutes16747320.180367
Seconds1004839210.822
Milliseconds1004839210822
Usage
Pick a start date and an end date above and get answers to everything you have ever wondered about! Before you change anything, the start date is my birthday and the end date is today's date. As you can see, the values keep updating, and they will as long as the end date is today's date. If you pick a different end date, the updates stop.
When you pick the start date you can also enter a time of day by ticking "Include time". That way you can specify your birthday with a precise time of day to find out exactly how many minutes old you are. When "Include time" is unticked, the time defaults to 00:00:00.000, and when it's ticked, hours and minutes are taken from the picker while seconds and milliseconds default to 0. The seconds and milliseconds in the result are especially interesting because they tick in real time while the end date is today's date — you can really see time pass.
The Date object
In JavaScript, the built-in Date object holds a lot of useful information and is therefore a great fit for this kind of calculation. With new Date() a new Date object is created with information about the current millisecond, which can be extracted with a number of functions belonging to the Date object. Here are a few examples for the date 2020-10-12:
const now = new Date()
const currentYear = now.getFullYear()
// currentYear = 2020
const currentMonth = now.getMonth()
// currentMonth = 9
const currentDate = now.getDate()
// currentDate = 12
const currentTime = now.getTime()
// currentTime = 1602486314645
Two of these that might look strange are getMonth and getTime. Even though it is 2020-10-12, currentMonth is 9. That's because months are counted from 0. So January is 0, all the way to December which is month 11. You might assume getTime returns a time of day like "09:05", but instead it returns something as odd as the number of milliseconds since midnight 1970-01-01. That is JavaScript's way of expressing an exact moment in time. Any time before 1970-01-01 is therefore represented as a negative number when using getTime.
The calculations
The trivial part: every unit with a fixed length can be computed by dividing the millisecond difference by the right constant.
const MS_PER_SECOND = 1000
const MS_PER_MINUTE = MS_PER_SECOND * 60
const MS_PER_HOUR = MS_PER_MINUTE * 60
const MS_PER_DAY = MS_PER_HOUR * 24
const MS_PER_WEEK = MS_PER_DAY * 7
function getMillisecondsDiff(startDate, endDate) {
return endDate.getTime() - startDate.getTime()
}
function getSecondsDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_SECOND
}
function getMinutesDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_MINUTE
}
function getHoursDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_HOUR
}
function getDaysDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_DAY
}
function getWeeksDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_WEEK
}
All these functions follow the same pattern — just swap the constant in the denominator. Thanks to the millisecond resolution we get "live" decimals that tick upwards in real time for free.
Computing exactly how many months or years it is between two Date objects is going to turn out to be a lot harder.
Computing months between moments
Before we get into computing in code, we have to consider how one month is defined. Since the length of a month varies — unlike days per week or minutes per hour — it isn't obvious how many months 10 days is. Should the average length of a month be used? Probably not — both because it's awkward to compute given the complexity around leap years, and because it would lead to oddities like February 1–15 being counted as less than a month even though more than half of the current month has passed.
Instead, we'll use an anchor-based model — the same one the Temporal API uses:
- Count how many whole months fit when we step forward from the start date, month by month.
- Place an anchor at start + that many months.
- The fractional part is how far we've moved from the anchor toward the next anchor (one more month), divided by the length of that period.
Let me explain with a few examples.
Example 1Start date: 2018-01-01 at 00:00
End date: 2020-02-01 at 00:00
All of 2018 and 2019 give 24 months, and January 2020 gives one more — that's 25 whole months. The end date lands exactly on the next anchor (2020-02-01), so the fraction is 0. Total: 25 months.
Example 2Start date: 2018-04-01 at 00:00
End date: 2020-02-01 at 00:00
From April 2018, 22 whole months fit up to February 2020. Once again, the end date lands exactly on the anchor, so the fraction is 0. Total: 22 months.
Example 3Start date: 2018-04-24 at 00:00
End date: 2020-02-06 at 00:00
From 2018-04-24 we can step forward month by month: 2018-05-24, 2018-06-24, …, all the way to 2020-01-24. The next anchor would be 2020-02-24, but that's after the end date, so we stop there. That gives us 21 whole months, with the anchor on 2020-01-24.
The fractional part is how far we've moved from the anchor (2020-01-24) toward the next anchor (2020-02-24). The end date 2020-02-06 is 13 days after 2020-01-24, and the full period 2020-01-24 → 2020-02-24 is 31 days. So 13/31 ≈ 0.4193548 of a month. Total: 21.4193548 months.
Adding months is trickier than it sounds
To compute the anchor we need a function that adds n months to a date. Trickier than it sounds, because months have different lengths:
- 2018-01-31 + 1 month = ? February doesn't have 31 days. The standard answer is to clamp to the last day of the target month: 2018-02-28.
- 2020-01-31 + 1 month = 2020-02-29 (leap year!).
- 2020-02-29 + 12 months = 2021-02-28 (clamping again).
JavaScript's built-in setMonth also has a sneaky quirk: new Date(2018, 0, 31) followed by setMonth(1) gives 2018-03-03, not 2018-02-28, because JavaScript "rolls over" the days instead of clamping. We can work around it by first setting the day to 1, then changing the month, and finally setting the day again with clamping:
function addMonths(date, n) {
const result = new Date(date)
result.setDate(1) // Avoid day-overflow when changing month
result.setMonth(date.getMonth() + n)
const daysInResultMonth = new Date(
result.getFullYear(),
result.getMonth() + 1,
0,
).getDate()
result.setDate(Math.min(date.getDate(), daysInResultMonth))
return result
}
The second-to-last line uses the day-0 trick: new Date(year, month + 1, 0) looks like "day 0 of next month", but JavaScript normalizes it to the last day of the current month. A neat way to get the days in a month without thinking about leap years yourself.
The model in code
With addMonths in place, getMonthsDiff looks like this:
function getMonthsDiff(startDate, endDate) {
let months =
(endDate.getFullYear() - startDate.getFullYear()) * 12 +
(endDate.getMonth() - startDate.getMonth())
if (addMonths(startDate, months) > endDate) months -= 1
const anchor = addMonths(startDate, months)
const nextAnchor = addMonths(startDate, months + 1)
const fractional =
(endDate.getTime() - anchor.getTime()) /
(nextAnchor.getTime() - anchor.getTime())
return months + fractional
}
The first expression computes the difference in calendar months between end and start, which is an upper bound on the number of whole months. If we've overshot — i.e., if the anchor at that many months lands after the end date (as in Example 3, where 2020-02-24 is after 2020-02-06) — we step back by one. Then we divide the distance from the anchor to the end date by the length of the next full month period.
We can now test getMonthsDiff with Example 3:
const startDate = new Date("2018-04-24 00:00")
const endDate = new Date("2020-02-06 00:00")
const monthsDiff = getMonthsDiff(startDate, endDate)
// monthsDiff = 21.419354838709676
That matches the sanity check above (21 + 13/31).
Computing years between moments
Years use the exact same anchor model — they're really just twelve months at a time:
function getYearsDiff(startDate, endDate) {
let years = endDate.getFullYear() - startDate.getFullYear()
if (addMonths(startDate, years * 12) > endDate) years -= 1
const anchor = addMonths(startDate, years * 12)
const nextAnchor = addMonths(startDate, (years + 1) * 12)
const fractional =
(endDate.getTime() - anchor.getTime()) /
(nextAnchor.getTime() - anchor.getTime())
return years + fractional
}
Testing with a simple case gives what we'd expect:
const startDate = new Date("2018-01-01")
const endDate = new Date("2020-01-01")
const yearsDiff = getYearsDiff(startDate, endDate)
// yearsDiff = 2
And with something more complicated:
const startDate = new Date("2018-05-03")
const endDate = new Date("2020-03-22")
const yearsDiff = getYearsDiff(startDate, endDate)
// yearsDiff = 1.8852459016393444
Sanity check: 1 whole year fits from 2018-05-03 to 2019-05-03. The next anchor (2020-05-03) lands after the end date, so we stop. The fraction is (2020-03-22 − 2019-05-03) / (2020-05-03 − 2019-05-03) = 324 / 366 = 0.8852459. Total: 1.8852459 years.
Putting it all together
The full implementation looks like this:
const MS_PER_SECOND = 1000
const MS_PER_MINUTE = MS_PER_SECOND * 60
const MS_PER_HOUR = MS_PER_MINUTE * 60
const MS_PER_DAY = MS_PER_HOUR * 24
const MS_PER_WEEK = MS_PER_DAY * 7
function getMillisecondsDiff(startDate, endDate) {
return endDate.getTime() - startDate.getTime()
}
function getSecondsDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_SECOND
}
function getMinutesDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_MINUTE
}
function getHoursDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_HOUR
}
function getDaysDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_DAY
}
function getWeeksDiff(startDate, endDate) {
return getMillisecondsDiff(startDate, endDate) / MS_PER_WEEK
}
function addMonths(date, n) {
const result = new Date(date)
result.setDate(1)
result.setMonth(date.getMonth() + n)
const daysInResultMonth = new Date(
result.getFullYear(),
result.getMonth() + 1,
0,
).getDate()
result.setDate(Math.min(date.getDate(), daysInResultMonth))
return result
}
function getMonthsDiff(startDate, endDate) {
let months =
(endDate.getFullYear() - startDate.getFullYear()) * 12 +
(endDate.getMonth() - startDate.getMonth())
if (addMonths(startDate, months) > endDate) months -= 1
const anchor = addMonths(startDate, months)
const nextAnchor = addMonths(startDate, months + 1)
const fractional =
(endDate.getTime() - anchor.getTime()) /
(nextAnchor.getTime() - anchor.getTime())
return months + fractional
}
function getYearsDiff(startDate, endDate) {
let years = endDate.getFullYear() - startDate.getFullYear()
if (addMonths(startDate, years * 12) > endDate) years -= 1
const anchor = addMonths(startDate, years * 12)
const nextAnchor = addMonths(startDate, (years + 1) * 12)
const fractional =
(endDate.getTime() - anchor.getTime()) /
(nextAnchor.getTime() - anchor.getTime())
return years + fractional
}
Better alternatives
The model above is essentially a hand-rolled version of Temporal's Duration.prototype.total. In new code I'd just use the Temporal API directly:
const start = Temporal.PlainDateTime.from("1994-08-06T10:00")
const end = Temporal.Now.plainDateTimeISO()
// Decimals in a specific unit
const years = end.since(start).total({ unit: "year", relativeTo: start })
const months = end.since(start).total({ unit: "month", relativeTo: start })
const weeks = end.since(start).total({ unit: "week" })
// Or as an integer breakdown: { years, months, days, hours, ... }
const duration = end.since(start, { largestUnit: "year" })
Temporal is a recent addition, so support still varies across environments. Where it isn't available, the @js-temporal/polyfill polyfill works.
An established library like date-fns also has functions like differenceInYears, differenceInMonths and differenceInWeeks, but they only return integers — for decimals you'd have to combine multiple calls or compute on milliseconds yourself.
So the manual implementation in this article is essentially obsolete in practice — but hopefully a useful exercise in understanding why computing the months or years between two moments isn't always trivial.
That's it! I can brag about being 1661.440494112 weeks old, and you can see for yourself how old you are — exactly.
References
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration
- https://date-fns.org/
