系列文章

Python 从入门到精通

第 21 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区正在阅读
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 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

练习

  1. 写程序输出”今天是 2026年8月14日 星期五”(用 strftime 的中文组合)
  2. 写程序:输入生日(YYYY-MM-DD),计算活了多少天(提示:今天 – 生日)
  3. 思考:为什么说”存储用 UTC、展示用本地时间”?如果服务器在美国、用户在敦化,直接存本地时间会出什么问题?