datetime模块 — 日期时间处理

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对象的属性

属性类型说明范围
yearint年份1 到 9999
monthint月份1 到 12
dayint日期根据月份和年份变化
# 访问属性 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对象的属性

属性类型说明范围
hourint小时0 到 23
minuteint分钟0 到 59
secondint0 到 59
microsecondint微秒0 到 999999
tzinfotzinfo or None关联的时区信息
foldint夏令时回退时的歧义标识(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部分的全部属性:

属性类型说明
yearint年份(继承自date)
monthint月份(继承自date)
dayint天数(继承自date)
hourint小时
minuteint分钟
secondint
microsecondint微秒
tzinfotzinfo or None时区信息
foldint夏令时歧义标识

常用方法

方法返回说明
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')strISO 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的属性与方法

属性/方法类型说明
daysint天数部分(可正可负)
secondsint秒数部分(0 到 86399)
microsecondsint微秒部分(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)
%Y4位数的年份2026
%y2位数的年份26
%m月份(01-12,补零)05
%b缩写的月份名称May
%B完整的月份名称May
%d日期(01-31,补零)05
%H24小时制小时(00-23)14
%I12小时制小时(01-12)02
%M分钟(00-59)30
%S秒(00-59)45
%f微秒(6位,补零)000000
%pAM或PMPM
%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时间进行存储和计算,仅在展示时转换为本地时区。