Skip to content

Latest commit

 

History

107 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ntime

Dates, times and time zones for Nitpick — the safety-critical systems language. No dependencies, no libc, no /usr/share/zoneinfo, no C anywhere in the artifact. The time-zone database is compiled in from a pinned IANA release, so the same program gives the same answer on every machine.

Status: cycle 0.2, instants and timestamps, CLOSED on 2026-10-02, archived at meta/roadmap/done/0.2/, as cycle 0.1, the civil calendar, was on 2026-09-26 (meta/roadmap/done/0.1/) — each with its code, its gate and its audit done — and cycle 0.3, the host boundary, is next. What exists: src/core/ — Vec<T>, Bytes and the named limits — since cycle 0.0, its Vec holding only Copy elements since cycle 0.2.0b; and src/cal/ — CivilDate, CivilTime, CivilDateTime, Weekday, Month, the day-number algorithms, the weekday, the day of the year and the ISO week date — whose gate is exhaustive: every day of years −9999 … +9999 goes to its day number and back, both ways, on every full run, and every date of years 1 … 9999 agrees with Python's datetime. Since cycle 0.2.0, src/span/ holds Instant, a reading of one of two clocks that no function converts to a point on the UTC scale, nor the type implicitly, nor the language's cast, and since cycle 0.2.1 Timestamp, such a point, with one spelling per instant. Since cycle 0.2.2 a Timestamp converts to its civil reading in UTC and back, and both directions are checked on every full run, over both sides of every day boundary in the range and every second of 512 days chosen at random. Since cycle 0.2.3 ntime adds the minute, the hour, the day and the week to the prelude's Duration — a day exactly 86 400 seconds, not a calendar day — moves a Timestamp by one, and gives the span between two, refusing one past Duration's ±292 years rather than wrapping it. Zones, formats and the clocks are still placeholders, each replaced by the cycle meta/roadmap/ROADMAP.md names. The specification set is in meta/specs/ and the plan in meta/roadmap/, written the way the compiler's are — specs first, then a cycle map, then execution-grade subcycles, then code — against a compiler that is still moving, pinned by commit (CLAUDE.md names the pin: 5fbaf4a since cycle 0.2.0a, the adoption that opened cycle 0.2).

(Until cycle 0.1.5 this block read "Status: planning. No code yet" — false since cycle 0.0.4 — and "the compiler itself is at cycle 1.5"; between the close's two halves, that cycle 0.1 "is in its close … and the audit that closes it is next"; until cycle 0.2.0b, that cycle 0.2 "is next", which 0.2.0a had opened; until cycle 0.2.1, that timestamps were placeholders too; and until cycle 0.2.2, that an Instant could not be "converted to a wall-clock time" — a civil reading, in meta/specs/GLOSSARY.md's words, where the conversion refused is to a point on the UTC scale, TM-226.)


Why another date library

Because the mistakes date libraries make are the mistakes this language is built to make unspellable, and because the ones it cannot make unspellable are worth stating out loud instead of discovering.

