Get the ISO Week Number of a Date in JavaScript

To get the ISO week number, read Temporal.PlainDate.from(date).weekOfYear (with yearOfWeek for the week-based year) — it implements the ISO 8601 rule that trips up hand-written formulas. Part of Date Arithmetic Without Mutations.

Why this scenario is tricky

ISO week numbering has a reputation for being confusing, and the reputation is deserved — not because the rules are complicated, but because they defy the intuition that "the week number resets on January 1." Under ISO 8601 a week always runs Monday to Sunday, and week 1 of a year is defined as the week containing that year's first Thursday. An equivalent way to say it is that week 1 is the week containing January 4, since January 4 is always in the first week no matter which day it falls on. The consequence is that the first few days of January can belong to the last week of the previous year, and the last few days of December can belong to week 1 of the next year.

That is why a correct implementation must expose two values, not one: the week number and the week-based year. The date January 1, 2026 might report as week 53 of week-year 2025, even though its calendar year is 2026. If you display only the week number you produce a label like "W53" attached to a 2026 date, which is not wrong exactly but is uninterpretable without the year that goes with it. Pairing weekOfYear with yearOfWeek is what makes the label unambiguous, and it is the single most common thing people get wrong when they roll their own week logic.

Historically, computing this by hand was error-prone enough that developers copied cryptic one-liners from forums, most of which were subtly broken around the year boundary or for years with 53 weeks. Temporal removes the guesswork by building the ISO rules into the calendar itself: weekOfYear and yearOfWeek return the specification-correct values directly, so you never reimplement the Thursday rule. The remaining job is simply to combine them into whatever label your domain needs.

ISO week numbering has two rules that break naive code: weeks start on Monday, and week 1 is the week containing the year's first Thursday. So 1 January can belong to week 52 or 53 of the previous year, and 31 December can belong to week 1 of the next. weekOfYear and yearOfWeek encode both rules.

Year-boundary weeks are the trap1 Jan can be week 52 of last yearYear-boundary weeks are the trap(dayOfYear/7) formulawrong near year edgesweekOfYear + yearOfWeekISO-correctThe week-based year can differ from the calendar year at both ends.

Minimal working solution

Temporal.PlainDate exposes the two properties the ISO standard requires. weekOfYear gives the 1-based week number, which ranges from 1 to either 52 or 53 depending on the year, and yearOfWeek gives the week-based year that the number is counted within. Reading them off a PlainDate is all it takes, because the polyfill has already applied the Monday-start and first-Thursday rules. There is no arithmetic for you to write and no boundary case for you to special-case — the values are correct by construction, including for the awkward dates at the very start and end of the year.

The reason both properties matter becomes obvious the moment you inspect a boundary date. For January 1, 2026, weekOfYear may be 1 or it may be 53, and only yearOfWeek tells you which year that count belongs to. Always read them as a pair. Treating weekOfYear in isolation — for instance, grouping records by week number alone across a year boundary — silently merges week 53 of one year with week 53 of another, or splits a single ISO week across two buckets. Carrying the week-based year alongside keeps each ISO week distinct.

import { Temporal } from '@js-temporal/polyfill';
const d = Temporal.PlainDate.from('2026-01-01');
d.weekOfYear;  // 1  (or 52/53 depending on the year)
d.yearOfWeek;  // the ISO week-based year

Week fieldsweekOfYear and yearOfWeek off a PlainDateWeek fieldsPlainDateweekOfYearyearOfWeek

Full production version

The canonical string form of an ISO week is YYYY-Www, for example 2026-W01, and building it is a matter of combining yearOfWeek with a zero-padded weekOfYear. Using yearOfWeek rather than the calendar year in that string is the crucial detail: it is what makes December 29, 2025 correctly render as 2026-W01 if it falls in the next year's first ISO week, so the label sorts and groups correctly. Zero-padding the week number to two digits keeps the strings lexicographically sortable, which means you can order or bucket them as plain text without parsing them back into dates.

This week label is the natural key for time-series aggregation. Reporting dashboards that summarize activity "by week" want a stable, sortable identifier for each week that is consistent regardless of the server's time zone, and the YYYY-Www string is exactly that. Because it is derived from a zoneless PlainDate, two servers in different zones that process the same civil date produce the same week label, so a distributed pipeline does not disagree with itself about which week a record belongs to. If your data arrives as ZonedDateTime or Instant, reduce it to the PlainDate in the reporting zone first, then take the week label, so the bucketing matches the calendar your users actually see.

For the full ISO week-date representation you can also read dayOfWeek to get the day within the week (1–7), giving the complete YYYY-Www-D form. That is rarely needed for reporting but occasionally matters for interoperability with systems that speak ISO week dates natively, such as certain industrial and financial scheduling formats. Having all three components — week-year, week, and weekday — available directly from the PlainDate means you can emit any of these standard forms without reaching for a separate date library.

