Thankfully for the basics we don’t need to consider all that complicated stuff like leap days and leap seconds—Typst’s datetime handles this correctly (hopefully) by being based on a proper calendar library.
Adding 3 months is “hard” exactly because it is not an exact duration like duration(days: 90), but in reverse that means that we only need to handle the number of months “naively”. Here’s one way to offset dates by a number of years and months:
#let date-offset(date, years: 0, months: 0) = {
let date = (year: date.year(), month: date.month(), day: date.day())
// months overflow into years
// months are 1-based, factor that in when dividing
let month = months + date.month
years += calc.div-euclid(month - 1, 12)
date.month = calc.rem-euclid(month - 1, 12) + 1
date.year += years
// conert back into datetime -- won't work if a month is too short
datetime(..date)
}
(I purposefully didn’t add a days parameter since it is ambiguous: if you increase 2026-02-28 by one month and one day, do you get 2026-03-29 (one day after 2026-03-28) or 2026-04-01 (one month after 2026-03-01)? For days and weeks, just use duration)
Here’s a test:
#let d = datetime(year: 2025, month: 10, day: 31)
#d.display()\
// #date-offset(d, months: 1).display() // 2025-11-31 doesn't exist!
#date-offset(d, months: 2).display()\
#date-offset(d, months: 3).display() // year overflows
#date-offset(d, months: -2).display()\ // negative offset
#date-offset(d, months: -9).display()\
#date-offset(d, months: -10).display() // year overflows
#let d = datetime(year: 2024, month: 2, day: 29)
// #date-offset(d, years: 1).display() // 2025-02-29 doesn't exist!
And here’s an implementation for leap year detection:
#let is-leap-year(year) = {
let a = datetime(year: year, month: 2, day: 28)
let b = datetime(year: year, month: 3, day: 1)
b - a == duration(days: 2)
}
Weekdays are built into Typst, and you can determine the length of a month similarly to how I detected a leap year.
@Steve_Moore if your code is significantly more complex than this, then I hope this helps. Otherwise, hopefully others can benefit at least. Handling work days and holidays requires more than Typst built-in types provide, so that code will end up more complex…
Finally—not really related to writing a calendar math package, but maybe interesting for this thread: a while ago, I created a calendar for this thread: Is anyone working on something like the LaTeX tikz Kalender? - #2 by SillyFreak. It demonstrates a bit how working with datetime and duration can look like.