Skip to content

0007. Represent identifiers, money and time explicitly

  • Status: accepted
  • Date: 2026-10-26

Identifiers, amounts and dates are where hospitality software breaks most often: rounding with floating point, a stay computed in the server’s time zone instead of the property’s, an identifier that reveals nothing about what it points to. The analysis fixes the principles (chapters 5, 9 and 18); this ADR fixes the representations.

Identifiers: TypeID strings in the API (rsv_01k8…), with one registered prefix per entity, implemented in core/shared from the specification, with the clock and the random source injected. In PostgreSQL the value is stored as the underlying UUIDv7 in a uuid column: sortable by creation time to the millisecond, compact, and convertible at the edges. Identifiers created in the same millisecond have no defined order (the 74 other bits are random, which RFC 9562 allows), so no feature orders rows by identifier: a stream that needs a strict order, such as the event log or the revision feed, has its own sequence.

Money: an amount is an integer number of minor units plus an ISO 4217 currency code ({ amountMinor: 12050, currency: "EUR" }). The number of decimals comes from the ISO 4217 table, not from locale data, so that JPY has 0 and KWD has 3. Values must be safe integers; parsing and formatting of decimal strings (as Channex sends them) are exact string operations. Floating-point arithmetic on money is forbidden.

Time: the Temporal API, through temporal-polyfill until Node.js and all target browsers ship it natively.

  • A stay night or a business date is a Temporal.PlainDate; a stay is the half-open interval [arrival, departure).
  • An instant is a Temporal.Instant; it is converted to a date only with an explicit time zone, normally the property’s IANA time zone.
  • core never reads the system clock or time zone. A Clock port gives the current instant.
  • No identifier, amount or date is ever an ambiguous string or number inside the domain: types say what a value is.
  • Every conversion lives at a boundary (API serialization, repositories, the Channex client) and has its own tests, including daylight-saving transitions (for example Europe/Rome on 29 March and 25 October 2026) and currencies with 0, 2 and 3 decimals.
  • The polyfill adds about 20 kB where it is used; it goes away when native Temporal is available everywhere.
  • Date and date-fns. Rejected: Date is an instant, not a calendar date, and invites time-zone bugs.
  • bigint for minor units. Rejected for now: JSON cannot carry it and safe integers cover amounts up to about 90 trillion in a two-decimal currency.
  • Plain UUIDs in the API. Rejected: TypeIDs tell humans and logs what an identifier refers to, at no storage cost.