Functional Weave
Code in TypeScript

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

  • weekNumber(2026-09-23) → week year 2,026, week 39, label 2026-W39 an ordinary Wednesday in September
  • weekNumber(2026-01-01) → week year 2,026, week 1, label 2026-W01 1 January 2026 is a Thursday, so it is in week 1
  • weekNumber(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.

export function weekNumber(iso: string): IsoWeek
isodateany date
returnsIsoWeek

The type it declares, generated into your project

/** A date's ISO 8601 week. */
export interface IsoWeek {
  /** the ISO week-numbering year; differs from the calendar year for a few days around 1 January */
  readonly weekYear: number;
  /** 1 to 52 or 53 */
  readonly week: number;
  /** ISO week form, e.g. 2026-W39 */
  readonly label: string;
}

Your code names it in one line, in the file that uses it

import { weekNumber } from "#fune/dates.week-number@^1";
impl/typescript.ts · 19 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 { civilFromDays, daysFromCivil, epochDayFromIso } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { dayOfWeek } from "./dates_day_of_week.ts";  ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { type IsoWeek } from "./dates_week_number_types.ts";

/**
 * 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.
 */
export function weekNumber(iso: string): IsoWeek {
  const thursday = epochDayFromIso(iso) + (4 - dayOfWeek(iso));
  const weekYear = civilFromDays(thursday).year;
  const week = Math.floor((thursday - daysFromCivil(weekYear, 1, 1)) / 7) + 1;
  return {
    weekYear,
    week,
    label: `${String(weekYear).padStart(4, "0")}-W${String(week).padStart(2, "0")}`,
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, 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.week-number
Download for TypeScript dates.week-number-1.0.0-typescript.fune · 6,622 bytes sha256 3aa453a07e537a798b81d7e0539e41cfc8abe4d5ed83ee588d86bbb0d3888767

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./dates.week-number-1.0.0-typescript.fune, or fetch it from a terminal with fune pull dates.week-number@1.0.0:typescript.

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md1,020
impl/python.py687
impl/rust.rs1,149
impl/typescript.ts768
vectors.json2,600