☰
Python日期转星期几:weekday、isoweekday与strftime避坑指南
2026/10/21 10:20:25 网站建设 项目流程

简介:面向Python初学者的日期处理PDF,围绕“根据日期返回中文星期几”这一高频需求,系统介绍datetime模块的核心用法。内容先说明导入time与datetime库的必要性,再给出可直接复用的get_week_day()函数,利用weekday()返回的0-6整数配合字典映射到星期一至星期天,同时附带通过datetime.datetime.now()获取当前日期星期的调用示例,并提示weekday()返回值与时区的关系,避免实际使用中的理解偏差。文档还延伸介绍了timetuple、strftime、strptime等方法,为读者后续处理日期格式化与字符串解析提供便捷思路。整个资源包仅含1个PDF文件,大小约27KB,内容精炼,便于离线查阅。已有5936人浏览学习,适合需要在Python脚本中快速实现日期与星期换算、避免从零封装逻辑的开发者和学习者。

1. python根据日期返回星期几:三个容易记混的入口,决定你代码对错

先说说“python根据日期返回星期几的方法”这件事。做考勤系统的某开发者A同学,把整个月的日期批量标注星期几,用datetime.now()跑出来发现所有结果错一天;做排班表的某开发者用字符串格式化拿星期英文名,导出的排班表全是 Saturday、Sunday,业务方根本看不懂;还有人做日报聚合,要判断当天是否周末决定要不要触发补发任务,结果%w和%W只差一个大小写,整周的聚合数据全乱。这三个需求听起来都是“返回星期几”,但 Python 里同一个日期对象有weekday()、isoweekday()、strftime('%w')三种常见取法,返回值范围、起点、字符串格式各不相同,选错一个就是批量数据全错。这篇文章直接把这几个方法、参数差异、中文本地化和常见翻车点讲透,适合刚接触日期处理的 Python 新手,也适合做排班、考勤、报表、定时任务需要批量判定星期几的从业者。

2. 标准库入口:datetime 里取星期几的三个正交方法

2.1 weekday() 与 isoweekday():0 和 1 的差异不是小事

date和datetime对象都自带两个返回星期几整数的方法,这是最直接、最常考的入口。weekday()返回 0 到 6,其中 0 代表周一,6 代表周日;isoweekday()返回 1 到 7,其中 1 代表周一,7 代表周日。两者只差一个偏移量,但混用会造成严重错位。

from datetime import date, timedelta base = date(2024, 11, 11) # 2024-11-11 实际是周一 for offset in range(7): cur = base + timedelta(days=offset) print(cur, "weekday=", cur.weekday(), "isoweekday=", cur.isoweekday())

这段代码从 2024-11-11 开始连续打印 7 天的两种返回值。输出前几行分别是2024-11-11 weekday= 0 isoweekday= 1、2024-11-12 weekday= 1 isoweekday= 2。对比看,isoweekday()就是把weekday()加 1。两者的设计背景不同:weekday()沿用了 Python 早期时间模块的索引习惯,从 0 开始;isoweekday()对齐了 ISO 8601 标准,该标准明确规定周一是一周的第一天。生产环境里我一般优先用isoweekday(),因为它的 1 到 7 更符合业务人员的直觉,写进排班规则不容易被质疑。

参数说明:这两个方法都不接收参数,直接对date或datetime实例调用。如果你手里的是字符串日期,需先用datetime.strptime解析,这一点会在 2.3 节单独演示。注意datetime对象是date的子类,所以两种对象都能调用这两个方法,返回值语义完全一致。

2.2 strftime 格式化:%w、%A、%a 三个占位符的含义完全不同

strftime是统一的日期格式化入口,它同样有三种和星期相关的占位符,但语义差异很大,很多人第一次用时就把%w和weekday()对等起来,结果翻车。

from datetime import date d = date(2024, 11, 11) # 周一 print(d.strftime("%w")) # 输出 1,%w 里 0 代表周日 print(d.strftime("%A")) # 输出 Monday,完整英文名 print(d.strftime("%a")) # 输出 Mon,三字母缩写

