Functional Weave
Code in Rust

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 days
  • days_between(2026-09-22, 2026-09-23) → 1 the next day is one
  • days_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_isodateISO date, YYYY-MM-DD
end_isodateISO date, YYYY-MM-DD
returnsintend 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(…)
impl/rust.rs · 16 lines · open · raw

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
Download for Rust dates.days-between-1.0.0-rust.fune · 4,891 bytes sha256 0bd33d149fb769492fd3d0f9fc15754684118e087dc5e29f8c3a24f11fbc8c35

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md967
impl/python.py395
impl/rust.rs597
impl/typescript.ts406
vectors.json1,707