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.
export function quarter(iso: string, fiscalYearStartMonth: number): Quarter
| iso | date | any date |
| fiscalYearStartMonth | 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. */
export interface Quarter {
readonly calendarYear: number;
/** 1 to 4; Q1 is January to March */
readonly calendarQuarter: number;
/** 1 to 4; Q1 starts in fiscalYearStartMonth */
readonly fiscalQuarter: number;
/** first day of the fiscal year containing the date */
readonly fiscalYearStart: string;
/** last day of that fiscal year */
readonly fiscalYearEnd: string;
/** first day of the fiscal quarter */
readonly fiscalQuarterStart: string;
/** last day of the fiscal quarter */
readonly fiscalQuarterEnd: string;
}
Your code names it in one line, in the file that uses it
import { quarter } from "#fune/dates.quarter@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { daysInMonth, formatIsoDate, parseIsoDate } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { type Quarter } from "./dates_quarter_types.ts";
/** A month counted from January of year 0, back to its year and month. */
function monthFromIndex(index: number): { year: number; month: number } {
const year = Math.floor(index / 12);
return { year, month: index - year * 12 + 1 };
}
function firstDay(index: number): string {
const { year, month } = monthFromIndex(index);
return formatIsoDate({ year, month, day: 1 });
}
function lastDay(index: number): string {
const { year, month } = monthFromIndex(index);
return formatIsoDate({ year, month, day: daysInMonth(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.
*/
export function quarter(iso: string, fiscalYearStartMonth: number): Quarter {
if (!Number.isInteger(fiscalYearStartMonth)) {
throw new TypeError(`fiscalYearStartMonth must be an integer, received ${fiscalYearStartMonth}`);
}
if (fiscalYearStartMonth < 1 || fiscalYearStartMonth > 12) {
throw new RangeError(`fiscalYearStartMonth must be 1-12, received ${fiscalYearStartMonth}`);
}
const date = parseIsoDate(iso);
const monthsIntoYear = (date.month - fiscalYearStartMonth + 12) % 12;
const fiscalQuarter = Math.floor(monthsIntoYear / 3) + 1;
// Work in months counted from year 0 so year boundaries need no special case.
const yearStart = date.year * 12 + (date.month - 1) - monthsIntoYear;
const quarterStart = yearStart + 3 * (fiscalQuarter - 1);
if (monthFromIndex(yearStart).year < 1 || monthFromIndex(yearStart + 11).year > 9999) {
throw new RangeError(`the fiscal year containing "${iso}" is outside the supported range 0001-01-01 to 9999-12-31`);
}
return {
calendarYear: date.year,
calendarQuarter: Math.floor((date.month - 1) / 3) + 1,
fiscalQuarter,
fiscalYearStart: firstDay(yearStart),
fiscalYearEnd: lastDay(yearStart + 11),
fiscalQuarterStart: firstDay(quarterStart),
fiscalQuarterEnd: lastDay(quarterStart + 2),
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the TypeScript 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. Or pin a range in fune.project and build in one step:
fune add dates.quarter
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./dates.quarter-1.0.0-typescript.fune, or fetch it from a terminal with fune pull dates.quarter@1.0.0:typescript.
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 |