dates.week-number
ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 18 tests, run in TypeScript, Python and Rust.
What it does
The ISO 8601 week of a date. Weeks run Monday to Sunday, and week 1 of a year is the week containing that year's first Thursday (equivalently, the week containing 4 January). A year has 52 or 53 weeks.
The trap is the year. Around 1 January a date can belong to the previous or the next ISO week-year: 1 January 2027 is in week 53 of 2026, and 30 December 2024 is in week 1 of 2025. So the result carries `weekYear` alongside `week`, and `label` ("2026-W53") is built from the week-year, never the calendar year. A report keyed on calendar year and ISO week puts 30 December 2024 in "2024 week 1", eleven months early.
For example
week_number(2026-09-23)→ week year 2,026, week 39, label 2026-W39 an ordinary Wednesday in Septemberweek_number(2026-01-01)→ week year 2,026, week 1, label 2026-W01 1 January 2026 is a Thursday, so it is in week 1week_number(2027-01-01)→ week year 2,026, week 53, label 2026-W53 1 January 2027 is a Friday and belongs to week 53 of 2026, not week 1 of 2027
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 week_number(iso: str) -> IsoWeek
| iso | date | any date |
| returns | IsoWeek |
The type it declares, generated into your project
@dataclass(frozen=True)
class IsoWeek:
"""A date's ISO 8601 week."""
#: the ISO week-numbering year; differs from the calendar year for a few days around 1 January
week_year: int
#: 1 to 52 or 53
week: int
#: ISO week form, e.g. 2026-W39
label: str
Your code names it in one line, in the file that uses it
from fune.dates.week_number import week_number # dates.week-number@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .dates_add_days import civil_from_days, days_from_civil, epoch_day_from_iso ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week ← from dates.day-of-week ^1.0.0 · built alongside by fune
from .dates_week_number_types import IsoWeek
def week_number(iso: str) -> IsoWeek:
"""The ISO 8601 week of a date.
The Thursday of the date's Monday-to-Sunday week decides everything: its
calendar year is the week-year, and its day of that year gives the week
number.
"""
thursday = epoch_day_from_iso(iso) + (4 - day_of_week(iso))
week_year = civil_from_days(thursday).year
week = (thursday - days_from_civil(week_year, 1, 1)) // 7 + 1
return IsoWeek(week_year=week_year, week=week, label="%04d-W%02d" % (week_year, week))Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, 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.week-number
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./dates.week-number-1.0.0-python.fune, or fetch it from a terminal with fune pull dates.week-number@1.0.0:python.
The whole function, every language, is one file too: dates.week-number-1.0.0.fune, 8,545 bytes, sha256 57419625d710371dddc8e6db21a61c341ada81dd0670f975210cf019b72de781. 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.week-number
after — your function gets the result and the arguments, and returns the final result.
# fune: after dates.week-number
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.week-number
# fune: replace dates.day-of-week in dates.week-number
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.week-number --steps.
# fune: step dates.week-number 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 | |
|---|---|---|---|
| an ordinary Wednesday in September | 2026-09-23 | → | week year 2,026, week 39, label 2026-W39 |
| 1 January 2026 is a Thursday, so it is in week 1 | 2026-01-01 | → | week year 2,026, week 1, label 2026-W01 |
| 1 January 2027 is a Friday and belongs to week 53 of 2026, not week 1 of 2027 | 2027-01-01 | → | week year 2,026, week 53, label 2026-W53 |
| Sunday 3 January 2027 still ends week 53 of 2026 | 2027-01-03 | → | week year 2,026, week 53, label 2026-W53 |
| Monday 4 January 2027 starts week 1 of 2027 | 2027-01-04 | → | week year 2,027, week 1, label 2027-W01 |
| 30 December 2024 is in week 1 of 2025: calendar year and week-year differ | 2024-12-30 | → | week year 2,025, week 1, label 2025-W01 |
| 29 December 2008 is in week 1 of 2009 | 2008-12-29 | → | week year 2,009, week 1, label 2009-W01 |
| 2020 had 53 weeks: 31 December 2020 is in week 53 | 2020-12-31 | → | week year 2,020, week 53, label 2020-W53 |
| 3 January 2021 is still in week 53 of 2020 | 2021-01-03 | → | week year 2,020, week 53, label 2020-W53 |
| 2015 had 53 weeks | 2015-12-31 | → | week year 2,015, week 53, label 2015-W53 |
Show the other 8 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 31 December 2026 is in week 53 of 2026 | 2026-12-31 | → | week year 2,026, week 53, label 2026-W53 |
| 2004 was a leap year starting on Thursday, so it had 53 weeks | 2004-12-31 | → | week year 2,004, week 53, label 2004-W53 |
| 3 January 2010 belongs to week 53 of 2009 | 2010-01-03 | → | week year 2,009, week 53, label 2009-W53 |
| 1 January 1900 was a Monday: week 1 | 1900-01-01 | → | week year 1,900, week 1, label 1900-W01 |
| the first supported date was a Monday: 0001-W01 | 0001-01-01 | → | week year 1, week 1, label 0001-W01 |
| the last supported date is in week 52 of 9999 | 9999-12-31 | → | week year 9,999, week 52, label 9999-W52 |
| an impossible date is an error | 2026-02-29 | → | error: is not a real calendar date |
| an ISO week string is not a date | 2026-W39 | → | error: is not an ISO date |
More from the author
This is the ISO scheme only. US-style week numbers (weeks starting Sunday, week 1 containing 1 January) and "week of the month" are different questions.
The week is found from the Thursday of the date's week, whose calendar year is by definition the week-year, using the dates.add-days kernel and dates.day-of-week, so it is exact for every date from 0001-01-01 to 9999-12-31.
Files
| Path | Bytes |
|---|---|
| README.md | 1,020 |
| impl/python.py | 687 |
| impl/rust.rs | 1,149 |
| impl/typescript.ts | 768 |
| vectors.json | 2,600 |