dates.days-between
Calendar days from one ISO date to another, signed: negative when the second date is earlier.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 15 tests, run in TypeScript, Python and Rust.
What it does
End minus start, in calendar days. The same date twice is 0, the next day is 1, and a start after the end is negative rather than an error or zero: a negative answer makes a swapped argument pair obvious, where a clamped zero would hide it.
This is the difference of the two dates, not a count of days "including both ends". 1 January to 31 January is 30. If you need an inclusive count (a hotel night count is the difference; a leave request "1st to 5th inclusive" is the difference plus one), add one yourself, deliberately.
For example
days_between(2026-09-22, 2026-09-22)→ 0 the same date twice is zero daysdays_between(2026-09-22, 2026-09-23)→ 1 the next day is onedays_between(2026-01-01, 2026-01-31)→ 30 1 to 31 January is 30, not 31: the difference, not an inclusive count
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn days_between(start_iso: &str, end_iso: &str) -> i64
| start_iso | date | ISO date, YYYY-MM-DD |
| end_iso | date | ISO date, YYYY-MM-DD |
| returns | int | end minus start in days; 0 for the same date |
Your code names it in one line, in the file that uses it
fune!(dates.days-between@^1); // then call days_between(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::epoch_day_from_iso; ← from dates.add-days ^1.0.0 · built alongside by fune
/// Calendar days from one date to another: end minus start, negative when the
/// end is earlier. Both dates are parsed strictly, so an impossible date fails
/// rather than being rolled into the next month.
///
/// # Panics
/// Panics if either date is malformed or impossible.
pub fn days_between(start_iso: &str, end_iso: &str) -> i64 {
epoch_day_from_iso(end_iso) - epoch_day_from_iso(start_iso)
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Int(days_between(args[0].as_str(), args[1].as_str()))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add dates.days-between
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./dates.days-between-1.0.0-rust.fune, or fetch it from a terminal with fune pull dates.days-between@1.0.0:rust.
The whole function, every language, is one file too: dates.days-between-1.0.0.fune, 5,753 bytes, sha256 565c4d0d13627f23956a64889cf830784d375e938692821120e090059e785da8. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before dates.days-between
after — your function gets the result and the arguments, and returns the final result.
// fune: after dates.days-between
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace dates.add-days in dates.days-between
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show dates.days-between --steps.
// fune: step dates.days-between after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| the same date twice is zero days | 2026-09-22, 2026-09-22 | → | 0 |
| the next day is one | 2026-09-22, 2026-09-23 | → | 1 |
| 1 to 31 January is 30, not 31: the difference, not an inclusive count | 2026-01-01, 2026-01-31 | → | 30 |
| an end before the start is negative, not zero | 2026-09-23, 2026-09-22 | → | -1 |
| across the end of February in a leap year | 2024-02-28, 2024-03-01 | → | 2 |
| across the end of February in an ordinary year | 2026-02-28, 2026-03-01 | → | 1 |
| across the UK clocks going forward is still one calendar day | 2026-03-29, 2026-03-30 | → | 1 |
| a whole leap year is 366 days | 2024-01-01, 2025-01-01 | → | 366 |
| a whole ordinary year is 365 days | 2025-01-01, 2026-01-01 | → | 365 |
| 1900 had no 29 February, so its year is 365 days | 1900-01-01, 1901-01-01 | → | 365 |
Show the other 5 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| across the 1970 epoch | 1969-12-31, 1970-01-02 | → | 2 |
| the full supported range | 0001-01-01, 9999-12-31 | → | 3,652,058 |
| the full supported range, backwards | 9999-12-31, 0001-01-01 | → | -3,652,058 |
| an impossible start date is an error, not 2 March | 2026-02-30, 2026-03-05 | → | error: is not a real calendar date |
| a UK-style date is an error, not a guess | 2026-01-01, 31/01/2026 | → | error: is not an ISO date |
More from the author
Days are calendar days on the civil calendar, so there are no time zones and no daylight-saving hours to lose: 29 March 2026 to 30 March 2026 is 1 in every country. Working days are dates.business-days-between.
Both dates are validated strictly by the dates.add-days kernel (2026-02-30 is an error, not 2 March), and the arithmetic is that kernel's day numbers, so dates before 1970 and across centuries are exact.
Files
| Path | Bytes |
|---|---|
| README.md | 967 |
| impl/python.py | 395 |
| impl/rust.rs | 597 |
| impl/typescript.ts | 406 |
| vectors.json | 1,707 |