← 中文资源

简体中文版本

ISO 周数与周起始日期

计算 ISO 8601 周数,并把年份加周数反解为该周周一的日期,附带边界校验。

阅读英文原文

功能说明

该示例按 ISO 8601 规则计算某个日期所属的年份与周数,并支持从年份加周数反推出该周的周一。ISO 周从周一开始,包含当年第一个周四的那一周为第 1 周,因此跨年日期可能属于上一年的最后一周或下一年的第 1 周。它适合周报、排班、按周聚合指标等场景。

关键代码

isoWeek 先把日期归一到 UTC 的日粒度,再找到当周周四。把周四所在的年份作为 ISO 年,并计算该年第一个周四,两个周四之间相差的周数加一就是周号。isoWeekStart 以 1 月 4 日一定落在第 1 周为锚点,回退到周一,再加上周偏移,最后调用 isoWeek 做往返校验,不存在的第 53 周会抛 RangeError。

使用注意

所有计算都基于 UTC,避免本地时区与夏令时导致日期偏移。year 限制在 1 到 9999,week 限制在 1 到 53,但并非每年都有第 53 周,因此反解后必须校验。返回的周一是 UTC 零点,如果需要本地日历日期,应再按目标时区转换。ISO 周与日历周、财务周的定义不同,不要混用。

验证方式

断言 2026 年 1 月 1 日属于 2026 年第 1 周,2024 年 12 月 30 日属于 2025 年第 1 周。对第 1 周反解得到 2025 年 12 月 29 日。测试不存在的第 53 周抛 RangeError,非法年份与周数也抛 RangeError,并验证同一周内任意一天返回相同周号。

代码示例

export function isoWeek(date = new Date()) {
  const value = toUtcDate(date);
  const thursday = new Date(value.getTime());
  const weekday = value.getUTCDay() || 7;
  thursday.setUTCDate(value.getUTCDate() + 4 - weekday);
  const year = thursday.getUTCFullYear();
  const firstThursday = new Date(Date.UTC(year, 0, 4));
  const offset = firstThursday.getUTCDay() || 7;
  firstThursday.setUTCDate(firstThursday.getUTCDate() + 4 - offset);
  const week = 1 + Math.round((thursday - firstThursday) / (7 * 24 * 60 * 60 * 1000));
  return { year, week };
}

export function isoWeekStart(year, week) {
  if (!Number.isInteger(year) || year < 1 || year > 9999) throw new RangeError("year out of range");
  if (!Number.isInteger(week) || week < 1 || week > 53) throw new RangeError("week out of range");
  const januaryFourth = new Date(Date.UTC(year, 0, 4));
  const weekday = januaryFourth.getUTCDay() || 7;
  const monday = new Date(januaryFourth.getTime());
  monday.setUTCDate(januaryFourth.getUTCDate() - weekday + 1 + (week - 1) * 7);
  const check = isoWeek(monday);
  if (check.year !== year || check.week !== week) {
    throw new RangeError("week does not exist in that ISO year");
  }
  return monday;
}

function toUtcDate(date) {
  if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
    throw new TypeError("date must be a valid Date");
  }
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}

const week = isoWeek(new Date("2026-01-01T00:00:00Z"));
console.log(week);
console.log(isoWeekStart(week.year, week.week).toISOString().slice(0, 10));
console.log(isoWeek(new Date("2024-12-30T00:00:00Z")));