import { Temporal } from '@js-temporal/polyfill';
// Format an ISO week label like '2026-W01'.
function isoWeek(d: Temporal.PlainDate): string {
  return `${d.yearOfWeek}-W${String(d.weekOfYear).padStart(2, '0')}`;
}

ISO week labelCombine week-based year and weekISO week labelPlainDateyearOfWeekweekOfYear'2026-W01'

Verification snippet

The tests that matter are all at the year boundary, because the interior of the year is unambiguous and rarely wrong. Assert that a January date which the ISO rules place in the previous year's final week reports the previous year via yearOfWeek and week 52 or 53 via weekOfYear — January 1, 2023, for instance, falls in week 52 of week-year 2022. Assert the mirror case: a late-December date that belongs to the next year's week 1 reports the next year and week 1. These two cases prove the pairing of week number and week-based year is being read and combined correctly.

Add a 53-week year to the suite. Most years have 52 ISO weeks, but a year whose first day is a Thursday (or a leap year starting on a Wednesday) has 53, and a common bug is to assume the maximum is always 52 and clamp or wrap incorrectly. Pick such a year and assert that its last week reports 53. Finally, assert that the YYYY-Www label is zero-padded and uses yearOfWeek, and run the whole suite under several TZ values to confirm the zoneless computation is host-independent.

ISO Week Number assertionsKey cases assert correctlyAssertions that prove the edge case2026-01-01week 12027-01-01week 53 of 2026first Thursday ruleweek 1label'YYYY-Www'

Common pitfalls

The dominant pitfall is using the calendar year instead of the week-based year when forming a week label. A date's .year and its .yearOfWeek differ by one for a handful of days at each year boundary, and using the wrong one produces labels like 2026-W53 that no downstream system can reconcile. Always pair weekOfYear with yearOfWeek. The second common mistake is assuming a US or other locale week convention — weeks starting on Sunday, or week 1 being simply "the week containing January 1" — which is a different numbering from ISO 8601 and will disagree with it by up to a week near the boundaries. If your product needs the US convention, compute it deliberately; do not assume weekOfYear provides it, because it follows ISO.

A third trap is grouping time-series data by weekOfYear alone. Across a multi-year dataset that silently collapses week 10 of every year into one bucket, or worse, mixes the boundary weeks of adjacent years. The fix is to bucket by the full YYYY-Www string, which is unique per ISO week. A related issue is sorting: sort week labels as strings only if they are zero-padded, since 2026-W9 sorts after 2026-W10 lexicographically while 2026-W09 sorts correctly. Padding to two digits is a one-line safeguard that prevents a whole class of ordering bugs.

Finally, beware of manual reimplementations copied from the pre-Temporal era. Many of the widely-shared week-number snippets are subtly wrong for 53-week years or for the first days of January, precisely because their authors did not account for the week-based year. Now that weekOfYear and yearOfWeek are available directly, there is no reason to carry that risk — delete the hand-rolled version and read the values from the PlainDate.

ISO Week Number pitfallsCommon mistakes and their fixesWrongRightdayOfYear / 7ignores Monday startweekOfYear propertyISO ruleuse calendar yearwrong at boundariespair with yearOfWeekcorrect label

Frequently Asked Questions

How do I get the ISO 8601 week number of a date?

Read the weekOfYear property of a Temporal.PlainDate, and pair it with yearOfWeek for the week-based year. These implement the ISO rules — weeks start Monday and week 1 contains the first Thursday — so you never write the fiddly formula yourself.

Why does 1 January sometimes report week 52 or 53?

Under ISO 8601, week 1 is the week containing the year's first Thursday, so early-January days can belong to the last week of the previous week-based year. That is why you use yearOfWeek rather than the calendar year when labelling the week.

Why does January 1 sometimes report as week 52 or 53 of the previous year?

Because ISO 8601 defines week 1 as the week containing the year's first Thursday (equivalently, the week containing January 4), and weeks run Monday to Sunday. If January 1 falls late in a week — on a Friday, Saturday, or Sunday — that week's Thursday belonged to December, so the days up to the following Monday are still in the previous year's final week. Temporal reports this correctly by pairing weekOfYear with yearOfWeek, which names the week-based year the count belongs to.

What is the difference between weekOfYear and yearOfWeek?

weekOfYear is the 1-based ISO week number (1 to 52 or 53). yearOfWeek is the week-based year that number is counted within, which can differ from the calendar year by one for the few boundary days at the start and end of January and December. You must read them together: a week number without its week-based year is ambiguous at the year boundary. The canonical label YYYY-Www is built from yearOfWeek plus a zero-padded weekOfYear.

How do I build a sortable weekly bucket key from a date?

Combine yearOfWeek with weekOfYear zero-padded to two digits, giving the ISO form like 2026-W01. Using yearOfWeek rather than the calendar year makes boundary dates group into the correct ISO week, and zero-padding keeps the strings lexicographically sortable so you can order or aggregate them as plain text. Because the value comes from a zoneless PlainDate, servers in different time zones produce the same key for the same civil date.