Calendar and money rules
What is one month after January 31st? Is a 12-month lease that starts on March 1st over on March 1st or on the last day of February? Does 2.625 round to 2.62 or 2.63? Documents rarely say, and different systems answer differently. This page describes how Tickmarks answers, for every value a relation computes.
The rules are fixed for each schema version, so the same document and schema always give the
same answer. Where a rule has a setting, such as eom or end, you can change it in the
schema’s defaults or on a single relation.
Durations
Section titled “Durations”Tickmarks stores a duration as a number of months and a number of days, plus a time part for
date-times. When it reads a duration, a year becomes 12 months and a week becomes 7 days, so
P1Y equals P12M. Months and days are never converted into each other, though, because a month
isn’t a fixed number of days: P1M is not P30D.
Adding a duration to a date
Section titled “Adding a duration to a date”Tickmarks adds the months first, then the days. The order matters: 2027-01-30 + P1M2D first adds
one month, which lands on 2027-02-28 because February has no 30th (see below), and then two days,
giving 2027-03-02.
End of month
Section titled “End of month”Adding a month to January 31st lands on a date that doesn’t exist. The eom setting decides what
happens: clamp moves to the last day of the month, and strict gives no result.
clamp (default) |
strict |
|
|---|---|---|
2027-01-31 + P1M |
2027-02-28 |
no result |
2028-01-31 + P1M |
2028-02-29 |
no result |
2028-02-29 + P12M |
2029-02-28 |
no result |
2028-02-29 + P48M |
2032-02-29 |
2032-02-29 |
“No result” means that a document stating that combination contradicts the relation.
Clamping has a side effect when Tickmarks works backwards. Four start dates give 2027-02-28 after
adding P1M under clamp: 2027-01-28, -29, -30 and -31. Under strict only 2027-01-28
does. If the document doesn’t state the start date and several are possible, the field is
underdetermined until other evidence decides.
Exclusive and inclusive end
Section titled “Exclusive and inclusive end”Leases and contracts disagree about what “end date” means. With end: exclusive, the default,
it’s the first day after the period, so endDate == startDate + duration holds exactly. With
inclusive, it’s the last day of the period.
12 months from 2027-03-01 |
End |
|---|---|
exclusive |
2028-03-01 |
inclusive |
2028-02-29 |
Use inclusive when your documents say “until and including”.
Difference between two dates
Section titled “Difference between two dates”endDate - startDate gives a duration, and the basis of the
field it’s compared with decides how that duration is counted. The same two dates can give quite
different answers:
2027-01-31 → 2027-03-15 |
clamp |
strict |
|---|---|---|
calendar: whole months, then the days left |
1 month 15 days | 43 days |
months: exact whole months only |
no result | no result |
days |
43 days | 43 days |
2027-01-31 → 2027-02-28 |
clamp |
strict |
|---|---|---|
calendar |
1 month | 28 days |
months |
1 month | no result |
days |
28 days | 28 days |
A months difference is never rounded, so if the dates aren’t a whole number of months apart,
there is no result. If the end is before the start, both parts of a calendar difference are
negative, counted from the start date.
Comparing durations
Section titled “Comparing durations”Durations of different bases can’t be compared (E-BASIS). Two calendar durations are compared
only when the answer is the same whatever date you start from. P1M3D > P1M works, but P1M
against P30D is refused, since a month is 28 to 31 days, and so is P2M against P1M5D.
Date ranges and date-times
Section titled “Date ranges and date-times”Dates run from 0001-01-01 to 9999-12-31. A result outside that range is an error, and that
includes the intermediate date after adding the months.
You can only add whole days to a date. A date-time takes exact durations only (hours, minutes,
seconds) and keeps its UTC offset: 2027-02-28T23:00:00+01:00 + PT2H is
2027-03-01T01:00:00+01:00. Named time zones and daylight saving aren’t supported, so the offset
stays the same even when a period crosses a clock change.
Numbers
Section titled “Numbers”Arithmetic in Tickmarks is exact, with no floating point anywhere, so 0.1 + 0.2 == 0.3 is true.
Integers are 64-bit, and an overflow is an error rather than a silent wrap. Decimals hold up to
28 digits after the point, and a result that needs more is an error too.
Because arithmetic is exact, equality has no tolerance either. If a document shows rounded figures, round explicitly in the relation.
Solving backwards
Section titled “Solving backwards”Given total == net + vat and a known total and net, Tickmarks derives vat by subtraction.
It only accepts such an inverse if putting the result back into the relation reproduces the known
value exactly:
| Relation and known values | Derived |
|---|---|
total == net + vat, total 150.00, net 100.00 |
vat 50.00 |
12.10 == amount * 1.21 |
amount 10 |
10.00 == amount * 4 |
amount 2.5 |
10.00 == amount * 3 |
nothing: 10 / 3 is not exact |
Rounding
Section titled “Rounding”The default rounding mode, half_even, sends a tie to the even digit, which surprises people
who expect ties to round up. Both of these round to 2 places:
half_even (default) |
half_up |
|
|---|---|---|
12.50 × 21 / 100 = 2.625 |
2.62 |
2.63 |
1.005 |
1.00 |
1.01 |
1.005 is a tie that half_even rounds down to 1.00. Many invoicing systems round half up
instead, and if yours do, say so in the relation: round(net * rate / 100, 2, half_up).
A money field’s currency is its own currency if it sets one, otherwise defaults.currency,
otherwise EUR. An amount in the document without a currency marker is read in the field’s
currency, while an amount with a different marker ($1,200.00 on a euro field) fails to
normalize. Currencies never mix and are never converted:
100.00 EUR + 50.00 USD is a compile error.
A value fits a field’s scale when it has no more digits after the point, ignoring trailing zeros.
With scale 1, 1.50 fits and 1.55 doesn’t.
Duration display
Section titled “Duration display”A duration read from the document keeps the units it was written in, so “1 year” shows as P1Y
and “2 weeks” as P2W. A duration Tickmarks computes is shown in months and days (P12M, P14D).
That’s only display: P1Y == P12M is still true.