一、strftime到底是什么?
strftime,全称 string format time,即"字符串格式化时间"。它是Python time模块和datetime模块中的一个方法,作用是:把datetime对象转换成你指定格式的字符串。
它的兄弟是strptime(string parse time),作用正好相反:把字符串解析成datetime对象。
from datetime import datetime
# strftime:datetime → 字符串(格式化输出)
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))
# 输出:2026-06-22 10:24:22
# strptime:字符串 → datetime(解析输入)
dt = datetime.strptime("2026-06-22 10:24:22", "%Y-%m-%d %H:%M:%S")
print(dt)
# 输出:2026-06-22 10:24:22
一句话总结:strftime是"往外吐"时间字符串,strptime是"往里吃"时间字符串。 记住这个方向,永远不会搞混。
二、完整格式符速查表
strftime的核心就是那些以%开头的格式符。下面这张表,建议收藏:
| 格式符 | 含义 | 示例输出 |
|---|---|---|
%Y |
四位年份 | 2026 |
%y |
两位年份(00-99) | 26 |
%m |
月份(01-12) | 06 |
%d |
日期(01-31) | 22 |
%H |
24小时制小时(00-23) | 10 |
%I |
12小时制小时(01-12) | 10 |
%M |
分钟(00-59) | 24 |
%S |
秒(00-59) | 22 |
%p |
AM/PM | AM |
%A |
星期全称 | Monday |
%a |
星期缩写 | Mon |
%B |
月份全称 | June |
%b |
月份缩写 | Jun |
%j |
一年中的第几天(001-366) | 173 |
%U |
一年中的第几周(周日为起始,00-53) | 25 |
%W |
一年中的第几周(周一为起始,00-53) | 25 |
%w |
星期几(0=周日,6=周六) | 1 |
%z |
UTC偏移量 | +0800 |
%Z |
时区名称 | CST |
%% |
字面量% | % |
高频踩坑预警:
%Yvs%y:日志和数据库存储必须用%Y,两位数年份在2000年前后会产生歧义。%Ivs%H:%I是12小时制,必须搭配%p使用,否则"10:00"你分不清是上午还是晚上。%w:Python里周日是0,不是1。很多人按ISO标准(周一=1)来用,结果全错。
三、实战代码:五个生产场景全覆盖
光看表格记不住,下面用五个真实场景把知识点串起来。
场景1:日志文件命名(最常用)
生产环境的日志文件必须带时间戳,方便按天归档和排查问题。
from datetime import datetime
def get_log_filename():
"""生成带时间戳的日志文件名"""
now = datetime.now()
filename = now.strftime("log_%Y%m%d_%H%M%S.log")
return filename
print(get_log_filename())
# 输出:log_20260622_102422.log
为什么这样写? 文件名用%Y%m%d_%H%M%S格式,排序时天然按时间顺序排列,不需要额外处理。这是我在多个项目中验证过的最佳实践。
场景2:生成标准化报表时间
业务报表要求时间格式统一为"2026年06月22日 星期一 上午10点24分",这种中文格式strftime原生不支持,需要组合处理:
from datetime import datetime
def format_chinese_time(dt=None):
if dt is None:
dt = datetime.now()
weekday_map = {0: '日', 1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六'}
year = dt.strftime("%Y")
month = dt.strftime("%m").lstrip("0") # 去掉前导零
day = dt.strftime("%d").lstrip("0")
weekday = weekday_map[dt.weekday()]
hour = dt.strftime("%I").lstrip("0")
minute = dt.strftime("%M")
period = dt.strftime("%p")
period_cn = "上午" if period == "AM" else "下午"
return f"{year}年{month}月{day}日 星期{weekday} {period_cn}{hour}点{minute}分"
print(format_chinese_time())
# 输出:2026年6月22日 星期一 上午10点24分
这段代码的关键在于:strftime只负责提取数值,中文拼接由字符串格式化完成。不要试图用一个格式符解决所有问题。
场景3:处理时区时间(跨境业务必看)
strftime本身不处理时区,它只是把datetime对象"拍照"成字符串。如果你的datetime对象是带时区信息的(timezone-aware),%z和%Z才会有值。
from datetime import datetime, timezone, timedelta
# UTC时间
utc_now = datetime.now(timezone.utc)
print("UTC:", utc_now.strftime("%Y-%m-%d %H:%M:%S %z"))
# 输出:UTC: 2026-06-22 02:24:22 +0000
# 北京时间(UTC+8)
beijing = utc_now + timedelta(hours=8)
print("北京:", beijing.strftime("%Y-%m-%d %H:%M:%S %z"))
# 输出:北京: 2026-06-22 10:24:22 +0800
# 错误示范:naive datetime直接用%z
naive = datetime.now()
print("无时区:", naive.strftime("%Y-%m-%d %H:%M:%S %z"))
# 输出:无时区: 2026-06-22 10:24:22 (空字符串,不是+0800)
关键结论:%z只对timezone-aware的datetime对象有效。 如果你的系统涉及多时区,务必使用pytz或zoneinfo模块先转换时区,再调用strftime。
场景4:解析用户输入的日期字符串
用户在表单里输入"2026/06/22",你需要把它转成datetime对象存数据库。这时候用strptime:
from datetime import datetime
def parse_user_date(date_str):
"""解析用户输入的日期,支持多种分隔符"""
formats = [
"%Y/%m/%d",
"%Y-%m-%d",
"%Y.%m.%d",
"%Y%m%d"
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期格式: {date_str}")
# 测试
print(parse_user_date("2026/06/22"))
print(parse_user_date("2026-06-22"))
print(parse_user_date("20260622"))
# 全部输出:2026-06-22 00:00:00
这种"多格式尝试"的写法在Web开发中非常实用。用户输入格式不可控,与其报错,不如多试几种常见格式。
场景5:计算两个时间的差值并格式化输出
运维监控场景经常需要输出"系统已运行 X天 X小时 X分钟":
from datetime import datetime, timedelta
def format_uptime(start_time: datetime) -> str:
"""格式化系统运行时长"""
now = datetime.now()
delta = now - start_time
days = delta.days
seconds = delta.seconds
hours = seconds // 3600
minutes = (seconds % 3600) // 60
return f"系统已运行 {days}天 {hours}小时 {minutes}分钟"
# 模拟系统启动时间(7天前)
boot_time = datetime.now() - timedelta(days=7, hours=3, minutes=45)
print(format_uptime(boot_time))
# 输出:系统已运行 7天 3小时 45分钟
这里有个细节:timedelta.seconds只返回当天剩余秒数(不超过86400),总秒数要用delta.total_seconds()。我在代码里用delta.days单独取天数,再用delta.seconds取余数,这是最清晰的写法。
四、常见陷阱与最佳实践
陷阱1:月份和日期的前导零
%m和%d默认带前导零(06、09),但很多业务场景不需要。用.lstrip("0")或转成int再转回str都可以:
dt = datetime(2026, 6, 9)
print(dt.strftime("%m")) # 06
print(str(int(dt.strftime("%m")))) # 6
陷阱2:线程安全问题
time.strftime()是线程安全的,但datetime.strftime()在某些旧版本Python中存在共享状态问题。生产环境建议统一使用datetime模块的方法,并确保Python版本≥3.7。
陷阱3:Windows与Linux的%z差异
Windows上%z的输出格式是+08:00(带冒号),Linux上是+0800(不带冒号)。如果你的字符串要跨平台解析,需要标准化处理:
def normalize_timezone(dt):
tz_str = dt.strftime("%z")
if ":" in tz_str:
return tz_str.replace(":", "")
return tz_str
五、总结:记住这张思维导图
strftime
├── 方向:datetime → 字符串(输出/格式化)
├── 兄弟:strptime(字符串 → datetime,输入/解析)
├── 核心:%开头的格式符,大小写敏感
├── 高频组合:
│ ├── "%Y-%m-%d %H:%M:%S" → 数据库标准
│ ├── "%Y%m%d_%H%M%S" → 日志文件名
│ └── "%Y年%m月%d日" → 中文报表
├── 铁律:
│ ├── 年份用%Y不用%y
│ ├── 12小时制必须配%p
│ └── 时区用timezone-aware对象
└── 验证:改完配置先跑一遍,看输出是否符合预期
strftime本身不复杂,复杂的是业务场景对时间格式的千奇百怪的要求。掌握格式符只是第一步,真正的功力在于知道什么场景该用什么格式、怎么处理时区、怎么防御用户的奇葩输入。
把上面五个实战场景吃透,你在时间处理这块就已经能覆盖90%的业务需求了。剩下那10%,交给arrow或pendulum这类第三方库就好——但前提是,你得先把strftime的底层逻辑搞明白。