在日常开发中,时间处理最容易出错的三个地方就是:格式转换、时区处理和各种边界情况。本节聚焦最实用的操作方法和避坑指南。
一、时间格式转换
时间数据在系统中频繁以字符串和datetime 对象两种形态出现,转换是基本功。
字符串 → datetime 对象:strptime
from datetime import datetime
date_str = "2025-03-15 14:30:00"
dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(dt) # 2025-03-15 14:30:00
strptime是“string parse time”的缩写,需要你提供与字符串精确匹配的格式指令。- 常用格式符号速记:
%Y四位年份,%m两位月份,%d两位日期%H24小时制小时,%M分钟,%S秒%f微秒(6位),%z时区偏移(如+0800),%Z时区名称(如 CST)
datetime 对象 → 字符串:strftime
dt = datetime(2025, 3, 15, 14, 30)
formatted = dt.strftime("%Y年%m月%d日 %H:%M")
print(formatted) # 2025年03月15日 14:30
strftime是“string format time”,可以按需要任意组合格式。- 输出中文日期等展示性内容时特别方便。
时间戳与 datetime 互转
from datetime import datetime
# 时间戳 → datetime
ts = 1710484200
dt = datetime.fromtimestamp(ts) # 本地时区
dt_utc = datetime.utcfromtimestamp(ts) # UTC(已弃用,建议用timezone处理)
# datetime → 时间戳
dt = datetime(2025, 3, 15, 14, 30)
ts = dt.timestamp() # 返回浮点数秒
- 高版本 Python 中
datetime.utcfromtimestamp已标记弃用,推荐显式使用时区:datetime.fromtimestamp(ts, tz=timezone.utc)。
二、时区问题
时区是时间处理的头号坑点,核心矛盾在于:datetime 对象可以“无时区意识”(naive)或“有时区意识”(aware),混用会导致错误的结果。
创建 aware datetime 对象
from datetime import datetime, timezone, timedelta
# 使用 UTC 时区
dt_utc = datetime(2025, 3, 15, 14, 30, tzinfo=timezone.utc)
# 使用固定偏移(如 UTC+8)
cst = timezone(timedelta(hours=8))
dt_cst = datetime(2025, 3, 15, 14, 30, tzinfo=cst)
# 使用第三方库 pytz(更完整的时区数据库)
import pytz
shanghai_tz = pytz.timezone('Asia/Shanghai')
dt_sh = shanghai_tz.localize(datetime(2025, 3, 15, 14, 30))
- 建议:在业务系统中,所有时间内部存储统一使用 UTC,只在显示时转换为用户本地时区。这样可以避免跨时区计算的无尽混乱。
时区转换
# 将 aware datetime 转为其他时区
dt_utc = datetime(2025, 3, 15, 6, 30, tzinfo=timezone.utc)
dt_sh = dt_utc.astimezone(cst) # 转换为 UTC+8,得到 14:30
naive 和 aware 的陷阱
- 对 naive datetime 调用
timestamp(),Python 会假定它是本地时间,这在服务器上可能违背预期。 - 两个 naive datetime 可以相减,但无法正确跨越夏令时边界。因此务必尽量使用 aware datetime。
- Django、SQLAlchemy 等框架默认存储 naive datetime,需要配置
USE_TZ = True等选项来开启时区支持。
三、处理时间的最佳实践
- 存储用 UTC,展示用本地
数据库、API 传输一律使用 UTC 时间,前端或报表层再根据用户所在时区转换。这样做数据库里的一条记录在任何地区看到的绝对时刻都是一致的。
- 统一使用 aware datetime
从一开始就养成所有 datetime 对象都带时区的习惯,能避免 90% 的时区 bug。项目代码中可以封装辅助函数,禁止创建 naive datetime。
- 善用
dateutil解析自然语言时间
第三方库 python-dateutil 的 parser.parse() 能自动识别大多数常见时间字符串格式,比 strptime 更灵活:
from dateutil import parser
dt = parser.parse("2025-03-15 14:30")
dt = parser.parse("March 15, 2025 2:30 PM")
- 计算时间差用
timedelta,慎用数值运算
from datetime import timedelta
tomorrow = dt + timedelta(days=1)
last_week = dt - timedelta(weeks=1)
避免直接加减 86400 秒等数值,因为夏令时切换日可能不是 24 小时。
- 获取当前时间明确时区
# 推荐:获取当前 UTC 时间
now_utc = datetime.now(timezone.utc)
# 不推荐:无时区的“现在”
now_naive = datetime.now() # 你不知道它到底是什么时区
- 日志和系统监控时间也要统一
logging 模块的默认时间戳是本地时间且不带时区,建议配置为 UTC 并加上时区信息,排错时才能准确对应服务器事件。
- 使用
time模块处理纯时间差或性能计时
当只需要计时、计时睡眠而不在乎具体日期时,time.time()、time.perf_counter() 等更轻量且不必纠结时区。
- 测试时区相关代码
编写单元测试时,显式给函数传入指定的 aware datetime,或使用 monkeypatch 模拟不同时区,确保逻辑正确。
掌握这些转换方法和时区原则,就能让时间处理从“最容易出 bug”变为“最可控”的模块之一。