逻辑说明:strftime(" %w ")返回一个字符串形式的数字,但这个数字的起点和weekday()相反,它把周日当作 0,周一当作 1,周六当作 6。这个设计来自 C 语言struct tm的tm_wday字段,Python 的strftime为了兼容 C 标准原样继承。所以判断周末如果用%w,条件是result >= 5(周五、周六),而用weekday(),条件是result >= 5(周六、周日)。如果你把两种口径混在一个表达式里,就必然错一天。

参数说明:%A返回完整英文星期名,%a返回缩写,这两个便于直接拼进报表文案。%w返回的是一位数字字符串,不会自动补零,做字符串拼接时要留意'6'和'06'的差异。除这三个之外,%u在部分平台可用,表示周一为 1 的星期数字,但它不是跨平台标准,我不建议在业务代码里依赖它。

2.3 从字符串解析日期再取星期:strptime 的格式串要严格匹配

实际业务里很少直接构造date对象,更多是拿到 "2024-11-11" 或 "2024/11/11" 这样的字符串。此时要先解析再取值,这一步的格式串必须和字符串实际格式完全一致。

from datetime import datetime raw = "2024-11-11" dt = datetime.strptime(raw, "%Y-%m-%d") weekday_num = dt.weekday() weekday_name = dt.strftime("%A") print(weekday_num, weekday_name) # 0 Monday

逻辑说明:strptime是字符串解析方法,第一个参数是待解析文本,第二个参数是格式模板。这里%Y匹配四位年份,%m匹配两位月份,%d匹配两位日期,中间的短横线必须和原始字符串里的分隔符一致。解析出来的是一个datetime对象,所以可以直接调用weekday()和strftime()。

参数说明:如果源字符串是 "2024/11/11",格式串要写成"%Y/%m/%d";如果是 "11/11/2024",则要写成"%m/%d/%Y"。三者混写会直接抛ValueError: time data '2024/11/11' does not match format '%Y-%m-%d'。这个报错本身很好排查,真正容易忽略的是月份和日期的位置调换,比如把 11 月 12 日解析成 12 月 11 日,星期自然也会错。每次解析前,先打印两个样本验证格式串,再跑全量。

3. 中文本地化与批量计算:把星期几变成可用的业务字段

3.1 输出“星期一”而不是 Monday:推荐手写映射表

如果报表要展示中文星期名,strftime没有内置中文支持。常见做法是拿weekday()或isoweekday()的返回值去映射一个中文列表。这里有个细节值得强调:映射表的下标必须和方法的起点对齐,用错起点整列错位。

from datetime import date def weekday_cn(dt): # 依赖 weekday(),0 对应周一,6 对应周日 week_map = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] return week_map[dt.weekday()] print(weekday_cn(date(2024, 11, 11))) # 星期一

这段代码的思路是建立一个从整数到中文名的顺序列表,用weekday()的返回值直接索引。逻辑很直白,但对应关系必须背清楚:weekday()的 0 是周一,不是周日。有些人会误把周日放第一位,导致这一列数据整体错一天。为了避免这种错位,也可以改用isoweekday()版本,把列表第一位占位,让下标 1 对应周一,下标 7 对应周日:

from datetime import date def weekday_cn_iso(dt): # 依赖 isoweekday(),1 对应周一,7 对应周日 week_map = ["占位", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] return week_map[dt.isoweekday()]

参数说明:两个版本的入参都要求是date或datetime对象。如果你传入的是时间戳数字,要先date.fromtimestamp(ts)转换。weekday_cn依赖 0 起始的索引,weekday_cn_iso依赖 1 起始的索引,两者不能混用。若你的代码里有多个模块都在做星期转换,建议只用其中一种并写进团队约定。

3.2 批量输出整月日期与星期对照表:calendar.monthrange 处理大小月

