"use strict"; // Day-number kernel: Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms", // https://howardhinnant.github.io/date_algorithms.html (days_from_civil / civil_from_days). // ISO week numbering rules per ISO 8601 (cited below): weeks run Monday to Sunday, and week 1 // of a year is the week containing that year's first Thursday. No host clock, no Date.now(). function daysFromCivil(y, m, d) { y = m <= 2 ? y - 1 : y; const era = Math.floor((y >= 0 ? y : y - 399) / 400); const yoe = y - era * 400; const doy = Math.floor((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5) + d - 1; const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy; return era * 146097 + doe - 719468; } function civilFromDays(z) { z += 719468; const era = Math.floor((z >= 0 ? z : z - 146096) / 146097); const doe = z - era * 146097; const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365); const y = yoe + era * 400; const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100)); const mp = Math.floor((5 * doy + 2) / 153); const d = doy - Math.floor((153 * mp + 2) / 5) + 1; const m = mp + (mp < 10 ? 3 : -9); const yy = y + (m <= 2 ? 1 : 0); return { year: yy, month: m, day: d }; } const EPOCH_JDN = 2440588; function toJDN(y, m, d) { return daysFromCivil(y, m, d) + EPOCH_JDN; } function fromJDN(jdn) { return civilFromDays(jdn - EPOCH_JDN); } function isLeapGregorian(y) { return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0); } function daysInMonth(y, m) { const dim = [31, isLeapGregorian(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; return dim[m - 1]; } function parseISODate(s, label) { const m = /^(-?\d{4})-(\d{2})-(\d{2})$/.exec(String(s).trim()); if (!m) throw new Error((label || "date") + " must be an ISO 8601 date YYYY-MM-DD, got: " + s); const year = parseInt(m[1], 10), month = parseInt(m[2], 10), day = parseInt(m[3], 10); if (month < 1 || month > 12) throw new Error((label || "date") + " month out of range 1-12"); if (day < 1 || day > daysInMonth(year, month)) throw new Error((label || "date") + " day out of range for that month"); return { year, month, day }; } const WEEKDAY_NAMES = ["Thursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"]; const ISO_ORDER = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; function isoWeekday(y, m, d) { const z = daysFromCivil(y, m, d); const name = WEEKDAY_NAMES[((z % 7) + 7) % 7]; return ISO_ORDER.indexOf(name) + 1; } module.exports = { slug: "iso-week-number", title: "ISO week number", deck_category: "dates", description: "Computes the ISO 8601 week-numbering year and week number for a given date.", inputs: [ { name: "date", type: "string", format: "date", required: true, description: "ISO 8601 date, YYYY-MM-DD" } ], outputs: [ { name: "iso_year", description: "ISO week-numbering year (may differ from the calendar year near year boundaries)" }, { name: "iso_week", unit: "week", description: "ISO week number, 1 to 53" } ], steps: [ "Parse the date as a proleptic Gregorian civil date and find its ISO weekday (Monday=1 .. Sunday=7) from the day-number modulo 7 (Hinnant days_from_civil, cited above), calibrated against 1970-01-01 being a Thursday.", "Find the Thursday of the same ISO week: subtract (iso_weekday - 4) days from the date's day-number.", "The ISO week-numbering year is the calendar year that Thursday falls in.", "iso_week = floor((Thursday's day-number - day-number of January 1 of the ISO week-numbering year) / 7) + 1." ], constants: [], sources: [ { kind: "standard", value: "ISO 8601:2004", title: "Data elements and interchange formats -- Information interchange -- Representation of dates and times", publisher: "International Organization for Standardization", retrieved_utc: "2026-09-30T19:41:35Z", note: "defines the week-date system: weeks run Monday to Sunday, week 1 contains the year's first Thursday" }, { kind: "url", value: "https://howardhinnant.github.io/date_algorithms.html", title: "chrono-Compatible Low-Level Date Algorithms", publisher: "Howard Hinnant", retrieved_utc: "2026-09-30T19:41:35Z", note: "day-number kernel used for the weekday and week arithmetic" } ], disclaimer: null, questionText(inputs) { return "What is the ISO 8601 week number for " + inputs.date + "?"; }, run(inputs) { const d = parseISODate(inputs.date, "date"); const jdn = toJDN(d.year, d.month, d.day); const iso = isoWeekday(d.year, d.month, d.day); const thursdayJdn = jdn - (iso - 4); const thu = fromJDN(thursdayJdn); const isoYear = thu.year; const jan1Jdn = toJDN(isoYear, 1, 1); const isoWeek = Math.floor((thursdayJdn - jan1Jdn) / 7) + 1; const answer = isoYear + "-W" + String(isoWeek).padStart(2, "0"); return { answer, values: { iso_year: isoYear, iso_week: isoWeek } }; }, examples: [ { name: "worked example", inputs: { date: "2024-01-01" }, expected: { answer: "2024-W01" }, check: "2024-01-01 is a Monday, so it starts the ISO week containing the year's first Thursday (Jan 4), which is week 1" }, { name: "test vector: date belongs to prior ISO year", inputs: { date: "2016-01-01" }, expected: { answer: "2015-W53" }, check: "2016-01-01 is a Friday; its week's Thursday (Dec 31, 2015) falls in 2015, and 2015 has 53 ISO weeks" }, { name: "test vector: year with 53 ISO weeks", inputs: { date: "2020-12-31" }, expected: { answer: "2020-W53" }, check: "2020-12-31 is a Thursday, so it is the anchor day of its own ISO week; 2020 is a 53-week ISO year" } ] };