1970-01-01 00:00:00 — the Unix epoch — to that instant. The date functions read that number as a point on the calendar.
This is the spreadsheet model, where a date is a serial number and any number can be read as a date. What TrueMath adds is that the serial is a time value rather than a bare count, so a date combines with durations under the ordinary unit rules: closing_date + 30 d is a date because 30 d is a quantity of time.
What follows from the model
- Midnight on the epoch is
0, one day later is86400, and every instant before the epoch is negative. Dates before 1970 need no special handling. - No hidden state. A date carries no flags — nothing marks it as “has a time” or “is a month end.” Two dates are equal exactly when their values are equal, and a function such as
adjdateworks only from the value it is given. - Wall clock, no zones. A date is a plain wall-clock reading — the components are the value. Nothing in a calculation shifts a date between time zones or applies daylight-saving rules.
- Nothing in the math reads a clock. Date functions are pure: the same arguments always give the same result, which is part of what makes a result reproducible. A clock enters through the values a calculation is given — a value or default written as
todayornowbecomes a number before the calculation runs, and two of them in the same calculation mean the same moment.todayis midnight of the current day andnowcarries the time of day as well, read in the time zone the request supplies — the browser’s in the Playground, thetime_zoneparameter over the API — and otherwise in the account’s time-zone setting. See Time zones. Only that number is used in the calculation. - Sub-second precision is held in the fractional part:
0.5is half a second. The smallest named time unit is the millisecond (ms), and there is no millisecond accessor — read it from the fraction ofsecond(d)withfpart. - Text is read on the way in, not in the math. A date you supply can be written the way people write dates —
7/21/2026,today— and it becomes a number before the calculation runs. Inside an equation there is no parsing:date()builds a date from numeric components, so an equation never reads"7/21/2026".
Supported range and calendar rules
- Supported range:
1900-01-01 00:00:00through2200-12-31 23:59:59.999…— every instant of 1900 through every instant of 2200, since the limit is on the year.date()rejects a year outside it withinput.out_of_range. A value that arithmetic carries past either end is not itself an error — it is still just a number — but reading it with a date function returnsmath.out_of_range. - Gregorian calendar throughout. A leap year is divisible by 4, except century years, except century years divisible by 400 —
2000is a leap year and1900is not. - A day that does not exist is rejected, not rolled over: there is no
2026-02-29. - Every day is exactly 86,400 seconds. Leap seconds are not modeled, matching Unix and spreadsheets, which keeps the conversion between a serial and its calendar components pure arithmetic.
Arithmetic
Because a date is a number carrying a time unit, date arithmetic is ordinary unit arithmetic.
A duration is any value carrying a time unit:
ms, s, min, hr, d, wk, mo, or yr. The fixed-length units — ms through wk — are exact, so date + 1 wk always lands on the same wall-clock time seven days later. A date - date difference comes back in seconds, the base unit of time; cast it to report the span in another unit.
Durations
The difference between two dates is a duration, and a duration is an ordinary unit number. Durations add and subtract, scale by a plain number, and convert on cast:mod:
mo and yr shift by an average, not a calendar step
The averages are deliberate, and they are what makes duration math exact: 12 mo is precisely 1 yr, 18 mo precisely 1.5 yr, and a rate of 1200 USD/yr precisely 100 USD/mo. A conversion between time units always round-trips. But that consistency is the opposite of what a calendar does, where a month is 28 to 31 days and a year is 365 or 366.
Used to step a date, an average misses in one of three ways:
- The day slips. Thirty and a bit days from the 15th is the 14th of the next month, at 10:30 in the morning.
- A month can be skipped entirely. January 31 plus
1 molands in March, stepping straight over February. - And the miss can be invisible. A year later is the right calendar day — and six hours past midnight, because a quarter of a day is left over. Shown as a date alone it reads
2027-07-21and looks correct, while the value is not equal todate(2027; 7; 21). Comparisons against it fail, day boundaries fall in the wrong place, and nothing raises an error.
adjdate exists: it is the calendar-correct form of all three, and it preserves the time of day it started with.
yr stands in for a calendar year rather than an increment. The average year is 365.25 days, which is the length of no particular year, so measuring against it drifts by a quarter or a half day depending on the year’s position in the leap cycle. To count days within a year, count from the start of that year:
floor gives 1 January of that year and ddays counts actual days to the date, so the result is right in every year, leap or not.
The rule of thumb: mo and yr are for durations — spans, rates, terms — and never for moving a date, and never as a stand-in for a calendar year. See Time units and dates.
Measuring a span in months or years
The averages apply in reverse too. Adate - date difference is itself exact, since it is a count of seconds, but expressing that difference in mo or yr divides by an average — so a one-month span does not come back as 1:
hr, d, wk — where the figure is exact, or count days with ddays, whose basis argument covers the 30/360 conventions finance uses for month fractions. For a count of whole calendar months, read the components:
A bare number is not a duration
Adding or subtracting a plain scalar is the one operation a date rejects:date(2026; 7; 21) + 30 returns input.incompatible_units. Write date(2026; 7; 21) + 30 d and state the unit you mean.
This is the ordinary unit rule — 5 m + 3 is rejected for the same reason — but it is worth calling out, because a spreadsheet would have taken the bare 30 as 30 days. Here the serial counts seconds, so a bare 30 could only mean half a minute. Requiring the unit removes the ambiguity rather than guessing at it.
Beyond that guard, a date behaves like any other number. Operations with no natural calendar meaning are not blocked — dividing a date by 2, or adding two dates together, produces a number, exactly as it would in a spreadsheet. Whether the result means anything is yours to decide.
Comparison
Dates compare like numbers, and the comparison is over the whole instant. A date at midnight is not equal to the same calendar day at nine-thirty:Writing dates in equations
The examples above are written in the reference style, with a bare unit suffix. Inside an activity equation a unit literal is wrapped in backticks so it reads as a unit rather than a variable name —closing_date + 30`d`. See Units in equations.
A date variable holds an ordinary numeric value, so it takes a default and a display format like any other variable. Giving a variable the date, datetime, time, or duration display type is what makes its number render as a calendar value, and how its format and default are set is covered in Authoring dates and durations. For how a date is written on input, see Dates, times, and durations. See Combining types for how time values combine with the other types.
