Skip to main content
These functions build and read dates. A date is a time value counted in seconds from the Unix epoch, so shifting a date — adding a duration, subtracting two dates — is ordinary unit arithmetic, with no function involved. What the functions here provide is the calendar: constructing a date from components, reading those components back, and the two operations where calendar structure matters, adjdate and ddays. Every date argument below is an ordinary numeric value. The supported range runs from 1900-01-01 00:00:00 through 2200-12-31 23:59:59.999…: building a date outside it returns input.out_of_range, while reading a value that falls outside it — one that arithmetic carried past either end — returns math.out_of_range.

Building a date [date]

A date built from numeric components. Omitted components take the start of their period, so date(2026) is midnight on January 1 and date(2026; 7) is midnight on July 1. date() has no string form — it takes numbers, not text like "7/21/2026". Write text like that as a value you supply: it becomes a number before the calculation runs, so an equation only ever operates on the number. See Dates.
Construction is strict. Every component is range-checked against the calendar, and an out-of-range component returns input.out_of_range rather than rolling over into the neighboring month or year. Each of these is an error: date(2026; 2; 29) because 2026 is not a leap year, date(2026; 13; 1) because there is no thirteenth month, and date(1899; 12; 31) or date(2201; 1; 1) because the year is outside the supported range. To roll a value over on purpose, use adjdate.

Date components

The named component of date, as a bare scalar rather than a unit number — so a component can be used directly in arithmetic and in date(). Passing all six back to date() reconstructs the value it came from.
second is the finest accessor. Recover milliseconds from its fractional part with fpartfpart(second(d)) is the sub-second remainder, in seconds. The accessors read one component at a time. For the whole time of day as a single quantity, take the remainder of the date divided by one day with mod: mod(d; 1 d) is the time elapsed since midnight, and floor(date; "d") is the midnight it counts from. Cast the date to days first — mod(date d; 1 d) — and the same time of day comes back as a fraction of a day, 0.5 d at noon. fpart produces that second figure without mod. Cast the date to the unit you want to split at and take the fractional part: fpart(date d) is the fraction of the day, fpart(date hr) the part-hour. ipart gives the whole part alongside it.

Day of week [weekday]

The day of the week as a number from 1 to 7, where 1 is Sunday and 7 is Saturday — the same numbering as the default WEEKDAY in Excel and Google Sheets.
Those are a Tuesday, a Friday, and a Monday. To test for a weekend, compare against 1 and 7.

Leap year test [isleapyear]

true if date falls in a leap year, false otherwise, under the Gregorian rules: divisible by 4, except century years, except century years divisible by 400.

Calendar adjustment [adjdate]

adjdate is the only calendar-correct way to step a date by months or years. date + 1 mo and date + 1 yr shift by a fixed average — 30.4375 and 365.25 days — which lands on a different day of the month, or on the right day at the wrong time of day. date(2026; 7; 21) + 1 yr is July 21, 2027 at 06:00, and nothing reports an error. See mo and yr shift by an average.
A calendar-correct shift of date by whole days, months, and years. Each amount may be negative, and adjustments roll across month and year boundaries automatically. basis applies a day-count convention to the days amount and defaults to 0, actual days.
The third argument is basis, not months. Adjusting by months or years requires the four-argument form. adjdate(d; 0; 1; 0) is one month later; adjdate(d; 0; 1) shifts by zero days under day-count basis 1, which leaves the date where it was.
Amounts apply from the largest unit down: years, then months, then days.

Month ends

Adjusting by whole months or years keeps the same day number — the 15th stays the 15th. Two more rules apply at month ends, where the day number may not exist in the target month, or where the starting date is the last day of its own month. Both are read from the date, so nothing is carried between adjustments:
  • The last day of a month maps to the last day of the target month. A month-end date stays on month ends as you add months, even as the lengths of those months change.
  • A day that does not exist in the target month clamps back to that month’s last day — the 31st of a 30-day month becomes the 30th, and the 30th of February becomes the 28th or 29th.
Because “last day of the month” is read from the date each time rather than carried along, chaining is predictable. Once a date lands on a month end it keeps landing on month ends:
Note the second line: February 28 is February’s last day in 2026, so a month later is March’s last day, not March 28. The same rules mean a month forward and a month back does not always return the starting date. January 30 clamps to February 28, and February 28 is a month end, so stepping back lands on January 31:
To generate a monthly schedule, put adjdate inside a loop and step the month count rather than the date, so every row is measured from the original date instead of from the previous row:

Day counts [ddays]

The number of days from date1 to date2, as a bare scalar. The count is signed — negative when date2 is the earlier of the two.
basis sets the day-count convention, the same set the finance world uses for accruing interest over a partial period: Only basis 1 changes the day count itself. Bases 0, 2, and 3 all count actual days and return the same number from ddays — they differ in the year length a later step uses when expressing the span as a fraction of a year.
The 30/360 end-of-month adjustments follow the standard financial definitions. ddays reads the calendar date of each argument and ignores the time of day, so any two instants on the same day give the same count. In an activity, ddays also runs the other way: either date can be marked calculable and calculated from the day count and the other date. This works under every basis, in both the two- and three-argument forms. Under basis 1, several dates can give the same count, because the 30/360 adjustments read the 31st as the 30th:
A date calculated from a 30/360 count is therefore a date that satisfies the count, not necessarily the one the count came from — re-deriving a date you already had can return a different one. Where a date has to round-trip exactly, count with the default basis.

Snapping a date to a boundary

floor, ceil, and round take a time unit as a quoted string in place of a decimal place, which snaps a date to that boundary — the start of the day, the start of the month, the nearest hour.
This is also how you compare dates at a coarser granularity than the instant: snap both sides to the same unit, then compare. See Snapping a date to a boundary for the units accepted and what each one zeroes.

Common patterns

The same operations with variable names in place of literal dates, as an activity would use them:
Note the d inside the third line. ddays returns a bare count, so it needs a unit attached before it can be converted: casting a plain number straight to wk relabels it rather than converting it, turning a 14-day span into 14 weeks. The second and third lines also answer slightly different questions. Subtracting is exact to the second, while ddays counts calendar days and ignores the time of day, so the two agree only when both dates sit at the same time of day. Inside an activity equation, wrap the unit literals in backticks — closing_date + 30d“ — so they read as units rather than variable names. See Units in equations.