Functional Weave
Code in TypeScript

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.

export function quarter(iso: string, fiscalYearStartMonth: number): Quarter
isodateany date
fiscalYearStartMonthint1 = 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. */
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";
impl/typescript.ts · 50 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.

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
Download for TypeScript dates.quarter-1.0.0-typescript.fune · 10,816 bytes sha256 47389b683857f4b83e7ef66287ac02474ea3dbe1d846d3d210a6c26638f551e2

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.

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