Repository navigation
ADR 0002 single rust core with thin bindings
All calendar logic lives in one Rust crate, and the Python and npm packages only convert values at the edge and call the core's JSON API.
Last reviewed: 31 Oct 2026
Accepted on 31 Oct 2026. Decided by the research documents: the research proposal ("Ecosystem bindings") and the gap analysis ("Binding and platform risks"), which calls the one-core architecture right. Built in 0.1.0.
The proposal's goal is one engine that gives identical answers in Rust, Python and JavaScript. The existing tools it surveys each live in one ecosystem: ThreeTen-Extra in Java, Fiscal5253 in Perl, fiscalyear and pandas in Python, retail-calendar in JavaScript (see Analysis). Fiscal calendar arithmetic has many edge cases (start days 29 to 31, the extra week, year numbers near 1 January), and every separate implementation is another place for them to go wrong.
The proposal also argues for a batch path: each operation is cheap, so for single dates most of the time goes into crossing the language boundary, and a compiled core taking whole arrays is where it expects the gain. That claim is to be measured by per-binding benchmarks, planned for 0.2 on the Roadmap; it is not yet measured.
- Identical results in every language, checkable byte for byte.
- One place to fix a bug and one place to add a vector.
- Small maintainer team: the proposal notes that
fiscalyearstalled with one maintainer. - Reach into Python, JavaScript and Rust users, plus WebAssembly for browsers.
Pros:
- No native build step; pure-Python and pure-JS packages install everywhere.
- Each package can use its ecosystem's idioms throughout.
Cons:
- Three implementations of every edge case, kept in step by hand.
- Shared conformance vectors catch drift only for the cases they contain.
Pros:
- Calendar arithmetic and validation exist once. Bindings cannot disagree on a result because they do not compute one.
- Rust compiles to native extensions for Python and to WebAssembly for JavaScript.
- The core's JSON API gives a byte-level comparison point across languages (ADR-0019).
Cons:
- Python and npm packages need a native or WebAssembly build per platform, which makes the release workflow larger (14 wheels, an sdist, a crate and an npm tarball in 0.1.0).
- Contributors who only know Python or JavaScript cannot change behaviour without touching Rust.
Pros:
- Bindings could offer language-specific conveniences, such as pandas-native results, without a round trip through JSON.
Cons:
- Logic in a binding is logic the other bindings lack, which reopens the drift problem of Option 1.
Option 2. AGENTS.md design rule 9 makes it checkable: bindings call fiscalclock::json and return its JSON unchanged on their *_json methods, and no binding reimplements a computation, reorders keys or reformats values. The repository map forbids calendar arithmetic or validation in bindings/python/ and bindings/js/.
As built in 0.1.0:
-
crates/coreis the cratefiscalclock, withjson::validateandjson::calltaking and returning JSON text (crate README). -
bindings/pythonis a PyO3 extension plus a Python layer that convertsdatetime.datevalues and dataclasses (ADR-0011). -
bindings/jsis a wasm-bindgen module plus a TypeScript layer (ADR-0020). - The CI job
identical-outputinci.ymlcompares 14 transcripts (Rust, and every Python and Node.js job on Linux, macOS and Windows) with the Rust transcript byte for byte.
Good:
- A fix or new behaviour lands once and reaches every package.
- The cross-binding check is a plain file comparison, not a semantic diff.
Bad:
- Every binding pays a JSON round trip per call. The bulk paths that would amortise it (ADR-0012, typed arrays in JavaScript) are planned for 0.2, not built.
- The CLI and MCP server planned for 0.4 must also go through the core's JSON API (ADR-0013); the repository map forbids CLI logic that the JSON API cannot reach.
- Architecture, Testing-Strategy, PDR
spec/api-v1.md- Related: ADR-0003, ADR-0011, ADR-0019, ADR-0020
fiscalclock is licensed under MIT OR Apache-2.0 (MIT, Apache-2.0). Last reviewed: 31 Oct 2026. Found an error on this page? Report it with the documentation form.