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.