考勤表、排班表、月度台账都经常需要生成“某月所有日期对应星期几”的对照表。最稳妥的起点是calendar.monthrange(year, month),它一次性返回两个值:这个月第一天是星期几、这个月有多少天。用它配合循环,可以正确覆盖 28、29、30、31 天四种情况,不用自己写判断。

from datetime import date import calendar def month_week_table(year, month): # 第一返回值表示当月1号星期几,这里用不到,只取天数 _, days_in_month = calendar.monthrange(year, month) week_map = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] rows = [] for day in range(1, days_in_month + 1): cur = date(year, month, day) rows.append((cur.isoformat(), week_map[cur.weekday()])) return rows for row in month_week_table(2024, 11): print(row)

逻辑说明:核心是把“月份有多少天”的判定交给标准库,而不是自己手写[31, 28, 31...]之类的数组。二月在闰年有 29 天,手写数组很容易漏掉这个边界。monthrange返回的第二个值正是当月的总天数,循环从 1 走到这个值,自然覆盖最大日期。输出里用cur.isoformat()得到 "YYYY-MM-DD" 格式的字符串,方便直接写进 CSV 或 Excel。

参数说明:monthrange的第一个返回值是 0 到 6 的整数,0 代表周一;它和weekday()的起点一致,但不能直接当作某天是星期几用,它只描述“这个月 1 号是星期几”。如果业务里有节假日,这个对照表还可以继续追加一列“是否节假日”,建议在循环外准备一个set存放节假日日期,循环内做cur in holidays判断,性能比每次list遍历好得多。

3.3 判断工作日和周末:一个可复用的函数模板

除了输出“星期几”,更常见的需求是判断某天是否工作日、是否周末。方法就是拿weekday()和阈值 5 比较:weekday()返回 5 和 6 分别对应周六、周日。这个判断可以封装成函数,搭配节假日集合扩展成完整的排班规则。

from datetime import date def is_weekend(dt) -> bool: return dt.weekday() >= 5 def is_workday(dt) -> bool: return dt.weekday() < 5 def is_workday_with_holiday(dt, extra_holidays=(), extra_workdays=()): # extra_holidays 存放法定节假日,extra_workdays 存放调休上班日 if dt in extra_workdays: return True if dt in extra_holidays: return False return dt.weekday() < 5

逻辑说明:is_workday_with_holiday处理的是“法定节假日调休”场景,也就是周中放假、周末补班的月份。函数先判断调休和节假日集合,集合中没有命中再用星期判断。这里的关键是入参类型统一,extra_holidays里的元素必须是date对象,且与传入的dt类型一致,否则会踩到 5.1 节讲的哈希查找坑。

参数说明:extra_holidays和extra_workdays建议传set或frozenset,判断速度比列表快一个量级。调用示例:is_workday_with_holiday(date(2024,10,1), extra_holidays={date(2024,10,1), date(2024,10,2)})。这段代码适合直接进你的工具函数库,排班、任务调度、报表补数都能复用。

4. 不依赖 datetime 的星期计算:蔡勒公式与基姆拉尔森公式

4.1 蔡勒公式实现:搞清公式里的 0 对应哪一天

在某些受限环境里不能用datetime,或者你纯粹想从原理上理解“星期几是怎么算出来的”,可以手推蔡勒公式。这个公式适用于公历,输入年、月、日三个整数,输出一个 0 到 6 的整数。需要注意的是,蔡勒公式原版里 0 对应的是星期六,和 Python 的weekday()起点不同,映射时最容易出错。

def zeller_weekday(year, month, day): # 蔡勒公式,只适用于格里高利历,即公历 1582-10-15 之后的日期 if month < 3: month += 12 year -= 1 K = year % 100 J = year // 100 h = (day + 13 * (month + 1) // 5 + K + K // 4 + J // 4 + 5 * J) % 7 # 公式原版约定:0=周六,1=周日,2=周一,3=周二,4=周三,5=周四,6=周五 week_map = ["周六", "周日", "周一", "周二", "周三", "周四", "周五"] return week_map[h] print(zeller_weekday(2024, 11, 11)) # 周一

