fasti
Dates, calendars, business-day conventions, and day-count fractions for
financial code — a native Rust library, no_std and free of
floating-point arithmetic, designed after QuantLib's ql/time.
Named for the fasti, the ancient Roman calendar that marked the dies fasti — the days on which business could lawfully be conducted.
Why
Financial code needs to answer questions like "when is the next coupon
date?", "how many days of interest accrued?", and "is the market open
on Friday?" — precisely, deterministically, and without floating-point
drift. General-purpose date libraries answer none of these; QuantLib
answers all of them but brings C++ and doubles. fasti covers the
same capability surface with:
- No float arithmetic — anywhere. Day-count fractions are integer
rationals (
Fraction, ani64/u64reduced fraction). Scaling money by an accrual fraction stays exact. no_std+alloc. No I/O, no clock, no timezone database, no runtime dependencies beyondthiserror. The library has no concept of "now" — you tell it the dates. CI compiles it for a bare-metal target (thumbv7em-none-eabihf), so the claim is checked, not asserted.- Const-first. Every primitive constructor is
const fn, and every built-in calendar is apub const— zero allocation, zero setup. - No panics in library code. Fallible operations return
Result<_, TimeError>.unwrap/expect/panicare clippy-walled. - Property-tested invariants. Conservation laws (ACT-family additivity, adjust idempotence, schedule monotonicity) are proptest suites, not comments. A separate integration test compiles against the crate from outside, so the public API is exercised the way a dependent sees it.
What's in the box
| Area | Types |
|---|---|
| Date primitives | Date (serial, 1901-01-01..=2199-12-31), Year, Month, Weekday, Ordinal |
| Durations | Period (days/weeks/months/years), Frequency |
| Holiday rules | Rule: fixed-date (with weekend-shift policies), nth/last weekday, Easter offsets (Western & Orthodox), one-offs, custom fn(Date) -> bool |
| Calendars | Calendar / CalendarBuilder; built-ins: TARGET, UK Settlement, US Settlement, NYSE, Federal Reserve, Government Bond, SOFR, NERC, France Settlement & Exchange, plus WEEKENDS_ONLY / NULL_CALENDAR baselines. Business-day and holiday enumeration over a date range, month edges, and joint calendars via CalendarBuilder::union |
| Business days | BusinessDayConvention (Following, ModifiedFollowing, Preceding, ModifiedPreceding, Unadjusted), adjust, advance |
| Day counts | DayCount: ACT/360, ACT/365F, 30/360 (Bond Basis, US, 30E/360, 30E/360 ISDA), ACT/ACT (ISDA and schedule-aware ICMA) — all returning Fraction |
| Schedules | Schedule / ScheduleBuilder: forward/backward/zero generation, stubs, end-of-month preservation |
Python
The bindings live in bindings/python and ship as
fasti-dates on PyPI; the import name is fasti, matching the crate.
(fasti itself is an unrelated project there, and PyPI refuses
fasti-py as too similar to it.)
$ pip install fasti-dates
>>>
>>>
>>>
Dates cross as datetime.date and year fractions come back as
fractions.Fraction, so the float-free guarantee survives the boundary.
Install
[]
= "0.1"
# Optional features (both off by default):
# serde — Serialize/Deserialize on the data types
# chrono — From/TryFrom conversions with chrono::NaiveDate,
# chrono::Weekday, and chrono::Month
= { = "0.1", = ["serde", "chrono"] }
The minimum supported Rust version is 1.90 (edition 2024). CI tests against it on every change, using a committed lockfile pinned to a dependency resolution known to build there. Raising the MSRV is a minor-version bump, never a patch.
Coming from chrono? Enable the chrono feature and convert at the
boundary — let d: fasti::Date = naive_date.try_into()?; (fallible
only because fasti's supported range is 1901..=2199) and
chrono::NaiveDate::from(d) on the way out. fasti never uses chrono
internally.
Quickstart
use ;
Run the fuller example:
Design notes
See ARCHITECTURE.md for the design constraints:
serial dates, the rule-based calendar model, why Fraction instead of
floats, and the supported 1901..=2199 date range.
QuantLib's ql/time is the design reference; ported holiday tables and
lookup data are attributed in the module docs and in
THIRD-PARTY-NOTICES. The Easter tables are
additionally cross-validated in tests against independent computus
implementations, so they are reproducible from first principles.
Status
Pre-1.0. The public surface is small and deliberate but may still move. Planned next: CDS/IMM schedule generation rules and the long-tail day counts (Business/252, ACT/365 Canadian, NASD 30/360).
Contributing
Issues and pull requests are welcome. CONTRIBUTING.md
covers the development workflow and the ground rules;
ARCHITECTURE.md covers the constraints behind
them. Participation is under the
Code of Conduct.
Calendar and day-count data bugs are the most useful thing you can
report. The issue template for them asks for the published source the
fix will be checked against, because that is what makes such a bug
fixable in one pass. For anything that looks like a vulnerability, read
SECURITY.md first — it also explains why wrong
holiday data deliberately is not one.
License
Dual-licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Third-party material
The Easter-Monday lookup tables in src/easter.rs are ported from
QuantLib, which is distributed under a permissive modified-BSD
(3-clause) license. That license permits the redistribution above; its
full text and copyright notice are reproduced in
THIRD-PARTY-NOTICES, which ships inside the
published crate. If your license tooling flags fasti for third-party
content, this is what it has found — there is no copyleft anywhere in
the dependency graph, and cargo deny check enforces that on every
commit.