By Ether DataRequest data sample
All sections

Slot to UTC Window

A slot paired with an ISO week resolves deterministically to a one-hour UTC interval; reporting must honor the requested grain exactly or reject the request, never silently roll it up.

stable4 min read
Source time
hour_of_week_slot, iso_week
Destination time
utc_interval
exact

This is the inverse of Timestamp to slot: given a canonical (ISO week x slot) coordinate, recover the concrete UTC interval it names. Unlike the forward conversion, this direction carries no DST ambiguity at all — it is pure, deterministic arithmetic over a timeline that has no gaps or folds in it, because UTC itself does not observe daylight saving time.

Purpose

Convert a hour_of_week_slot (0–167) plus an ISO week (isoYear, isoWeek) into the concrete utc_interval — a half-open [start, end) range spanning exactly one UTC hour — that the slot names within that specific week. This is the conversion that turns an abstract, repeating coordinate back into a schedulable, queryable moment: "run this campaign during 2026-W30-S069" only means something once it is resolved to [2026-07-22T21:00:00Z, 2026-07-22T22:00:00Z).

Source and destination

source
hour_of_week_slot (0-167) + iso_week (isoYear, isoWeek)
destination
utc_interval: a half-open [start, end) UTC range, exactly one hour wide
exactness
exact — pure arithmetic, no zone resolution, no DST hazard
params
isoYear (number), isoWeek (1-53), slot (0-167, normalized if out of range)
outputs
startUtc (epoch ms / ISO string), endUtc (startUtc + 3,600,000 ms)
units
instants in epoch milliseconds; interval width is exactly one UTC hour (3,600,000 ms)

Algorithm

import { isoWeekStartUtc } from "@/lib/time/isoweek";
import { slotToInstant, normalizeSlot } from "@/lib/time/slot";

function slotToUtcWindow(
  isoYear: number,
  isoWeek: number,
  slot: number,
): { startUtc: string; endUtc: string; startMs: number; endMs: number } {
  const weekStartMs = isoWeekStartUtc(isoYear, isoWeek); // Monday 00:00 UTC
  const startMs = slotToInstant(weekStartMs, normalizeSlot(slot));
  const endMs = startMs + 3_600_000; // exactly one UTC hour, always
  return {
    startUtc: new Date(startMs).toISOString(),
    endUtc: new Date(endMs).toISOString(),
    startMs,
    endMs,
  };
}

// The inverse of the worked example on "The 168 axis":
const window = slotToUtcWindow(2026, 30, 69);
// window.startUtc -> "2026-07-22T21:00:00.000Z"
// window.endUtc   -> "2026-07-22T22:00:00.000Z"

isoWeekStartUtc anchors the repeating slot to a specific week by resolving Monday 00:00 UTC of that ISO week; slotToInstant then adds slot * 3,600,000 milliseconds. Because both steps operate purely in UTC — no zone lookup, no local calendar — the result is deterministic for every valid (isoYear, isoWeek, slot) triple, and normalizing the slot with normalizeSlot makes the function total over any integer input rather than throwing on an out-of-range value.

No-silent-rollup

Report at the requested grain or reject

If a caller asks for the UTC window of a specific slot and the serving system can only resolve to a day or week boundary — for example, a reporting table that only stores daily rollups — the correct response is an explicit rejection or a renegotiated grain, never a silently widened window. A caller who asked for a one-hour window and received an undisclosed 24-hour window has been given an answer to a coarser question than the one asked, and nothing in the response shape reveals that a substitution occurred.

This mirrors the geo KB's no-silent-rollup rule for cell aggregation: just as a system must never quietly return an H3 R5 cell's centroid when an R8 point was requested, this conversion must never quietly return a day-level window when an hour-level slot was requested. See Resolution and grain for how to negotiate grain up front so this situation is rare rather than a runtime surprise.

Quality and edge cases

The conversion is exact to the millisecond for any valid input, with one qualification: leap seconds. UTC has inserted 27 leap seconds since 1972 (with insertions expected to be phased out by around 2035), making an occasional UTC day 86,401 seconds rather than 86,400; cloud providers that "smear" the leap second across a 24-hour window can disagree with strict UTC by up to roughly half a second during the smear. For hour-of-week bucketing this is immaterial — a half-second discrepancy never crosses an hour boundary — but a system performing sub-second joins against this window (aligning a video frame or a bid-request timestamp to the hour boundary, say) should declare its clock model (UTC, TAI, or a specific smear algorithm) explicitly rather than assume all "UTC" timestamps in a join are measured against the same clock.

No-silent-temporal-rollup is the single most consequential edge case for this conversion precisely because the conversion itself has no failure mode of its own — the arithmetic is exact — so the entire risk surface sits in how the resulting window is reported downstream. Pair every UTC window this conversion returns with the ISO week and slot it was derived from, so a consumer can always verify the window matches the grain it originally requested. See The 168 axis for the slot definition this conversion inverts, and ISO week and the week-slot key for how isoWeekStartUtc anchors the week boundary this window is computed relative to.

Edge cases affecting this page

The 168 AxisConceptual modelISO WeekCalendar & week