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.
def quarter(iso: str, fiscal_year_start_month: int) -> 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
@dataclass(frozen=True)
class Quarter:
"""Where a date falls in the calendar year and in the fiscal year. Date ranges are inclusive."""
calendar_year: int
#: 1 to 4; Q1 is January to March
calendar_quarter: int
#: 1 to 4; Q1 starts in fiscalYearStartMonth
fiscal_quarter: int
#: first day of the fiscal year containing the date
fiscal_year_start: str
#: last day of that fiscal year
fiscal_year_end: str
#: first day of the fiscal quarter
fiscal_quarter_start: str
#: last day of the fiscal quarter
fiscal_quarter_end: str
Your code names it in one line, in the file that uses it
from fune.dates.quarter import quarter # 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.
from typing import Tuple
from .dates_add_days import CivilDate, days_in_month, format_iso_date, parse_iso_date ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_quarter_types import Quarter
def _month_from_index(index: int) -> Tuple[int, int]:
"""A month counted from January of year 0, back to its year and month."""
year = index // 12
return year, index - year * 12 + 1
def _first_day(index: int) -> str:
year, month = _month_from_index(index)
return format_iso_date(CivilDate(year=year, month=month, day=1))
def _last_day(index: int) -> str:
year, month = _month_from_index(index)
return format_iso_date(CivilDate(year=year, month=month, day=days_in_month(year, month)))
def quarter(iso: str, fiscal_year_start_month: int) -> Quarter:
"""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.
"""
if isinstance(fiscal_year_start_month, bool) or not isinstance(fiscal_year_start_month, int):
raise TypeError("fiscalYearStartMonth must be an integer, received %r" % (fiscal_year_start_month,))
if fiscal_year_start_month < 1 or fiscal_year_start_month > 12:
raise ValueError("fiscalYearStartMonth must be 1-12, received %s" % (fiscal_year_start_month,))
date = parse_iso_date(iso)
months_into_year = (date.month - fiscal_year_start_month) % 12
fiscal_quarter = months_into_year // 3 + 1
# Work in months counted from year 0 so year boundaries need no special case.
year_start = date.year * 12 + (date.month - 1) - months_into_year
quarter_start = year_start + 3 * (fiscal_quarter - 1)
if _month_from_index(year_start)[0] < 1 or _month_from_index(year_start + 11)[0] > 9999:
raise ValueError(
'the fiscal year containing "%s" is outside the supported range 0001-01-01 to 9999-12-31' % (iso,)
)
return Quarter(
calendar_year=date.year,
calendar_quarter=(date.month - 1) // 3 + 1,
fiscal_quarter=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),
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python 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 Python implementation. Install it without the registry with fune add ./dates.quarter-1.0.0-python.fune, or fetch it from a terminal with fune pull dates.quarter@1.0.0:python.
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 |