The clocks are different types, so you cannot confuse them. A reading of the monotonic clock and a reading of the system's realtime clock are not the same kind of thing: NTP steps the realtime clock and never the monotonic one, which is why the compiler's own deadline substrate will not use it at all — its runtime calls it a wall clock (D-176). ntime makes that structural — an Instant has no epoch, and the library measures only the distance between two readings of one clock; a Timestamp, what the realtime clock reads, is an absolute point on the UTC scale; and there is no conversion between them — no function, no implicit one, not even the language's unchecked cast. A timeout measured against the realtime clock is a bug you cannot write here by accident: what you can write is a construction, instant_of handed a number computed from a Timestamp, which names in writing the clock it claims to have read. (Until cycle 0.2.4b this said an Instant "cannot be built from a number", and that the bug cannot be written at all — the cycle audit's C2, TM-243; meta/OPEN_QUESTIONS.md Q-7 asks whether instant_of's name should say so.) (Until cycle 0.2.2 this paragraph called the realtime clock's reading "a wall-clock reading" — the words meta/specs/GLOSSARY.md keeps for a civil reading, which a Timestamp is not — and the clock itself "the wall clock" twice, TM-226.) (Until cycle 0.2.1 this said an Instant "yields only differences". Its number is readable — a sealed field — so a program can subtract two clocks' readings by hand, which the library refuses only in instant_since and instant_cmp; and the language's mono_now() hands any program the same number, so hiding it would close nothing — the workbench's question 15, TM-219.)

Wall time and calendar time are also different types. A CivilDateTime — "2026-03-29 02:30" — is not a point in time until you say where. In most of Europe that particular one does not exist, and in October the same wall reading happens twice. ntime will not let you add a duration to a wall-clock reading and pretend the answer is an instant; you convert through a zone, and the zone tells you when the answer is ambiguous or missing rather than picking one quietly.

Exact spans and calendar spans are different types too. A Duration is a number of nanoseconds and adding it is exact. A Period is "one month", which is not a number of nanoseconds and depends entirely on where you start. Libraries that fuse them are the reason "add one month to 31 January" is a famous question. Here the two cannot be added to the same things, and the rules for what a Period does are written down with worked examples rather than implied by an implementation.

The zone database is compiled in and version-pinned. Reading /usr/share/zoneinfo means parsing a binary format from files the program does not control, and it means the same program on two machines disagrees about what time it is. ntime generates its tables from a named IANA release and commits them as Nitpick source — the same choice the sibling TUI library made about terminfo, for the same reasons, and the same one the compiler makes about every generated table. Reading the system database is a post-1.0, opt-in module that a program has to import on purpose.

Overflow traps rather than wrapping. Adding a century to a timestamp that cannot hold one is a controlled stop in this language, not a silent journey to the year 292 billion. The representable range is stated and checked at every constructor, before the trap: a date outside it is refused, not built. (This said, until cycle 0.1.5, that the range would be "enforced by the type system when the compiler's limit<Rules> lands". It landed at the compiler's 1.5.2, and ntime uses it on its containers' lengths — TM-156 — while the calendar's range stays the constructors' to refuse, meta/specs/CALENDAR.md C-8.)

Importing it costs a consumer at most three error identities — and one if all you want is calendar arithmetic. In this language every error identity a library's reachable code can raise is a mandatory arm in every consuming program's shutdown handler ("a library declares" until 2026-09-27: the ecosystem audit's EC3, TM-213 — a declaration nothing raises costs nothing, and a private one that is raised costs the arm), so the number is an API decision, not an implementation detail. ntime's is three. The whole bill is larger, because the language also charges an arm for each kind of trap the imported code can reach — a division, an overflow, an index, a loop's measure, a length's limit — on top of a floor every program owes: measured at compiler c970483, a program importing only the calendar owes 11 arms, and one importing the whole library 13 . The table is meta/specs/SAFETY.md S-4, generated and checked against the compiler on every run — and since the close's second half the two numbers here are held to it too, so a bill that moves turns this page's run red (TM-205). (Until cycle 0.1.5 this paragraph said importing ntime "costs a consumer three failsafe arms", which counted the identities and not the bill.)


What it will provide

The calendar row exists since cycle 0.1; every other row is a later cycle of meta/roadmap/ROADMAP.md.

Layer Contents
calendar CivilDate, CivilTime, CivilDateTime, Weekday, Month, leap years, ISO week dates, ordinal dates — proleptic Gregorian, exact, and exhaustively tested over the whole supported range
instants Instant (monotonic, no epoch) and Timestamp (absolute UTC, seconds and nanoseconds)
spans the prelude's Duration for exact nanosecond spans, and Period for calendar spans, with the conversion rules stated
zones the compiled-in IANA database, offset lookup, DST transitions, and explicit answers for ambiguous and nonexistent local times
formatting RFC 3339, ISO 8601 (date, time, week date, ordinal date), RFC 5322, HTTP-date — as named functions, plus a typed layout for custom formats. There is no format-specifier language
parsing the same formats in reverse, with a stated leniency policy and a round trip that is a fixed point
host the one impure module: clock_gettime for the three clocks, and the system-zone discovery a program has to ask for

Layout

src/          # THE LIBRARY — Nitpick source only
  core/       #   growable storage, byte building, the named limits
  cal/        #   the civil calendar and its algorithms
  span/       #   Duration interop and Period
  zone/       #   the GENERATED time-zone tables and the offset lookup
  fmt/        #   formatting and parsing, and the typed layout
  host/       #   the only module that asks the machine anything
tests/        # probe, conformance, unit, golden, rejection, fixtures
examples/     # runnable demonstrations, built and run by the harness
harness/      # the Python build and test runner, until `npkg` can build a library
tools/        # generators — the civil cross-oracle's corpus since cycle 0.1.4, the tzdb
              # tables from cycle 0.5; everything they emit is committed
meta/specs/   # the design authority
meta/roadmap/ # the plan, in numbered cycles
docs/         # user-facing documentation, written at 1.0

Specification

meta/specs/ is the authority on behaviour, and meta/DECISIONS.md records every settled design decision with its reasoning — start there when something looks unusual, because it is recorded why.

Plan

meta/roadmap/ROADMAP.md is the cycle map. A cycle is a folder, a subcycle is a file inside it, and a finished cycle moves to meta/roadmap/done/.

Requirements

Linux on x86-64, the Nitpick compiler, and LLVM 20.1.2 — the same toolchain the compiler pins. Nothing else, at build time or at run time.

Licence

Apache 2.0. See LICENSE.

About

Dates, times and time zones for Nitpick — zero dependency, no libc, no system tzdb. The IANA database is compiled in from a pinned release, so the same program gives the same answer on every machine. Monotonic readings and UTC timestamps are different types.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages