← 返回Python标准库精讲目录
← 返回学习笔记首页
专题: Python标准库精讲系统学习
关键词: Python, 标准库, datetime, 日期, 时间, timedelta, strftime, strptime, 时区, date, time
一、datetime模块概述
datetime是Python标准库中处理日期和时间的核心模块,提供了五个核心类以及丰富的工具函数,用于创建、操作、格式化日期时间对象。该模块设计精良,遵循面向对象原则,每个类各司其职,又能够通过组合完成复杂的日期时间计算任务。
在Python生态中,datetime模块是日期时间处理的基石。无论是数据科学中的时间序列分析、Web开发中的会话管理、日志系统中的时间戳记录,还是日常脚本中的日期计算,datetime都扮演着不可或缺的角色。Python 3.9及以上版本还引入了zoneinfo模块,进一步完善了时区支持。
五大核心类关系
┌─────────────────────────────────────────┐
│ datetime 模块 │
│ │
│ ┌───────────┐ ┌──────────────────┐ │
│ │ date │ │ time │ │
│ │ (日期类) │ │ (时间类) │ │
│ │ 年/月/日 │ │ 时/分/秒/微秒 │ │
│ └─────┬─────┘ └────────┬─────────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ datetime │ │
│ │ (日期时间类) │ │
│ │ date + time │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌───────────┐ │ ┌──────────────────┐ │
│ │ timedelta│◄─┘ │ tzinfo │ │
│ │ (时间差) │ │ (时区信息抽象基类)│ │
│ └───────────┘ └──────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ timezone │ │ zoneinfo │ │
│ │ (固定偏移)│ │(IANA时区) │ │
│ └──────────┘ └──────────┘ │
└───────────────────────────────────────────┘
核心类一览
类名 描述 示例
date 日期类,表示年、月、日 date(2026, 5, 5)
time 时间类,表示时、分、秒、微秒 time(14, 30, 0)
datetime 日期时间类,同时包含日期和时间 datetime(2026, 5, 5, 14, 30)
timedelta 时间差类,表示两个日期时间的差值 timedelta(days=7)
tzinfo 时区信息抽象基类 timezone.utc
核心设计思想:date和time是基础组件,datetime继承自date并聚合了time的功能,timedelta为日期时间运算提供支持,tzinfo及其实现类提供时区感知能力。五个类协同工作,构建了Python强大而灵活的日期时间处理体系。
二、date日期类
date类用于处理公历日期,包含年、月、日三个基本属性。所有date对象都是不可变(immutable)的,这意味着一旦创建就无法修改,任何"修改"操作都会返回一个新的对象。这种设计保证了日期对象的安全性和可哈希性。
创建date对象
# 导入模块
from datetime import date
# 方式一:直接指定年月日
d1 = date(2026 , 5 , 5 ) # date(2026, 5, 5)
# 方式二:获取当前本地日期
today = date.today() # 如 date(2026, 5, 5)
# 方式三:从时间戳创建(Unix时间戳,秒)
import time
ts = time.time() # 当前时间戳
d2 = date.fromtimestamp(ts) # 转换为date对象
# 方式四:从序数创建(Grigorian序数,1年1月1日为第1天)
d3 = date.fromordinal(1 ) # date(1, 1, 1)
d4 = date.fromordinal(738646 ) # date(2023, 1, 1)
# 方式五:从ISO格式字符串创建(Python 3.7+)
d5 = date.fromisoformat('2026-05-05' )
date对象的属性
属性 类型 说明 范围
year int 年份 1 到 9999
month int 月份 1 到 12
day int 日期 根据月份和年份变化
# 访问属性
d = date(2026 , 5 , 5 )
print (d.year) # 2026
print (d.month) # 5
print (d.day) # 5
常用方法
方法 返回 说明
today() date 返回当前本地日期(类方法)
fromtimestamp(ts) date 从时间戳创建date(类方法)
fromordinal(n) date 从序数创建date(类方法)
fromisoformat(s) date 从ISO格式字符串解析(3.7+)
replace(**kwargs) date 替换指定的年月日,返回新对象
weekday() int 星期几,0=周一,6=周日
isoweekday() int 星期几,1=周一,7=周日
isocalendar() tuple 返回 (ISO年, ISO周, ISO工作日)
isoformat() str 返回ISO 8601格式日期字符串
ctime() str 返回类似 "Tue May 5 00:00:00 2026" 的字符串
strftime(fmt) str 按指定格式格式化日期
toordinal() int 返回Grigorian序数
timetuple() time.struct_time 返回time元组
# replace方法
d = date(2026 , 5 , 5 )
d2 = d.replace(month=12 , day=25 ) # date(2026, 12, 25)
# 原对象不变
print (d) # 2026-05-05
# 星期相关
print (d.weekday()) # 1 (周二,0=周一)
print (d.isoweekday()) # 2 (周二,1=周一)
print (d.isocalendar()) # (2026, 19, 2) 2026年第19周周二
# 格式化输出
print (d.isoformat()) # 2026-05-05
print (d.ctime()) # Tue May 5 00:00:00 2026
三、time时间类
time类表示一天中的某个时间点,包含时、分、秒、微秒以及可选的时区信息。与date类一样,time对象也是不可变的。time对象可以表示的时间范围从00:00:00.000000到23:59:59.999999,但需要注意它并不能表示所有有效的时间值——例如time(24, 0, 0)会引发ValueError。
创建time对象
from datetime import time, timezone, timedelta
# 基本创建:最简形式只需时和分
t1 = time(14 , 30 ) # 14:30:00
# 完整参数
t2 = time(14 , 30 , 45 , 123456 ) # 14:30:45.123456
# 带时区信息
BJ = timezone(timedelta(hours=8 ))
t3 = time(14 , 30 , tzinfo=BJ) # 14:30:00+08:00
# 最小/最大值
print (time.min) # 00:00:00
print (time.max) # 23:59:59.999999
print (time.resolution) # 0:00:00.000001 (1微秒)
time对象的属性
属性 类型 说明 范围
hour int 小时 0 到 23
minute int 分钟 0 到 59
second int 秒 0 到 59
microsecond int 微秒 0 到 999999
tzinfo tzinfo or None 关联的时区信息 —
fold int 夏令时回退时的歧义标识(0或1) 0 或 1
常用方法
# replace — 替换属性
t = time(14 , 30 , 45 )
t2 = t.replace(hour=8 , minute=0 ) # 08:00:45
# isoformat — 格式化输出
print (t.isoformat()) # 14:30:45
print (t.isoformat(timespec='minutes' )) # 14:30
print (t.isoformat(timespec='hours' )) # 14
# strftime — 自定义格式化
print (t.strftime('%H:%M:%S' )) # 14:30:45
# 判断是否带时区信息
print (t.tzinfo) # None
print (t.utcoffset()) # None(无时区时返回None)
注意:time对象不能直接进行加减运算。如果需要计算时间差,推荐的做法是将time转换为datetime(使用datetime.combine)后再进行计算,或者先转换为秒数进行运算。
四、datetime日期时间类
datetime类是date和time的联合体,同时包含日期和时间信息。它是实际开发中使用频率最高的类。datetime继承自date,因此拥有date类的所有方法和属性,同时补充了time类的相关特性。
创建datetime对象
from datetime import datetime, date, time, timezone, timedelta
# 方式一:直接指定
dt1 = datetime(2026 , 5 , 5 , 14 , 30 , 45 )
# datetime(2026, 5, 5, 14, 30, 45)
# 方式二:获取当前日期时间
now = datetime.now() # 当前本地日期时间
utc = datetime.utcnow() # 当前UTC日期时间(naive)
today = datetime.today() # 同now()
# 方式三:从date和time组合
d = date(2026 , 5 , 5 )
t = time(14 , 30 )
dt2 = datetime.combine(d, t) # datetime(2026, 5, 5, 14, 30)
# 方式四:从时间戳创建
dt3 = datetime.fromtimestamp(1714890000 ) # 转换为本地时间
dt4 = datetime.utcfromtimestamp(1714890000 ) # 转换为UTC时间
# 方式五:从字符串解析
dt5 = datetime.fromisoformat('2026-05-05T14:30:45' )
dt6 = datetime.strptime('2026-05-05 14:30:45' , '%Y-%m-%d %H:%M:%S' )
datetime对象的属性
datetime实例同时拥有date部分和time部分的全部属性:
属性 类型 说明
year int 年份(继承自date)
month int 月份(继承自date)
day int 天数(继承自date)
hour int 小时
minute int 分钟
second int 秒
microsecond int 微秒
tzinfo tzinfo or None 时区信息
fold int 夏令时歧义标识
常用方法
方法 返回 说明
now(tz=None) datetime 当前本地日期时间(可指定时区)
utcnow() datetime 当前UTC时间(naive,不推荐)
today() datetime 当前本地日期时间
combine(d, t) datetime 合并date和time(类方法)
fromtimestamp(ts, tz=None) datetime 从时间戳创建(类方法)
strptime(s, fmt) datetime 从字符串解析(类方法)
date() date 提取日期部分
time() time 提取时间部分(无时区)
timetz() time 提取时间部分(含时区)
replace(**kwargs) datetime 替换指定属性
astimezone(tz=None) datetime 转换时区
timestamp() float 转换为Unix时间戳
weekday() int 星期几(0=周一)
isoweekday() int 星期几(1=周一)
isoformat(sep='T') str ISO 8601格式输出
# now与时区
from datetime import timezone, timedelta
BJ = timezone(timedelta(hours=8 ))
now_bj = datetime.now(BJ) # 北京时间
now_utc = datetime.now(timezone.utc) # UTC时间
# 提取部分
dt = datetime(2026 , 5 , 5 , 14 , 30 )
print (dt.date()) # 2026-05-05
print (dt.time()) # 14:30:00
# timestamp转换
ts = dt.timestamp() # 1767496200.0
dt_back = datetime.fromtimestamp(ts) # 转回本地时间
# replace
dt2 = dt.replace(year=2025 , hour=9 ) # datetime(2025, 5, 5, 9, 30)
重要提示:datetime.utcnow()返回的是不带时区信息的naive datetime,容易引起歧义。Python 3.12+中推荐使用 datetime.now(timezone.utc) 替代,因为后者返回的是带时区信息的aware datetime,更加清晰和安全。
五、timedelta时间差
timedelta表示两个日期时间之间的差值,精确到微秒级。它是datetime模块中实现日期时间运算的核心工具。timedelta支持与datetime、date对象的加减运算,也支持timedelta之间的加减运算,甚至可以取负值。
创建timedelta对象
from datetime import timedelta
# 支持的参数:days, seconds, microseconds,
# milliseconds, minutes, hours, weeks
td1 = timedelta(days=7 ) # 7天
td2 = timedelta(hours=2 , minutes=30 ) # 2小时30分
td3 = timedelta(weeks=2 , days=3 , hours=12 ) # 2周3天12小时
td4 = timedelta(days=-1 ) # 负时间差
# 内部存储:只保days, seconds, microseconds三个字段
print (td3) # 17 days, 12:00:00
# 特殊值
print (timedelta.min) # -999999999 days
print (timedelta.max) # 999999999 days
print (timedelta.resolution) # 0:00:00.000001
日期时间运算
from datetime import datetime, timedelta, date
now = datetime.now()
yesterday = now - timedelta(days=1 ) # 昨天此时
tomorrow = now + timedelta(days=1 ) # 明天此时
next_week = now + timedelta(weeks=1 ) # 下周此时
two_hours_later = now + timedelta(hours=2 ) # 2小时后
# date对象同样支持
d = date(2026 , 5 , 5 )
d_next = d + timedelta(days=10 ) # date(2026, 5, 15)
# 计算时间差
dt1 = datetime(2026 , 1 , 1 )
dt2 = datetime(2026 , 5 , 5 )
diff = dt2 - dt1 # timedelta(124)
print (diff.days) # 124
print (diff.total_seconds()) # 10713600.0
timedelta的属性与方法
属性/方法 类型 说明
days int 天数部分(可正可负)
seconds int 秒数部分(0 到 86399)
microseconds int 微秒部分(0 到 999999)
total_seconds() float 转换为总秒数(含小数)
# 支持的全部运算
# timedelta + timedelta = timedelta
# timedelta - timedelta = timedelta
# timedelta * int/float = timedelta
# timedelta / timedelta = float
# timedelta // timedelta = int
# +timedelta / -timedelta (正负号)
# abs(timedelta) (绝对值)
td = timedelta(hours=5 , minutes=30 )
print (td.total_seconds()) # 19800.0
print (td * 2 ) # 11:00:00
print (td / timedelta(hours=1 )) # 5.5 (换算成小时)
六、格式化与解析
在实际开发中,日期时间对象与字符串之间的相互转换是最常见的操作之一。datetime模块提供了两套格式化方案:一套是传统的strftime/strptime组合,支持丰富的格式码;另一套是ISO 8601标准的isoformat/fromisoformat组合,是Python 3.7+的推荐做法。
strftime — 格式化日期时间为字符串
strftime(string format time)将日期时间对象按照指定的格式码转换为字符串。所有四个核心类(date、time、datetime、timedelta的str方法)都支持此方法。
格式码 说明 示例(2026-05-05 14:30:45)
%Y 4位数的年份 2026
%y 2位数的年份 26
%m 月份(01-12,补零) 05
%b 缩写的月份名称 May
%B 完整的月份名称 May
%d 日期(01-31,补零) 05
%H 24小时制小时(00-23) 14
%I 12小时制小时(01-12) 02
%M 分钟(00-59) 30
%S 秒(00-59) 45
%f 微秒(6位,补零) 000000
%p AM或PM PM
%a 缩写的星期名称 Tue
%A 完整的星期名称 Tuesday
%w 星期几(0=周日,6=周六) 2
%j 年中的第几天(001-366) 125
%U 年中的第几周(周日为一周开始) 18
%W 年中的第几周(周一为一周开始) 19
%z 时区偏移(+HHMM或-HHMM) +0800
%Z 时区名称 CST
%% 转义为普通的% %
from datetime import datetime
dt = datetime(2026 , 5 , 5 , 14 , 30 , 45 )
# 常见格式
print (dt.strftime('%Y-%m-%d %H:%M:%S' )) # 2026-05-05 14:30:45
print (dt.strftime('%Y/%m/%d' )) # 2026/05/05
print (dt.strftime('%Y年%m月%d日 %H时%M分' )) # 2026年05月05日 14时30分
print (dt.strftime('%A, %B %d, %Y' )) # Tuesday, May 05, 2026
print (dt.strftime('%I:%M %p' )) # 02:30 PM
print (dt.strftime('%j天 of %Y年 (第%W周)' )) # 125天 of 2026年 (第19周)
strptime — 解析字符串为datetime
strptime(string parse time)是strftime的逆操作,按照指定的格式码将字符串解析为datetime对象。注意这是datetime类的类方法,返回的是datetime对象而非date。
# 基本解析
dt = datetime.strptime('2026-05-05 14:30:45' , '%Y-%m-%d %H:%M:%S' )
# 解析中文格式
dt2 = datetime.strptime('2026年05月05日 14时30分45秒' , '%Y年%m月%d日 %H时%M分%S秒' )
# 解析常见日志格式
log = 'Tue, 05 May 2026 14:30:45 +0800'
dt3 = datetime.strptime(log, '%a, %d %b %Y %H:%M:%S %z' )
print (dt3) # 2026-05-05 14:30:45+08:00
ISO 8601格式(推荐)
ISO 8601是国际标准化的日期时间表示法,Python 3.7+为date、time、datetime都提供了fromisoformat类方法。在Python 3.11+中,fromisoformat的解析能力得到了显著增强,能够解析更多ISO 8601格式变体。
# isoformat — 转换为ISO 8601字符串
dt = datetime(2026 , 5 , 5 , 14 , 30 , 45 )
print (dt.isoformat()) # 2026-05-05T14:30:45
print (dt.isoformat(sep=' ' )) # 2026-05-05 14:30:45
print (dt.date().isoformat()) # 2026-05-05
print (dt.time().isoformat()) # 14:30:45
# fromisoformat — 从ISO 8601解析
dt2 = datetime.fromisoformat('2026-05-05T14:30:45' )
dt3 = datetime.fromisoformat('2026-05-05T14:30:45+08:00' )
d = date.fromisoformat('2026-05-05' )
t = time.fromisoformat('14:30:45' )
# Python 3.11+ 支持更多格式
dt4 = datetime.fromisoformat('2026-05-05T14:30:45.123456' )
七、时区处理
时区处理是日期时间编程中最容易出错的领域之一。Python的datetime模块通过tzinfo抽象基类和timezone实现类提供了基础时区支持。Python 3.9+引入的zoneinfo模块进一步提供了对IANA时区数据库的完整支持,使得时区处理更加方便和标准化。
timezone — 固定偏移时区
timezone是tzinfo的一个简单实现,表示固定偏移量的时区(如UTC+8)。它适用于不需要考虑夏令时变化的场景。
from datetime import timezone, timedelta, datetime
# 创建时区对象
utc = timezone.utc # UTC时区
bj = timezone(timedelta(hours=8 )) # 北京时间 UTC+8
ny = timezone(timedelta(hours=-5 )) # 纽约时间 UTC-5
# 创建aware datetime(带时区信息)
dt_utc = datetime(2026 , 5 , 5 , 6 , 30 , tzinfo=utc)
dt_bj = datetime(2026 , 5 , 5 , 14 , 30 , tzinfo=bj)
# 两个时区的时间相等(同一时刻的不同表示)
print (dt_utc == dt_bj) # True
# 时区转换
print (dt_bj.astimezone(utc)) # 2026-05-05 06:30:00+00:00
print (dt_bj.astimezone(ny)) # 2026-05-05 01:30:00-05:00
tzinfo — 抽象基类
tzinfo是时区信息的抽象基类,用于自定义时区规则。若要实现自定义时区,需要继承tzinfo并实现以下方法:
方法 说明
utcoffset(dt) 返回与UTC的偏移量(timedelta)
dst(dt) 返回夏令时偏移量
tzname(dt) 返回时区名称
fromutc(dt) 从UTC时间转换(可选实现)
# 自定义时区示例
from datetime import tzinfo, timedelta, datetime
class CST (tzinfo):
def utcoffset (self , dt):
return timedelta(hours=8 )
def dst (self , dt):
return timedelta(0 ) # 无夏令时
def tzname (self , dt):
return 'CST'
cst = CST()
dt_cst = datetime(2026 , 5 , 5 , 14 , 30 , tzinfo=cst)
print (dt_cst) # 2026-05-05 14:30:00+08:00
print (dt_cst.tzname()) # CST
zoneinfo — IANA时区数据库(Python 3.9+)
zoneinfo是Python 3.9引入的标准库模块,基于IANA(Internet Assigned Numbers Authority)时区数据库,提供了完整的世界时区支持,包括夏令时自动调整功能。这是现代Python时区处理的首选方案。
# Python 3.9+ zoneinfo
try :
from zoneinfo import ZoneInfo
except ImportError:
# Python 3.8及以下需要使用第三方库 pytz
from pytz import timezone as ZoneInfo
# 使用时区名称(IANA标准名称)
shanghai = ZoneInfo('Asia/Shanghai' )
new_york = ZoneInfo('America/New_York' )
tokyo = ZoneInfo('Asia/Tokyo' )
london = ZoneInfo('Europe/London' )
# 创建aware datetime
dt_sh = datetime(2026 , 5 , 5 , 14 , 30 , tzinfo=shanghai)
# 时区转换(自动处理夏令时)
dt_ny = dt_sh.astimezone(new_york)
print (dt_ny) # 自动转换为纽约时间,考虑夏令时
# 获取当前时间的指定时区表示
now_sh = datetime.now(shanghai)
now_ny = datetime.now(new_york)
print (f"上海: {now_sh}" )
print (f"纽约: {now_ny}" )
时区处理最佳实践
# 1. 内部统一使用UTC存储
utc_now = datetime.now(timezone.utc)
# 2. 展示时转换为本地时区
bj = timezone(timedelta(hours=8 ))
local_time = utc_now.astimezone(bj)
# 3. datetime比较要求一致:naive和aware不能比较
naive_dt = datetime(2026 , 5 , 5 )
aware_dt = datetime(2026 , 5 , 5 , tzinfo=timezone.utc)
# naive_dt == aware_dt # TypeError!
# 4. 数据库和API交互统一使用UTC或Unix时间戳
# 使用时间戳可以完全避免时区问题
ts = utc_now.timestamp()
核心原则:内部存储和计算统一使用UTC,只在展示时转换为本地时区。数据库中使用UTC时间存储,通过应用层进行时区转换。这样能最大程度避免夏令时、跨时区协作等问题带来的Bug。
八、实战案例
本节通过五个实际开发中常见的日期时间处理场景,展示datetime模块的综合应用技巧。这些案例涵盖了日期范围迭代、年龄计算、工作日判断、时间戳互转以及业务日期处理等典型需求。
案例一:日期范围生成器
在数据分析、报表生成等场景中,经常需要生成一个连续的日期序列。以下代码实现了一个高效的日期范围生成器:
from datetime import date, timedelta
def date_range (start, end, step=timedelta(days=1 )):
"""生成从start到end(含)之间的日期序列"""
current = start
while current <= end:
yield current
current += step
# 使用示例
start = date(2026 , 5 , 1 )
end = date(2026 , 5 , 15 )
for d in date_range (start, end):
print (d.isoformat(), d.strftime('%A' ))
# 每隔3天生成
for d in date_range (start, end, timedelta(days=3 )):
print (d)
案例二:计算年龄
年龄计算需要考虑出生日期和当前日期的年月日对比,不能单纯按天数除以365:
from datetime import date
def calculate_age (birth_date, from_date=None):
"""精确计算年龄(周岁)"""
if from_date is None :
from_date = date.today()
age = from_date.year - birth_date.year
# 如果今年生日还没到,减去一岁
if (from_date.month, from_date.day) < (birth_date.month, birth_date.day):
age -= 1
return age
# 测试
birth = date(1990 , 6 , 15 )
today = date(2026 , 5 , 5 )
print (calculate_age (birth, today)) # 35(还没到6月生日)
birth2 = date(1995 , 3 , 20 )
print (calculate_age (birth2, today)) # 31(已经过了3月生日)
案例三:工作日计算
计算两个日期之间的工作日天数,排除周末(周六、周日),同时可以考虑排除法定节假日列表:
from datetime import date, timedelta
def count_weekdays (start, end, holidays=None):
"""计算工作日天数,可传入节假日列表"""
holidays = holidays or set()
count = 0
current = start
while current <= end:
if current.weekday() < 5 and current not in holidays:
count += 1
current += timedelta(days=1 )
return count
def add_workdays (start, days):
"""计算从start开始days个工作日后的日期"""
current = start
added = 0
while added < days:
current += timedelta(days=1 )
if current.weekday() < 5 :
added += 1
return current
# 使用示例
# 5月1日-5月15日的工作日
start = date(2026 , 5 , 1 )
end = date(2026 , 5 , 15 )
print (count_weekdays (start, end)) # 11(去除两个周末)
# 从5月1日起10个工作日后的日期
print (add_workdays (date(2026 , 5 , 1 ), 10 )) # 2026-05-15
案例四:时间戳与datetime互转
Unix时间戳(Unix timestamp)是跨系统交换时间数据的通用格式,定义为从1970-01-01 00:00:00 UTC到指定时间的总秒数(忽略闰秒)。
from datetime import datetime, timezone
import time
# datetime → 时间戳
now = datetime.now(timezone.utc)
ts = now.timestamp()
print (ts) # 类似于 1746487845.123456
# 时间戳 → datetime
dt_from_ts = datetime.fromtimestamp(ts, tz=timezone.utc)
print (dt_from_ts) # 转回UTC时间
# 本地时间戳转换
local_dt = datetime.fromtimestamp(ts) # 自动转为本地时区
# 高精度:毫秒时间戳转换
ms_timestamp = 1746487845123 # JavaScript常用毫秒时间戳
dt_from_ms = datetime.fromtimestamp(ms_timestamp / 1000.0 , tz=timezone.utc)
# 当前时间戳的多种获取方式
ts1 = time.time()
ts2 = datetime.now().timestamp()
print (ts1, ts2)
案例五:本月第一天和最后一天
在报表统计、按月的业务处理中,经常需要计算某个月的第一天和最后一天:
from datetime import date, timedelta
import calendar
def month_first_day (dt):
"""获取指定日期所在月的第一天"""
return dt.replace(day=1 )
def month_last_day (dt):
"""获取指定日期所在月的最后一天"""
# 方法一:利用calendar模块
_, last_day = calendar.monthrange(dt.year, dt.month)
return dt.replace(day=last_day)
def month_last_day_v2 (dt):
"""方法二:下月第一天减1天"""
if dt.month == 12 :
return date(dt.year + 1 , 1 , 1 ) - timedelta(days=1 )
return date(dt.year, dt.month + 1 , 1 ) - timedelta(days=1 )
# 使用示例
d = date(2026 , 5 , 5 )
print (month_first_day (d)) # 2026-05-01
print (month_last_day (d)) # 2026-05-31
# 特殊情况:2月闰年
feb_leap = date(2024 , 2 , 15 )
print (month_last_day (feb_leap)) # 2024-02-29
案例六:时间差的可读格式化
在社交应用、日志系统中,常常需要将时间差显示为"3分钟前"、"2小时前"、"昨天"等可读形式:
from datetime import datetime, timedelta, timezone
def time_ago (dt, now=None):
"""将时间差转换为可读的文本描述"""
if now is None :
now = datetime.now(timezone.utc)
# 确保比较一致
if dt.tzinfo is None :
dt = dt.replace(tzinfo=timezone.utc)
diff = now - dt
if diff < timedelta(minutes=1 ):
return "刚刚"
elif diff < timedelta(hours=1 ):
return f" {diff.seconds // 60 }分钟前"
elif diff < timedelta(days=1 ):
return f" {diff.seconds // 3600 }小时前"
elif diff < timedelta(days=2 ):
return "昨天"
elif diff < timedelta(days=7 ):
return f" {diff.days}天前"
elif diff < timedelta(days=30 ):
return f" {diff.days // 7 }周前"
elif diff < timedelta(days=365 ):
return f" {diff.days // 30 }个月前"
else :
return f" {diff.days // 365 }年前"
# 测试
now = datetime(2026 , 5 , 5 , 14 , 30 , tzinfo=timezone.utc)
print (time_ago (now - timedelta(seconds=30 ), now)) # 刚刚
print (time_ago (now - timedelta(minutes=15 ), now)) # 15分钟前
print (time_ago (now - timedelta(hours=5 ), now)) # 5小时前
print (time_ago (now - timedelta(days=1 ), now)) # 昨天
print (time_ago (now - timedelta(days=20 ), now)) # 2周前
print (time_ago (now - timedelta(days=400 ), now)) # 1年前
综合应用:完整的工作日计算器
from datetime import date, timedelta
import calendar
class WorkdayCalculator :
"""工作日计算器:支持工作日统计和日期推算"""
def __init__ (self , holidays=None):
self .holidays = holidays or set()
def is_workday (self , d):
"""判断是否为工作日"""
return d.weekday() < 5 and d not in self .holidays
def workdays_between (self , start, end):
"""计算两个日期之间的工作日数"""
return sum (1 for i in range ((end - start).days + 1 )
if self .is_workday(start + timedelta(days=i)))
def add_workdays (self , start, n):
"""从起始日期增加n个工作日"""
current = start
added = 0
while added < n:
current += timedelta(days=1 )
if self .is_workday(current):
added += 1
return current
def month_workdays (self , year, month):
"""计算某个月的工作日总数"""
_, days = calendar.monthrange(year, month)
first = date(year, month, 1 )
last = date(year, month, days)
return self .workdays_between(first, last)
# 使用示例
cal = WorkdayCalculator(holidays={date(2026 , 5 , 1 )})
print (cal.month_workdays(2026 , 5 )) # 排除五一劳动节
print (cal.add_workdays(date(2026 , 5 , 5 ), 15 )) # 15个工作日后
学习建议:datetime模块是Python标准库中使用频率最高的模块之一,建议熟练掌握date、datetime、timedelta三个类的常用操作,以及strftime/strptime的格式码。对于时区处理,优先使用Python 3.9+的zoneinfo模块,避免使用已弃用的pytz库。在实际项目中,建议统一使用UTC时间进行存储和计算,仅在展示时转换为本地时区。