-
Notifications
You must be signed in to change notification settings - Fork 0
Civil and Exact Arithmetic
Two families of arithmetic that look alike and are not interchangeable. Picking
the wrong one is the most common date bug there is, and the reason this library
gives them different names instead of one add() with a unit argument.
Civil arithmetic moves the calendar and preserves wall-clock time. If it was 09:30 before, it is 09:30 after, whatever the clocks did in between.
Exact arithmetic moves the instant. Add 86,400,000 milliseconds and you get a moment exactly one day of elapsed time later, which on two days a year is not the same time of day.
| Civil | Exact |
|---|---|
plus_days, minus_days
|
plus_ms, minus_ms
|
plus_weeks, minus_weeks
|
plus_seconds, minus_seconds
|
plus_months, minus_months
|
plus_minutes, minus_minutes
|
plus_years, minus_years
|
plus_hours, minus_hours
|
plus_period, minus_period
|
The same split runs through the amount types. Period holds years, months and
days and cannot be converted to milliseconds without a reference date, because
months and years have no fixed length. An exact amount is just an int of
milliseconds.
On the Sunday a daylight-saving transition happens, the civil day is 23 or 25
hours long. plus_days(1) from Saturday 09:30 gives Sunday 09:30. Adding
MS_DAY gives Sunday 08:30 or 10:30, depending on the direction.
Both answers are correct answers to different questions. "Same time tomorrow" is civil. "Twenty-four hours from now" is exact. A library that offers only one of them forces you to write the other by hand, and that is where the hour goes missing.
31 January plus one month is a date that does not exist. There are only two
defensible things to do about it, and the library makes you pick with the
Overflow enum:
-
Overflow.CONSTRAINclamps to the last valid day, so you get 28 or 29 February. This is java.time's behaviour and the default here. -
Overflow.REJECTreturnsnarather than inventing a date.
d = t.new_datetime(2025, 1, 31)
d.plus_months(1) // 2025-02-28
d.plus_months(1, t.Overflow.REJECT) // na
A day below 1 is na under both, because that is not an overflow at all and
there is nothing sensible to clamp it to.
d = t.new_datetime(2025, 1, 31)
d.plus_months(1).plus_months(1) // 2025-03-28, via the 28 February clamp
d.plus_months(2) // 2025-03-31
Both are right. Clamping loses information, so applying it twice loses it twice. If you need the second answer, ask for two months in one call. This is the same behaviour java.time has, for the same reason.
days_between and months_between count whole units. months_between follows
ChronoUnit.MONTHS.between: a partial month does not count.
period_between returns a Period, decomposing the gap into years, months and
days, with every component carrying the same sign.
until(other, unit) is the generic form, so the unit can be chosen at runtime.
One note on period_between. It follows java.time's Period.between
definition, but it is not bug-compatible with java.time's implementation: over
1970 to 2200 the two disagree on 18 date pairs out of 4,550, and in all 18 cases
java.time fails its own documented round-trip promise. The library matches the
specification rather than the reference implementation, and the round-trip
a.plus_period(a.period_between(b)) == b holds.
start_of(unit), end_of(unit) and round_to(unit, mode) snap a DateTime to
a unit boundary. RoundMode follows TC39 Temporal, applied to elapsed time
since the epoch:
| Mode | Direction |
|---|---|
FLOOR |
Toward the earlier boundary |
CEIL |
Toward the later boundary |
TRUNC |
Toward the epoch, so FLOOR after 1970 and CEIL before it |
HALF_EXPAND |
Nearest boundary, exact ties away from the epoch |
TimeUnit.WEEK takes a week_start parameter rather than assuming one, because
the answer genuinely differs and there is no correct default across markets.
Previous: Core Concepts · Next: Value Semantics · Reference: API-DateTime, API-Period
std_time v1 · API Index · Task Index · Scope and Limitations · Verification
Calendar data current to the horizons on Versioning and Data Currency. Shanghai, Bombay and Singapore answer exactly through 2026.
MPL-2.0 · Copyright (c) 2026 Jesse Sanford · published on TradingView as The_Peaceful_Lizard
Start here
The model
- Core Concepts
- Civil and Exact Arithmetic
- Value Semantics
- Error Model
- Time Zones
- Exchange Calendars
- Trading Days and Day Counts
- Expiries
- Sessions
- Formatting and Parsing
- Choosing the Right Tool
- Pitfalls
Recipes
Reference
- API Index · Task Index
- DateTime
- Session
- Zone
- Exchange
- Period · Interval
- Weekday · Enums
- Free functions
- Glossary
The fine print