逻辑说明:公式里第一步先处理 1 月、2 月,把它们当作上一年的 13 月、14 月来参与运算,因为公历的年和星期基准在这个公式里需要这样的折算。K取年份后两位,J取世纪数,然后代入一个混合表达式,最后对 7 取模。这里注意整除优先级:13 * (month + 1) // 5要先算乘法再整除,不能写成13 * ((month + 1) // 5),否则结果直接错。上面代码的输出之所以正确,是因为week_map的排列刻意对齐了公式原版的 h 含义,0 索引位置上放的是“周六”。

参数说明:公式只处理公历日期。如果往里面传 1582-10-15 之前的日期,结果不会正确,因为历史上儒略历和公历的闰年规则不同。生产环境不建议用这个公式替代datetime,它的性能没有优势,价值在于理解星期计算的数学本质,以及在完全无法导入标准库的极简环境里应急。

4.2 基姆拉尔森公式:先校准偏移量再用,别直接抄

基姆拉尔森公式是另一个常见的手写方案,它和蔡勒公式类似,也需要对 1 月、2 月做年份折算。但这个公式在中文技术博客里的实现版本众多,不同版本对结果 0 和 1 的定义不一致,直接抄很容易错。稳妥做法是拿一个已知日期做校准,再确定偏移量。

def kim_larson_weekday_raw(year, month, day): # 基姆拉尔森公式原始计算,不同版本结果含义不同 if month < 3: month += 12 year -= 1 return (day + 2 * month + 3 * (month + 1) // 5 + year + year // 4 - year // 100 + year // 400 + 1) % 7 # 校准:2024-11-11 是周一,约定周一的 raw 值应该是 1 known = kim_larson_weekday_raw(2024, 11, 11) offset = 1 - known print("raw=", known, "offset=", offset) def kim_larson_weekday(year, month, day): raw = kim_larson_weekday_raw(year, month, day) return (raw + offset) % 7

逻辑说明:上面代码先计算已知日期2024-11-11的原始返回值,再和期望值 1(代表周一)相减得到偏移量,最后把这个偏移量应用到所有日期上。这样即使你手里的公式版本和我的常数写法不同,只要输出是稳定的,校准就能把它拉回正确轨道。这正是处理“网上抄来的公式”的正确姿势:不要盲目相信注释里写的映射关系,用一个自己确定的日子验证后,再进入业务逻辑。任何带星期的公式实现,在写完以后都应该跑一遍从周一到周日的连续七天测试。

参数说明:两个函数的入参都要求是整数的year、month、day。如果你从前端接口拿到的是字符串,比如"2024"、"11"、"11",务必先int()转换,否则month + 1会变成字符串拼接,//运算直接抛TypeError。蔡勒公式和基姆拉尔森公式的输出映射不同,同一日期在两者中得到的整数含义不同,所以不要在同一个项目里混用它们。

5. 避坑清单:类型、时区、Excel 序列号、pandas 口径的五个雷区

5.1 date 和 datetime 混用:集合 in 判断失效,== 却返回 True

现象:把某个节假日日期放进set,再用一个datetime对象做in判断,结果返回False,可明明打印出来是同一天。

原因:datetime是date的子类,两者做==比较时,datetime会被当作同一天的 0 点来比较,所以date(2024,10,1) == datetime(2024,10,1,0,0)返回True。但这个相等关系并没有延伸到哈希值上,set和dict是通过哈希索引查找的,datetime的哈希计算包含时分秒分量,date的哈希只包含年月日,即使时分秒全是 0,两者的哈希值也不相同。

解决:统一类型再进集合。最稳妥的做法是全部转成date,在集合生成和判断时都调用.date()方法:

from datetime import date, datetime holidays = {date(2024, 10, 1), date(2024, 10, 2)} dt = datetime(2024, 10, 1, 12, 30) print(dt.date() in holidays) # True

