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 Q3quarter(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 Aprilquarter(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
| iso | date | any date |
| fiscal_year_start_month | int | 1 = January (fiscal = calendar), 4 = April (UK government), 7 = July, 10 = October (US federal) |
| returns | Quarter |
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(…)
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
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 1,042 |
| impl/python.py | 2,303 |
| impl/rust.rs | 3,146 |
| impl/typescript.ts | 2,203 |
| vectors.json | 4,414 |