系列文章
Python 从入门到精通
第 21 / 36 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办 -
12
基础阶段练习:命令行记账本 -
13
类和对象:面向对象入门 -
14
继承、组合与特殊方法 -
15
迭代器与生成器 -
16
列表推导式与生成器表达式 -
17
装饰器:给函数增加能力 -
18
上下文管理器与 with -
19
类型标注与 dataclass -
20
正则表达式:从文本中找规律 -
21
日期、时间与时区正在阅读 -
22
日志与调试 -
23
虚拟环境与依赖管理 -
24
测试:让修改不再提心吊胆 -
25
网络请求:用 Python 调用 API -
26
网页解析与合规采集 -
27
操作 Excel、CSV 与批量文件 -
28
SQLite:给程序加一个数据库 -
29
数据分析入门:NumPy 与 Pandas -
30
画图:把数据变得直观 -
31
Flask 入门:做一个小网站 -
32
异步编程:同时处理多项任务 -
33
线程、进程与并发选择 -
34
性能分析与优化 -
35
项目结构、配置与发布 -
36
综合项目:从需求到上线
程序里经常要跟”时间”打交道:日志要记时间戳、任务要算倒计时、数据要按日期分组。Python 的 datetime 模块是处理日期时间的标准答案。这一篇把它讲透——包括最容易出错的时区问题。
四个核心类型
datetime 模块里四个类,先分清:
from datetime import date, time, datetime, timedelta
# date:只有日期
d = date(2026, 8, 14)
print(d) # 2026-08-14
# time:只有时间
t = time(14, 30, 0)
print(t) # 14:30:00
# datetime:日期 + 时间(最常用)
dt = datetime(2026, 8, 14, 14, 30, 0)
print(dt) # 2026-08-14 14:30:00
# timedelta:时间差(用于计算)
diff = timedelta(days=3, hours=2)
print(diff) # 3 days, 2:00:00
实际用的时候,datetime 一个类打天下,另外三个是辅助。
获取当前时间
from datetime import datetime
now = datetime.now() # 本地时间
print(now) # 2026-08-14 16:30:00.123456
print(now.year) # 2026
print(now.month) # 8
print(now.day) # 14
print(now.hour) # 16
print(now.weekday()) # 4(周一=0,周日=6)
today = datetime.today() # 和 now() 差不多
utc = datetime.utcnow() # UTC 时间(注意:已过时,见下面时区节)
时间计算:timedelta
日期加减用 timedelta,直接 + -:
from datetime import datetime, timedelta
now = datetime.now()
tomorrow = now + timedelta(days=1)
yesterday = now - timedelta(days=1)
next_week = now + timedelta(weeks=1)
in_90_min = now + timedelta(minutes=90)
print(tomorrow.date()) # 明天的日期
print((tomorrow - now).days) # 1(两个日期相减得到 timedelta)
# 倒计时:距离某个时间还有多久
deadline = datetime(2026, 12, 31, 23, 59, 59)
left = deadline - now
print(f"距离年底还有 {left.days} 天 {left.seconds // 3600} 小时")
格式化:strftime 和 strptime
两个方向相反的转换,名字记法:str f time(时间→字符串 format)、str p time(字符串→时间 parse):
from datetime import datetime
now = datetime.now()
# datetime → 字符串(strftime)
print(now.strftime("%Y-%m-%d")) # 2026-08-14
print(now.strftime("%Y/%m/%d %H:%M")) # 2026/08/14 16:30
print(now.strftime("%H:%M:%S")) # 16:30:00
print(now.strftime("%A")) # Friday(星期几,英文)
print(now.strftime("%j")) # 226(一年中的第几天)
# 字符串 → datetime(strptime,格式必须一一对应)
s = "2026-08-14 16:30:00"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
print(dt) # 2026-08-14 16:30:00
常用格式符速查:%Y 年、%m 月、%d 日、%H 时(24h)、%I 时(12h)、%M 分、%S 秒、%p AM/PM、%A 星期全名。
时区:最容易翻车的地方
先说结论:存储用 UTC,展示用本地时间。服务器可能在美国,用户在敦化,不处理时区,时间就乱了。
from datetime import datetime, timezone, timedelta
# UTC 时间(带时区信息)
utc_now = datetime.now(timezone.utc)
print(utc_now) # 2026-08-14 08:30:00+00:00(注意末尾的 +00:00)
# 转北京时间(UTC+8)
beijing = timezone(timedelta(hours=8))
bj_now = utc_now.astimezone(beijing)
print(bj_now) # 2026-08-14 16:30:00+08:00
# 推荐:用 zoneinfo 按 IANA 时区名(Python 3.9+)
from zoneinfo import ZoneInfo
bj = utc_now.astimezone(ZoneInfo("Asia/Shanghai"))
print(bj) # 2026-08-14 16:30:00+08:00
三个坑点:
datetime.utcnow()已过时——返回的 UTC 时间不带时区信息,容易误当本地时间。用datetime.now(timezone.utc)- 不带时区信息的 datetime 叫 naive,带了的叫 aware,两者不能直接比较,会报 TypeError
- 服务器时区 ≠ 用户时区,展示前一定要转换
时间戳:和时间打交道的老朋友
import time
from datetime import datetime
# 当前时间戳(秒,1970 年以来的秒数)
ts = time.time()
print(ts) # 1786600000.123
# 时间戳 ↔ datetime
dt = datetime.fromtimestamp(ts) # 转本地时间
print(dt)
ts2 = dt.timestamp() # 转回时间戳
print(ts2)
# 纳秒级(计时用)
start = time.perf_counter()
# ... 干点活 ...
print(f"耗时 {time.perf_counter() - start:.4f} 秒") # 高精度计时,返回浮点秒
时间戳适合存储(数字好比较、好排序、跨语言通用),展示时再转 datetime。
实战:日志文件名 + 按天分组
from datetime import datetime, timedelta
from pathlib import Path
# 1. 带日期的日志文件名
now = datetime.now()
log_path = Path(f"logs/app-{now:%Y%m%d-%H%M}.log")
print(log_path) # logs/app-20260814-1630.log
# 2. 生成最近 7 天的日期列表
days = [datetime.now() - timedelta(days=i) for i in range(7)]
for d in reversed(days):
print(d.strftime("%m-%d %A"))
# 3. 解析"2026-08-14"这种用户输入
def parse_date(s):
try:
return datetime.strptime(s, "%Y-%m-%d")
except ValueError:
return None
print(parse_date("2026-08-14")) # 2026-08-14 00:00:00
print(parse_date("随便写的")) # None(解析失败不崩溃)
新手坑
坑 1:strptime 格式不匹配。格式符必须和字符串一一对应。例如 "2026-08-14" 用 "%Y-%d-%m" 解析会得到错误的月日(把 14 当月份),甚至报 ValueError。用户输入五花八门,先清洗再解析。
坑 2:naive 和 aware 混用。比较/运算时报 TypeError: can't compare offset-naive and offset-aware datetimes。统一都带时区或都不带。
坑 3:utcnow 假 UTC。拿到的对象没时区信息,转换时全乱。用 now(timezone.utc)。
小结
- date/time/datetime/timedelta 四个类,datetime 最常用
- 加减用
timedelta;两个时间相减得时间差 strftime时间→字符串,strptime字符串→时间,格式符要一一对应- 时区:存储用 UTC(
now(timezone.utc)),展示用astimezone/ZoneInfo转本地 - 时间戳
time.time()适合存储,展示时转 datetime
练习
- 写程序输出”今天是 2026年8月14日 星期五”(用 strftime 的中文组合)
- 写程序:输入生日(YYYY-MM-DD),计算活了多少天(提示:今天 – 生日)
- 思考:为什么说”存储用 UTC、展示用本地时间”?如果服务器在美国、用户在敦化,直接存本地时间会出什么问题?