5.2 时区导致星期几在边界跨日

现象:服务部署在 UTC 时区的机器上,业务方在东八区。晚上 23 点以后产生的数据,按本地时间已经是第二天,但服务端datetime.now()取的还是 UTC 当天日期,星期几随之错位。

原因:星期几是个日历概念,和时区绑定。容器默认时区通常是 UTC,直接用无时区的datetime.now()得到的是系统时区时间,和业务期望时区可能相差 8 小时或更多。

解决:在业务入口统一用带时区的时间对象,计算星期前先转到业务时区再取date():

from datetime import datetime, timezone from zoneinfo import ZoneInfo dt_utc = datetime.now(timezone.utc) beijing_tz = ZoneInfo("Asia/Shanghai") dt_local = dt_utc.astimezone(beijing_tz) print(dt_local.date().weekday())

逻辑说明:astimezone会基于内置的时区数据库完成偏移换算,dt_local就是业务所在地的本地时间,之后再调用.date().weekday()就安全了。Python 3.9 及以上版本自带zoneinfo,不需要额外安装第三方库。

参数说明:ZoneInfo("Asia/Shanghai")的字符串参数是 IANA 时区名,大小写敏感。如果你的服务同时服务多个地区,建议把这个时区名做成配置项,而不是写死在代码里。

5.3 Excel 导出的日期是序列号,不是字符串

现象:从 Excel 导出的考勤日期列,用 Python 读取后打印出来是一串数字,比如45100,直接datetime.strptime报错。

原因:Windows 版 Excel 内部把日期存储为自 1899-12-30 以来的天数,这个数字叫 Excel 序列号。因为 Excel 把 1900 年错误地当作闰年,所以标准基准日不是 1900-01-01,而是 1899-12-30。

解决:用日期偏移法转回date对象再取星期:

from datetime import date, timedelta def excel_serial_to_date(serial): # Windows Excel 序列号基准日 1899-12-30 return date(1899, 12, 30) + timedelta(days=int(serial)) s = 45100 d = excel_serial_to_date(s) print(d, d.weekday()) # 2023-07-01 5

逻辑说明:date(1899,12,30)加timedelta(days=45100)得到的就是 Excel 中显示的日期。这里把serial先int()是为了丢弃小数部分,Excel 序列号的小数代表当天的时间,比如45100.5表示当天中午,直接传给timedelta会引入半天偏移。如果你的数据来源是 macOS 版本的 Excel,基准日是 1904-01-01,转换逻辑不同,需要单独判断。

参数说明:这个转换只针对 Windows Excel 导出的整数序列号。拿到 Excel 数据后,第一件事是打印前 5 行的原始值确认是数字还是字符串,再决定走strptime还是excel_serial_to_date。

5.4 pandas 的 dayofweek 与 strftime('%w') 口径相反

现象:用df["date"].dt.dayofweek判断周末,得到的结果和用df["date"].dt.strftime('%w')判断的结果互相矛盾,周末标记错位一整天。

原因:pandas 的dayofweek遵循 Python 原生weekday()语义,周一为 0,周日为 6;而strftime('%w')遵循 C 语言标准,周日为 0,周一为 1。两个口径在同一个数据集里混用时,只要条件判断写死某一个区间,就会有系统性偏差。另一个常见误区是%W与%w混写,前者是一年中的第几周,后者才是星期几,大小写不同语义完全不同。

解决:在 pandas 链式操作里统一用dayofweek做条件判断,只在最终展示时转成中文或英文名称:

import pandas as pd df = pd.DataFrame({"date": pd.to_datetime(["2024-11-11", "2024-11-16", "2024-11-17"])}) df["weekday"] = df["date"].dt.dayofweek df["is_weekend"] = df["weekday"] >= 5 print(df)

逻辑说明:dt.dayofweek对 Series 里每个时间戳元素调用weekday(),返回一个整数 Series,直接参与向量化比较。这样生成的新列is_weekend是布尔值,后续筛选、聚合都很方便。

