Skip to main content
A date is not a separate value type in TrueMath. It is a unit number carrying a time unit: the count of seconds from 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 is 86400, 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 adjdate works 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 today or now becomes a number before the calculation runs, and two of them in the same calculation mean the same moment. today is midnight of the current day and now carries the time of day as well, read in the time zone the request supplies — the browser’s in the Playground, the time_zone parameter 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.5 is half a second. The smallest named time unit is the millisecond (ms), and there is no millisecond accessor — read it from the fraction of second(d) with fpart.
  • 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:00 through 2200-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 with input.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 returns math.out_of_range.
  • Gregorian calendar throughout. A leap year is divisible by 4, except century years, except century years divisible by 400 — 2000 is a leap year and 1900 is 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.
The third line is the raw difference — seconds, because that is what both dates are counted in. The three after it are the same span cast to the unit you want to report in, which is how an elapsed span is reported in whichever unit the result should use.

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:
Multiplied by a rate, the time cancels and leaves what you were pricing — the arithmetic behind a timesheet, a carrying cost, or a rental:
To split a duration into whole units and a remainder, cast it and take the integer part, then read the leftover with mod:

mo and yr shift by an average, not a calendar step

Adding 1 mo or 1 yr to a date does not move it to the same day of the next month or year. mo and yr are fixed average durations — 30.4375 days and 365.25 days — so the result lands wherever that many seconds falls. Use adjdate to step a date by calendar months or years.
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 mo lands 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-21 and looks correct, while the value is not equal to date(2027; 7; 21). Comparisons against it fail, day boundaries fall in the wrong place, and nothing raises an error.
The third case is the one to watch. A wrong value that looks right in a result stays wrong in every calculation that consumes it, and no error marks the spot. This is why adjdate exists: it is the calendar-correct form of all three, and it preserves the time of day it started with.
The same trap appears wherever 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. A date - 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:
Report an elapsed span in the fixed units — 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 rule5 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:
To compare at a coarser granularity, snap both sides to the same boundary first — this is the idiom for “same day?” and “same month?” tests:
See Snapping a date to a boundary.

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.