By Ether DataRequest data sample
All sections

ISO Week

ISO-8601 defines the week as Monday-start with week 1 containing the first Thursday of the year, producing a week-numbering year that can diverge from the calendar year and years with 53 weeks.

stable5 min read
Source time
instant
Destination time
iso_week, week_slot_key
exact

Purpose

This KB's canonical bucket for a moment is the pair (ISO week, hour-of-week slot) — the "168 axis" coordinate. The slot repeats every week (0-167), so it alone cannot identify a unique moment; it must be paired with an unambiguous week identifier. ISO-8601 defines that identifier: weeks start on Monday, and week 1 of an ISO year is the week containing that year's first Thursday (equivalently: the week containing January 4th, or the first week with at least four days in the new year). Both the week and the slot are computed purely from the UTC instant, so the (isoYear, isoWeek, slot) triple is timezone-independent and reproducible by any system that implements ISO-8601 correctly.

Source and destination

Source: an instant (UTC epoch). Destination: an iso_week (isoYear, isoWeek, isoWeekday) and a week_slot_key — the combined "2026-W29-S045" string.

Exactness: exact

exact. ISO-8601 week numbering is a deterministic function of the calendar date with no external parameters, no DST dependency, and no configurable policy — unlike broadcast day or daypart conversions, there is nothing to declare. The only discipline required is carrying the week-numbering year alongside the week number, because — see below — it is not always the calendar year.

Algorithm

import { isoWeekOf, isoWeekStartUtc, isoWeeksInYear, weekSlotKey, parseWeekSlotKey } from "@/lib/time/isoweek";

isoWeekOf(Date.parse("2026-07-16T12:00:00Z"));
// -> { isoYear: 2026, isoWeek: 29, isoWeekday: 4 }  (Thursday)

// The week-year/calendar-year mismatch around January 1:
isoWeekOf(Date.parse("2027-01-01T00:00:00Z"));
// -> { isoYear: 2026, isoWeek: 53, isoWeekday: 5 }
// 2027-01-01 is a Friday that falls in the LAST ISO week of 2026, not week 1 of 2027,
// because that week's Thursday (2026-12-31) is still in 2026.

// 2026 is a 53-week ISO year:
isoWeeksInYear(2026); // -> 53

// Reverse: the UTC instant that begins a given ISO week.
isoWeekStartUtc(2026, 29); // -> epoch ms of Monday 2026-07-13T00:00:00Z

// The combined (week x slot) key used as the canonical bucket everywhere in this KB.
weekSlotKey(Date.parse("2026-07-16T21:00:00Z"));
// -> { isoYear: 2026, isoWeek: 29, slot: 93, key: "2026-W29-S093" }

parseWeekSlotKey("2026-W29-S045");
// -> { isoYear: 2026, isoWeek: 29, slot: 45, key: "2026-W29-S045" }

Parameters

epochMs
UTC instant, as epoch milliseconds. isoWeekOf and weekSlotKey take only this.
isoYear, isoWeek
For isoWeekStartUtc: the week-numbering year and week number (1-52 or 1-53) to resolve to a starting instant.

Outputs

isoWeekOf returns {isoYear, isoWeek, isoWeekday} (weekday 1=Monday through 7=Sunday). weekSlotKey returns the same plus the UTC hour-of-week slot (0-167) and the canonical string key, formatted YYYY-Www-Sss with zero-padded week and slot (e.g. 2026-W29-S045). isoWeeksInYear returns 52 or 53. isoWeekStartUtc returns the epoch ms of the Monday 00:00 UTC that begins the given week — the anchor that turns a repeating slot into a unique instant (see Slot-to-UTC-Window).

Units and convention

Instants are epoch milliseconds; ISO weekdays are 1 (Monday) through 7 (Sunday); slots follow the KB-wide convention (slot 0 = Monday 00:00 UTC), which is deliberately aligned with the ISO week's Monday start so that slot 0 of any week is always that week's opening instant. The tz engine is luxon, computed here in the UTC zone (ISO week numbering itself has no zone dependency once the instant is fixed).

DST and disambiguation behavior

None. ISO week computation operates on the UTC instant only; DST is a local-time phenomenon that affects how a local wall-clock reading maps to that instant (timestamp-to-slot), not how the instant maps to its ISO week.

Quality and provenance

Because this conversion is exact and parameter-free, the main provenance requirement is structural: never persist or transmit a bare week number. Always carry isoYear alongside isoWeek, and prefer the combined key string as the join key between systems — it is self-describing and avoids the year-boundary bug below by construction.

Edge cases

Week-year boundary: the ISO week-numbering year diverges from the calendar year in the days around January 1 in either direction — 2026-12-31 can fall in 2027-W01, and 2027-01-01 falls in 2026-W53, per the runnable example above. A system that logs "week 53" or "week 1" without its year is unreconcilable across this boundary. Fifty-three-week years: 2026 has 53 ISO weeks (a year has 53 ISO weeks when it starts on a Thursday, or is a leap year starting on Wednesday); code that hardcodes 52 weeks per year will misalign year-over-year comparisons in a 53-week year. Week-numbering systems: ISO is not the only week system in use — see Week Systems for the comparison to US/retail and broadcast weeks. Slot-origin convention: the KB's slot 0 is defined to align with the ISO week's Monday start; a system assuming a Sunday-start week or local-midnight origin will be off by a fixed offset. Date-line weekday divergence: near the International Date Line, a local weekday can differ from the weekday implied by the UTC instant's ISO week — this page's isoWeekday is always the UTC weekday, not any local one.

Python parity

from datetime import datetime, timezone

def iso_week_of(epoch_ms: int) -> tuple[int, int, int]:
    dt = datetime.fromtimestamp(epoch_ms / 1000, tz=timezone.utc)
    iso_year, iso_week, iso_weekday = dt.isocalendar()
    return iso_year, iso_week, iso_weekday

def week_slot_key(epoch_ms: int) -> str:
    iso_year, iso_week, iso_weekday = iso_week_of(epoch_ms)
    dt = datetime.fromtimestamp(epoch_ms / 1000, tz=timezone.utc)
    slot = (iso_weekday - 1) * 24 + dt.hour  # isoWeekday 1=Mon..7=Sun -> Mon0
    return f"{iso_year}-W{iso_week:02d}-S{slot:03d}"

Python's built-in datetime.isocalendar() (3.9+) implements ISO-8601 week numbering natively and agrees with isoWeekOf for every instant, including the 53-week and year-boundary cases above — no library beyond the standard datetime module is required for this conversion. The tested reference implementation remains the TypeScript in lib/time/isoweek.ts.

Edge cases affecting this page

Week SystemsTime systems & zonesThe 168 AxisConceptual model