参数说明:pd.to_datetime要求输入的日期字符串格式统一,混用 "2024-11-11" 和 "2024/11/11" 可能导致部分数据解析成NaT。解析后建议立刻检查df["date"].isna().sum(),为 0 再继续。

5.5 大小写 %W 和 %w 混写,周数统计全乱

现象:按周聚合业务数据,输出“2024 年第 45 周”后,发现和 44 周的数据混在一起,数字对不上。

原因:%W返回一年中的周数,以周一作为一周的第一天;%w返回星期几,以周日为 0。两者只差一个字母大小写,但含义完全不同。更隐蔽的是%W在跨年边界和周一起始日上可能返回 0,导致聚合分组错乱。

解决:涉及周数计算时,优先用isocalendar()而不是strftime:

from datetime import date d = date(2023, 1, 1) # 2023-01-01 是周日 print(d.strftime("%W")) # 00 print(d.isocalendar()) # (2022, 52, 7)

逻辑说明:isocalendar()返回一个包含 ISO 年、ISO 周号、ISO 星期几的三元组。它按照 ISO 8601 标准处理跨年边界,把周日归属到上一年的最后一个周,所以 2023-01-01 这个周日的周号是 2022 年的 52,而不是 2023 年的 00。用strftime("%W")会得到字符串"00",如果直接用它做聚合 key,这部分数据会单独落在一个“第 0 周”的桶里。

参数说明:isocalendar()返回的星期几分量和isoweekday()一致,周一为 1,周日为 7。如果业务报表要求展示“第几周”,建议直接用isocalendar()[1],并同步展示 ISO 年,避免跨年订单被归错年份。

6. 进阶技巧:把星期几变成调度策略里的判定逻辑

当星期判断稳定之后,可以做更有价值的衍生逻辑,比如计算“下一个工作日”和“上周一至上周日”的日期段。这比单纯打印星期几实用得多,适合定时任务、账单生成、消息提醒等场景。

from datetime import date, timedelta def next_workday(dt, holidays=()): # 从第二天开始找,跳过周末和额外节假日 cur = dt + timedelta(days=1) while cur.weekday() >= 5 or cur in holidays: cur += timedelta(days=1) return cur print(next_workday(date(2024, 11, 15))) # 周五,返回下一工作日

逻辑说明:next_workday从输入日期的下一天开始探测,只要weekday() >= 5说明是周六或周日,就继续加一天;同时用cur in holidays跳过额外传入的节假日。这里holidays建议传set,查找复杂度是常数级。如果你要处理调休补班,可以把补班日作为额外参数传入另一个类似的函数,或者在上面的函数里加一个extra_workdays参数优先放行。

另一个常用技巧是计算自然周的起止日期。以今天为基准,往回推上周一和上周日:

from datetime import date, timedelta today = date.today() this_monday = today - timedelta(days=today.weekday()) last_monday = this_monday - timedelta(days=7) last_sunday = this_monday - timedelta(days=1) print(last_monday, last_sunday)

逻辑说明:today.weekday()返回 0 到 6,正好是“今天距离本周一过了几天”。用今天减去这个天数就得到本周一,再减 7 天就是上周一,减 1 天就是上周日。这套算法不依赖任何第三方库,适用性很广。要注意的是date.today()使用的是服务器本地时区,如果你的任务运行在 UTC 机器上且面向东八区业务,需要先按 5.2 节的方式转换时区再取日期。

我自己的习惯是:任何涉及星期、日期的函数,接到需求后第一件事是写一个从周一到周日连续七天的断言用例,再跑一遍跨年和二月的边界日期。类型统一、时区统一、口径统一这三件事做在前面,后面基本不会翻车。时间处理这玩意儿看起来全是基础 API,实际上一旦口径错位就是批量事故,把规则写成测试用例固定下来,比任何时候靠记忆都可靠。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询