Functional Weave
Code in Rust

dates.quarter

Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

What it does

The calendar quarter of a date (Q1 is January to March) and its quarter in a fiscal year that starts on the 1st of any month, with the date ranges of that fiscal year and fiscal quarter, inclusive at both ends as a report prints them.

The fiscal year is returned as its start and end dates rather than as a year number, because there is no agreement on the number. The UK government's year from April 2026 is "2026-27"; the US federal year from October 2026 is "FY2027", named for the year it ends in; many companies name theirs for the year it starts in. The dates are unambiguous, and a label is one line of caller code in whichever house style applies.

For example

  • quarter(2026-09-23, 4) → calendar year 2,026, calendar quarter 3, fiscal quarter 2, fiscal year start 2026-04-01, fiscal year end 2027-03-31, fiscal quarter start 2026-07-01, fiscal quarter end 2026-09-30 UK government year from April: late September is fiscal Q2, calendar Q3
  • quarter(2026-02-15, 4) → calendar year 2,026, calendar quarter 1, fiscal quarter 4, fiscal year start 2025-04-01, fiscal year end 2026-03-31, fiscal quarter start 2026-01-01, fiscal quarter end 2026-03-31 April year: February is fiscal Q4 of the year that began the previous April
  • quarter(2026-03-31, 4) → calendar year 2,026, calendar quarter 1, fiscal quarter 4, fiscal year start 2025-04-01, fiscal year end 2026-03-31, fiscal quarter start 2026-01-01, fiscal quarter end 2026-03-31 April year: 31 March is the last day of the fiscal year

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 quarter(iso: &str, fiscal_year_start_month: i64) -> Quarter
isodateany date
fiscal_year_start_monthint1 = January (fiscal = calendar), 4 = April (UK government), 7 = July, 10 = October (US federal)
returnsQuarter

The type it declares, generated into your project

/// Where a date falls in the calendar year and in the fiscal year. Date ranges are inclusive.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Quarter {
    pub calendar_year: i64,
    /// 1 to 4; Q1 is January to March
    pub calendar_quarter: i64,
    /// 1 to 4; Q1 starts in fiscalYearStartMonth
    pub fiscal_quarter: i64,
    /// first day of the fiscal year containing the date
    pub fiscal_year_start: String,
    /// last day of that fiscal year
    pub fiscal_year_end: String,
    /// first day of the fiscal quarter
    pub fiscal_quarter_start: String,
    /// last day of the fiscal quarter
    pub fiscal_quarter_end: String,
}

Your code names it in one line, in the file that uses it

fune!(dates.quarter@^1);  // then call quarter(…)
impl/rust.rs · 81 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::{days_in_month, format_iso_date, parse_iso_date, CivilDate};  ← from dates.add-days ^1.0.0 · built alongside by fune

/// A month counted from January of year 0, back to its year and month.
fn month_from_index(index: i64) -> (i64, i64) {
    (index.div_euclid(12), index.rem_euclid(12) + 1)
}

fn first_day(index: i64) -> String {
    let (year, month) = month_from_index(index);
    format_iso_date(&CivilDate { year, month, day: 1 })
}

fn last_day(index: i64) -> String {
    let (year, month) = month_from_index(index);
    format_iso_date(&CivilDate {
        year,
        month,
        day: days_in_month(year, month),
    })
}

/// The calendar and fiscal quarter of a date. The fiscal year is given as
/// dates, not a number, because "FY2027" means the year ending in 2027 to one
/// organisation and the year starting in it to another.
///
/// # Panics
/// Panics on a malformed date, a start month outside 1-12, or a fiscal year
/// outside 0001-9999.
pub fn quarter(iso: &str, fiscal_year_start_month: i64) -> Quarter {
    if !(1..=12).contains(&fiscal_year_start_month) {
        panic!(
            "fiscalYearStartMonth must be 1-12, received {}",
            fiscal_year_start_month
        );
    }
    let date = parse_iso_date(iso);
    let months_into_year = (date.month - fiscal_year_start_month).rem_euclid(12);
    let fiscal_quarter = months_into_year / 3 + 1;
    // Work in months counted from year 0 so year boundaries need no special case.
    let year_start = date.year * 12 + (date.month - 1) - months_into_year;
    let quarter_start = year_start + 3 * (fiscal_quarter - 1);
    if month_from_index(year_start).0 < 1 || month_from_index(year_start + 11).0 > 9999 {
        panic!(
            "the fiscal year containing \"{}\" is outside the supported range 0001-01-01 to 9999-12-31",
            iso
        );
    }
    Quarter {
        calendar_year: date.year,
        calendar_quarter: (date.month - 1) / 3 + 1,
        fiscal_quarter,
        fiscal_year_start: first_day(year_start),
        fiscal_year_end: last_day(year_start + 11),
        fiscal_quarter_start: first_day(quarter_start),
        fiscal_quarter_end: last_day(quarter_start + 2),
    }
}

