如何把周数转换为日期范围——完整指南
有人给你发来排期:“第 23 周交付(W23)。”你打开日历盯着看:这到底对应哪些日期?
周数在项目计划、制造业、零售和物流中无处不在——但大多数人只把它当作标签。把周数反推回具体的日历日期并没有那么直观,而且跨年边界的情况会让连经验丰富的开发者都踩坑。
本指南涵盖完整换算:从周数得到周一,从周数得到完整日期范围(周一–周日),以及反向(日期 → 周数)。并提供 Python、JavaScript、Excel 和 SQL 的示例代码。
周数到底告诉了你什么
在开始计算之前,你必须先知道这个周数来自哪一种“周编号系统”。
ISO 8601(国际标准): 第 1 周是包含当年“第一个星期四”的那一周。周从周一到周日。一年有 52 或 53 个 ISO 周。关键点:ISO 的“周年份(week year)”可能与公历年份不同。12 月最后几天有时属于下一年的第 1 周。
美式(US-style): 第 1 周包含 1 月 1 日。周可能是周日到周六,或周一到周日(取决于工具/地区设置)。1 月 1 日永远在第 1 周,没有例外。
本指南后续提到的“周数”,除非特别说明,默认指 ISO 8601。如果别人给你的周数来自美式工具(例如 Excel WEEKNUM 的默认模式),换算公式会不同——见下文相关章节。
你还必须知道年份。 只有“第 23 周”是没有意义的。务必把周数当成一对 (year, week)。2025 年第 23 周从 6 月 2 日开始;2026 年第 23 周从 6 月 1 日开始。2026 年第 1 周甚至从 2025 年 12 月 29 日开始。
核心公式:ISO 周数 → 周一
每个 ISO 周都从周一开始。要找到年份 Y 的第 W 周的周一:
Monday = Jan 4 of Y + (W - 1) × 7 days - weekday(Jan 4 of Y) + 1
思路是:1 月 4 日一定在 ISO 第 1 周(按定义,它落在一年最初 4 天内,保证那一周包含星期四)。先找到包含 1 月 4 日那一周的周一,然后向前推进 (W - 1) 周。
一个更清晰的等价写法:
Monday of W1 = Jan 4 of Y - weekday(Jan 4 of Y) + 1
Monday of W = Monday of W1 + (W - 1) × 7
其中 weekday 返回周一=1 到周日=7(ISO 的 weekday 编号)。
示例:2026 年第 23 周
- 2026 年 1 月 4 日是周日。ISO weekday = 7。
- 第 1 周的周一:1 月 4 日 − 7 + 1 = 2025 年 12 月 29 日。
- 第 23 周的周一:2025-12-29 + 22 × 7 = 2025-12-29 + 154 天 = 2026 年 6 月 1 日。
- 第 23 周的周日:6 月 1 日 + 6 = 2026 年 6 月 7 日。
2026 年第 23 周范围是 2026-06-01(周一)到 2026-06-07(周日)。
跨年边界陷阱
最常见的错误:以为年份 Y 的第 1 周一定从年份 Y 的某一天开始。
并不是。
第 1 周是包含当年第一个星期四的那一周。如果那个星期四在 1 月初,那么第 1 周的周一可能落在上一年的 12 月。
示例:
| ISO 周 | 年 | 周一 | 周日 |
|---|---|---|---|
| W1 | 2025 | 2024 年 12 月 30 日 | 2025 年 1 月 5 日 |
| W1 | 2026 | 2025 年 12 月 29 日 | 2026 年 1 月 4 日 |
| W1 | 2027 | 2027 年 1 月 4 日 | 2027 年 1 月 10 日 |
| W1 | 2028 | 2028 年 1 月 3 日 | 2028 年 1 月 9 日 |
2025 年第 1 周从 2024 年 12 月开始。2026 年第 1 周从 2025 年 12 月开始。
这意味着:
- 2025 年 12 月 29–31 日属于 2026 年的 ISO 第 1 周,而不是 2025 年。
- 如果有人说“在 2026-W1 之前交付”,截止周其实是 2025-12-29 开始的那一周。
反向陷阱也存在:某些年份的最后几天会落入下一年的第 1 周,而不是本年的最后一周。给某一周打标签时,务必使用 ISO 周年份(Python 中的 %G,PostgreSQL 中的 isoyear),而不是公历年份。
任意周数的完整日期范围
一旦得到周一,本周其余日期就很简单:
| 星期 | 相对周一的偏移 |
|---|---|
| 周一 | +0 |
| 周二 | +1 |
| 周三 | +2 |
| 周四 | +3 |
| 周五 | +4 |
| 周六 | +5 |
| 周日 | +6 |
第 W 周的日期范围是:[周一, 周一 + 6 天]。
如果只关心工作日(周一–周五):[周一, 周一 + 4 天]。
Python
from datetime import date, timedelta
def iso_week_to_monday(year: int, week: int) -> date:
# Jan 4 is always in ISO week 1
jan4 = date(year, 1, 4)
# Move back to Monday of that week
week1_monday = jan4 - timedelta(days=jan4.weekday())
# Step forward to the target week
return week1_monday + timedelta(weeks=week - 1)
def iso_week_to_range(year: int, week: int) -> tuple[date, date]:
monday = iso_week_to_monday(year, week)
sunday = monday + timedelta(days=6)
return monday, sunday
# Examples
monday, sunday = iso_week_to_range(2026, 23)
print(monday) # 2026-06-01
print(sunday) # 2026-06-07
monday, sunday = iso_week_to_range(2026, 1)
print(monday) # 2025-12-29 ← note: starts in 2025
print(sunday) # 2026-01-04
Python 也内置了反向转换(日期 → ISO 周):
d = date(2026, 6, 4)
iso_year, iso_week, iso_weekday = d.isocalendar()
print(iso_year, iso_week) # 2026 23
当你需要 ISO 年份时,使用 d.isocalendar().year(不要用 d.year)——在跨年边界它们会不同。
Python 3.8+ 还可以用 fromisocalendar 反向获取:
# Get Monday of ISO week 23, 2026 directly
monday = date.fromisocalendar(2026, 23, 1) # weekday 1 = Monday
print(monday) # 2026-06-01
如果你使用 Python 3.8 或更高版本,date.fromisocalendar(year, week, weekday) 是最干净的方案。
JavaScript
JavaScript 没有原生 ISO 周支持。可以手写实现:
function isoWeekToMonday(year, week) {
// Jan 4 is always in ISO week 1
const jan4 = new Date(year, 0, 4)
const dayOfWeek = jan4.getDay() || 7 // convert Sun=0 to 7
const week1Monday = new Date(jan4)
week1Monday.setDate(jan4.getDate() - dayOfWeek + 1)
const monday = new Date(week1Monday)
monday.setDate(week1Monday.getDate() + (week - 1) * 7)
return monday
}
function isoWeekToRange(year, week) {
const monday = isoWeekToMonday(year, week)
const sunday = new Date(monday)
sunday.setDate(monday.getDate() + 6)
return { monday, sunday }
}
// Examples
const { monday, sunday } = isoWeekToRange(2026, 23)
console.log(monday.toISOString().slice(0, 10)) // 2026-06-01
console.log(sunday.toISOString().slice(0, 10)) // 2026-06-07
const w1 = isoWeekToRange(2026, 1)
console.log(w1.monday.toISOString().slice(0, 10)) // 2025-12-29
使用 date-fns:
import { setISOWeek, setISOWeekYear, startOfISOWeek, endOfISOWeek } from 'date-fns'
function isoWeekToRange(year, week) {
let d = new Date(year, 0, 4) // any date in the target year
d = setISOWeekYear(d, year)
d = setISOWeek(d, week)
return {
monday: startOfISOWeek(d),
sunday: endOfISOWeek(d)
}
}
date-fns 能正确处理所有边界情况,在生产环境更推荐。
Excel 和 Google Sheets
Excel 没有直接的“周数 → 日期”函数,但可以用公式拼出来。
年份 Y 的 ISO 第 W 周的周一:
=DATE(Y,1,4) - WEEKDAY(DATE(Y,1,4),2) + 1 + (W-1)*7
拆解一下:
DATE(Y,1,4)— 当年 1 月 4 日WEEKDAY(DATE(Y,1,4),2)— 星期几,其中周一=1,周日=7- 相减得到第 1 周的周一
- 加上
(W-1)*7移动到目标周
要得到完整范围,周日就是:
=Monday_formula + 6
表格示例(A1=年份,B1=周数):
周一: =DATE(A1,1,4) - WEEKDAY(DATE(A1,1,4),2) + 1 + (B1-1)*7
周日: =Monday_cell + 6
把单元格格式设置为日期。对于 2026 年第 1 周,这个公式会返回 2025 年 12 月 29 日作为周一——这是正确的。
反向(日期 → ISO 周数): 使用 ISOWEEKNUM(date)。ISO 周年份在 Excel 里没有原生函数,需要用 ISOWEEKNUM 相关的 workaround 来计算。
SQL
PostgreSQL 的 ISO 支持最完整:
-- Monday of ISO week W in year Y
SELECT
make_date(2026, 1, 4)
+ (23 - 1) * 7 -- step to week 23
- EXTRACT(isodow FROM make_date(2026, 1, 4))::int + 1
AS week_monday;
-- 2026-06-01
-- Or more cleanly using generate_series for a range lookup
SELECT
d::date AS week_monday,
(d + 6)::date AS week_sunday,
EXTRACT(isoyear FROM d) AS iso_year,
EXTRACT(week FROM d) AS iso_week
FROM generate_series(
DATE '2026-01-01',
DATE '2026-12-31',
INTERVAL '7 days'
) d
WHERE EXTRACT(week FROM d) = 23
AND EXTRACT(isoyear FROM d) = 2026;
更简洁的方法是使用 PostgreSQL 的 ISO 格式解析:
-- Convert ISO year + week to Monday
SELECT to_date('2026' || '23' || '1', 'IYYYIWid') AS monday;
-- 2026-06-01
-- Format: IYYY=ISO year, IW=ISO week, id=ISO day (1=Monday)
这是最简单的 PostgreSQL 写法:构造 YYYYWWD 字符串并用 ISO 格式码解析。
MySQL:
-- Monday of ISO week 23, 2026
SELECT STR_TO_DATE('202623 Monday', '%X%V %W');
-- 2026-06-01
-- Or step-based:
SELECT DATE_ADD(
DATE_ADD(STR_TO_DATE('2026-01-04', '%Y-%m-%d'),
INTERVAL (-(WEEKDAY(STR_TO_DATE('2026-01-04', '%Y-%m-%d')))) DAY),
INTERVAL (23 - 1) * 7 DAY
) AS week_monday;
SQL Server:
-- Monday of ISO week 23, 2026
DECLARE @year INT = 2026, @week INT = 23
SELECT DATEADD(
DAY,
(@week - 1) * 7
- (DATEPART(WEEKDAY, DATEFROMPARTS(@year, 1, 4)) + 5) % 7,
DATEFROMPARTS(@year, 1, 4)
) AS week_monday;
-- 2026-06-01
美式(US-style)周数 → 日期
如果你的周数来自美式系统(第 1 周包含 1 月 1 日,周从周日或周一开始),公式更简单,因为没有跨年边界问题:
周起始日(以周日为一周开始):
Week_start = DATE(Y, 1, 1) + (W - 1) × 7 - WEEKDAY(DATE(Y, 1, 1), 1)
其中 WEEKDAY 返回周日=1 到周六=7。
不过在排期和物流里,美式周数远不如 ISO 常见。如果有人只给你周数却不说明系统,尤其在国际/欧洲业务语境中,默认按 ISO 理解通常更安全。
2026 快速参考:ISO 第 1–10 周与第 48–53 周
| ISO 周 | 年 | 周一 | 周日 |
|---|---|---|---|
| W1 | 2026 | 2025-12-29 | 2026-01-04 |
| W2 | 2026 | 2026-01-05 | 2026-01-11 |
| W3 | 2026 | 2026-01-12 | 2026-01-18 |
| W4 | 2026 | 2026-01-19 | 2026-01-25 |
| W5 | 2026 | 2026-01-26 | 2026-02-01 |
| W6 | 2026 | 2026-02-02 | 2026-02-08 |
| W7 | 2026 | 2026-02-09 | 2026-02-15 |
| W8 | 2026 | 2026-02-16 | 2026-02-22 |
| W9 | 2026 | 2026-02-23 | 2026-03-01 |
| W10 | 2026 | 2026-03-02 | 2026-03-08 |
| ... | |||
| W48 | 2026 | 2026-11-23 | 2026-11-29 |
| W49 | 2026 | 2026-11-30 | 2026-12-06 |
| W50 | 2026 | 2026-12-07 | 2026-12-13 |
| W51 | 2026 | 2026-12-14 | 2026-12-20 |
| W52 | 2026 | 2026-12-21 | 2026-12-27 |
| W53 | 2026 | 2026-12-28 | 2027-01-03 |
注意:2026 年第 1 周从 2025-12-29 开始。注意:2026 年第 53 周到 2027-01-03 结束——2026 年是一个 53 周的年份。
常见错误汇总
不带年份。 “第 8 周”是含糊的。总是用 2026-W08 或等效格式。
用公历年份而不是 ISO 周年份。 2025-12-29 属于 2026 年 ISO 第 1 周,而不是 2025 年。把它存到 2025 的桶里会错分。
以为第 1 周从 1 月 1 日开始。 很多年份第 1 周从 12 月 29/30/31 开始。如果你需要 1 月 1 日永远属于第 1 周,那你用的是美式系统,不是 ISO。
周一公式 off-by-one。 Jan 4 这个锚点很关键——不是 1 月 1 日(它可能属于上一年的最后一周)。用 1 月 1 日做锚点在某些年份会错。
不测试跨年日期。 任何日期处理代码都应使用 12 月 28–31 和 1 月 1–4 进行测试。这几天集中了几乎所有边界情况。
使用 ISO Week Number Calculator 查询任意周数对应的日期范围,或查看 current week number 与本周的 ISO 日期范围。