pub fn quarter_to_value(q: &Quarter) -> Value {
    Value::obj(vec![
        ("calendarYear", Value::Int(q.calendar_year)),
        ("calendarQuarter", Value::Int(q.calendar_quarter)),
        ("fiscalQuarter", Value::Int(q.fiscal_quarter)),
        ("fiscalYearStart", Value::str(&q.fiscal_year_start)),
        ("fiscalYearEnd", Value::str(&q.fiscal_year_end)),
        ("fiscalQuarterStart", Value::str(&q.fiscal_quarter_start)),
        ("fiscalQuarterEnd", Value::str(&q.fiscal_quarter_end)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    if let Value::Float(f) = args[1] {
        if f.fract() != 0.0 {
            panic!("fiscalYearStartMonth must be an integer, received {}", f);
        }
    }
    quarter_to_value(&quarter(args[0].as_str(), args[1].as_i64()))
}

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.quarter
Download for Rust dates.quarter-1.0.0-rust.fune · 11,796 bytes sha256 b8cc788a1e30c3d2fb94c9ab77f4cfc8536d67d6a78d8fc4689fa513b47fe99e

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./dates.quarter-1.0.0-rust.fune, or fetch it from a terminal with fune pull dates.quarter@1.0.0:rust.

The whole function, every language, is one file too: dates.quarter-1.0.0.fune, 16,466 bytes, sha256 a9245cb609f1202d1bdf5f7fb0d9b801c6fd7b7a79a68d0dad7c04ae4e78cf34. 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.quarter

after — your function gets the result and the arguments, and returns the final result.

// fune: after dates.quarter

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.quarter

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.quarter --steps.

// fune: step dates.quarter 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
UK government year from April: late September is fiscal Q2, calendar Q3 2026-09-23, 4 → calendar year 2,026, calendar quarter 3, fiscal quarter 2, fiscal year start 2026-04-01, fiscal year end 2027-03-31, fiscal quarter start 2026-07-01, fiscal quarter end 2026-09-30
April year: February is fiscal Q4 of the year that began the previous April 2026-02-15, 4 → calendar year 2,026, calendar quarter 1, fiscal quarter 4, fiscal year start 2025-04-01, fiscal year end 2026-03-31, fiscal quarter start 2026-01-01, fiscal quarter end 2026-03-31
April year: 31 March is the last day of the fiscal year 2026-03-31, 4 → calendar year 2,026, calendar quarter 1, fiscal quarter 4, fiscal year start 2025-04-01, fiscal year end 2026-03-31, fiscal quarter start 2026-01-01, fiscal quarter end 2026-03-31
April year: 1 April starts fiscal Q1 of a new year 2026-04-01, 4 → calendar year 2,026, calendar quarter 2, fiscal quarter 1, fiscal year start 2026-04-01, fiscal year end 2027-03-31, fiscal quarter start 2026-04-01, fiscal quarter end 2026-06-30
a January start makes fiscal and calendar quarters the same 2026-11-05, 1 → calendar year 2,026, calendar quarter 4, fiscal quarter 4, fiscal year start 2026-01-01, fiscal year end 2026-12-31, fiscal quarter start 2026-10-01, fiscal quarter end 2026-12-31
US federal year from October: 1 October 2026 starts fiscal Q1 2026-10-01, 10 → calendar year 2,026, calendar quarter 4, fiscal quarter 1, fiscal year start 2026-10-01, fiscal year end 2027-09-30, fiscal quarter start 2026-10-01, fiscal quarter end 2026-12-31
US federal year: 30 September 2026 ends the year that began October 2025 2026-09-30, 10 → calendar year 2,026, calendar quarter 3, fiscal quarter 4, fiscal year start 2025-10-01, fiscal year end 2026-09-30, fiscal quarter start 2026-07-01, fiscal quarter end 2026-09-30
July year: 29 February 2024 is fiscal Q3 2024-02-29, 7 → calendar year 2,024, calendar quarter 1, fiscal quarter 3, fiscal year start 2023-07-01, fiscal year end 2024-06-30, fiscal quarter start 2024-01-01, fiscal quarter end 2024-03-31
February year: fiscal Q4 runs November to January, across the calendar year end 2025-01-31, 2 → calendar year 2,025, calendar quarter 1, fiscal quarter 4, fiscal year start 2024-02-01, fiscal year end 2025-01-31, fiscal quarter start 2024-11-01, fiscal quarter end 2025-01-31
March year ending in an ordinary February ends on the 28th 2024-05-10, 3 → calendar year 2,024, calendar quarter 2, fiscal quarter 1, fiscal year start 2024-03-01, fiscal year end 2025-02-28, fiscal quarter start 2024-03-01, fiscal quarter end 2024-05-31
Show the other 7 tests
CaseArgumentsExpected
March year ending in a leap February ends on the 29th 2023-06-01, 3 → calendar year 2,023, calendar quarter 2, fiscal quarter 2, fiscal year start 2023-03-01, fiscal year end 2024-02-29, fiscal quarter start 2023-06-01, fiscal quarter end 2023-08-31
December year: December is fiscal Q1 though it is calendar Q4 2026-12-01, 12 → calendar year 2,026, calendar quarter 4, fiscal quarter 1, fiscal year start 2026-12-01, fiscal year end 2027-11-30, fiscal quarter start 2026-12-01, fiscal quarter end 2027-02-28
month 13 is not a start month 2026-09-23, 13 → error: fiscalYearStartMonth must be 1-12
month 0 is not a start month 2026-09-23, 0 → error: fiscalYearStartMonth must be 1-12
a fractional start month is an error 2026-09-23, 4.5 → error: fiscalYearStartMonth must be an integer
a fiscal year running past 9999 is an error 9999-12-31, 4 → error: outside the supported range
an impossible date is an error 2026-02-30, 4 → error: is not a real calendar date

More from the author

A fiscal year always starts on the 1st of a month here. The UK personal tax year (6 April to 5 April) does not; use dates.uk-tax-year for that.

The fiscal year end and quarter ends use the real month lengths, so a fiscal year starting 1 March ends on 28 or 29 February as the calendar says. A fiscal year that would run outside 0001-01-01 to 9999-12-31 is an error.

Files

PathBytes
README.md1,042
impl/python.py2,303
impl/rust.rs3,146
impl/typescript.ts2,203
vectors.json4,414