日期和时间
日期看着简单,可 1 月 15 日加 30 天是哪天、离截止日还有几天,算起来就麻烦了。Python 的 datetime 模块知道每个月有几天、哪年是闰年、哪天星期几,这些都不用你操心。这一课讲它的三种主要类型、日期和文字互相转换、日期加减,以及给自己的代码计时。
造一个日期
datetime 模块里有三种最常用的类型,按名字导入:
| 类型 | 装的是 | 例子 |
|---|---|---|
date | 某一天 | date(2026, 3, 14) |
datetime | 某一天的某个时刻 | datetime(2026, 3, 14, 18, 30) |
timedelta | 一段时间 | timedelta(days=7) |
1from datetime import date, datetime2d = date(2026, 3, 14)3print(d)4print(d.year, d.month, d.day)5print(d.weekday())6t = datetime(2026, 3, 14, 18, 30)7print(t)8print(t.hour, t.minute)
2026-03-14 2026 3 14 5 2026-03-14 18:30:00 18 30
实参从大到小:年、月、日,再是时、分、秒。显示出来是标准的 ISO 顺序,年在前。weekday() 从星期一算 0,所以 5 是星期六;要中文的星期几,可以用 "一二三四五六日"[d.weekday()]。
不存在的日期会报错,Python 不会替你猜:
1from datetime import date2print(date(2028, 2, 29))3print(date(2026, 2, 29))
2028-02-29
Traceback (most recent call last):
File "main.py", line 3, in <module>
print(date(2026, 2, 29))
ValueError: day is out of range for month发生了什么: 值不对:类型是对的,但这个值用不了。
2028 年是闰年,2026 年不是,所以第二行抛出 ValueError。
今天和现在
date.today() 得到今天的日期,datetime.now() 得到现在的日期和时间,都是从电脑的时钟读的。
1from datetime import date, datetime2today = date.today()3now = datetime.now()4print(type(today))5print(type(now))6print(today > date(2020, 1, 1))7print(now > datetime(2020, 1, 1))
<class 'datetime.date'> <class 'datetime.datetime'> True True
这一页没有直接显示它们的值:页面上的输出是网站构建时跑出来的,显示出来的会是那天的日期。在你自己的电脑上,print(today) 和 print(now) 会显示类似这样的东西:
2026-11-01 2026-11-01 09:41:27.318204
秒后面那一段是微秒。now.date() 只留下日期,和 today 一样。这一课后面都用固定的日期,结果每次都一样。
日期变文字:strftime
strftime(string format time)把日期变成你想要的任何样子的文字。每个 % 代码换成日期的一部分,其他字符原样保留。
1from datetime import datetime2t = datetime(2026, 3, 14, 18, 30)3print(t.strftime("%d/%m/%Y"))4print(t.strftime("%A, %B %d, %Y"))5print(t.strftime("%a %d %b, %I:%M %p"))6print(t.strftime("%Y 年的第 %j 天"))7print(f"{t:%Y年%m月%d日 %H:%M}")8print(t.isoformat())
14/03/2026 Saturday, March 14, 2026 Sat 14 Mar, 06:30 PM 2026 年的第 073 天 2026年03月14日 18:30 2026-03-14T18:30:00
| 代码 | 意思 | 2026 年 3 月 14 日 18:30 |
|---|---|---|
%Y / %y | 年 | 2026 / 26 |
%m / %d | 月 / 日,两位数 | 03 / 14 |
%B / %b | 月份名 | March / Mar |
%A / %a | 星期名 | Saturday / Sat |
%H / %I | 时,24 小时制 / 12 小时制 | 18 / 06 |
%M / %S | 分 / 秒 | 30 / 00 |
%p | 上午或下午 | PM |
%j | 一年里的第几天 | 073 |
月份名和星期名是英文的;中文日期就像上面那样,把「年月日」直接写进去。这些代码也能写在 f-string 的冒号后面,和第 24 课的格式说明符一样。isoformat() 给出电脑之间交换日期用的标准格式。
文字变日期:strptime
datetime.strptime(string parse time)反过来:给它一段文字和这段文字的格式,它造出一个 datetime。
1from datetime import datetime2t = datetime.strptime("2026年3月14日 18:30", "%Y年%m月%d日 %H:%M")3print(t)4d = datetime.strptime("2026-03-14", "%Y-%m-%d").date()5print(d)6print(d.strftime("%m月%d日"))
2026-03-14 18:30:00 2026-03-14 03月14日
格式必须和文字一模一样,分隔符也算。对不上就是 ValueError,可以像第 21 课那样接住:
1from datetime import datetime2try:3 datetime.strptime("14/03/2026", "%Y-%m-%d")4except ValueError as e:5 print(f"日期不对:{e}")
日期不对:time data '14/03/2026' does not match format '%Y-%m-%d'
日期加减
timedelta 是一段时间。日期加上它就往后走,减去它就往前走。月底、闰年 Python 都会处理好。
1from datetime import date, datetime, timedelta2d = date(2026, 1, 15)3print(d + timedelta(days=30))4print(d - timedelta(weeks=3))5t = datetime(2026, 3, 14, 22, 0)6print(t + timedelta(hours=5))7print(timedelta(hours=36))8print(timedelta(hours=36).total_seconds())
2026-02-14 2025-12-25 2026-03-15 03:00:00 1 day, 12:00:00 129600.0
两个日期相减,得到一个 timedelta。它的 .days 就是相差的天数。
这段代码会显示什么?
1from datetime import date2gap = date(2026, 3, 1) - date(2026, 2, 1)3print(gap)
日期相减得到 timedelta,显示出来是天数,再加上「时:分:秒」。2026 年 2 月有 28 天。只要 28 的话,用 gap.days。
1from datetime import date, datetime2gap = date(2026, 12, 24) - date(2026, 11, 1)3print(gap.days)4shift = datetime(2026, 3, 14, 18, 30) - datetime(2026, 3, 14, 9, 15)5print(shift)6print(shift.total_seconds() / 3600)
53 9:15:00 9.25
改日期的另一个办法是 replace():得到一份把某几部分换掉的副本,比如 d.replace(year=2027)。没有 timedelta(months=1),因为每个月天数不一样;想要「下个月的同一天」,用 replace(month=...)。新的月份里没有这一天时(比如 4 月 31 日),会引发 ValueError。
比较日期
日期的比较和你想的一样:早的小,晚的大。所以 <、==、sorted、min、max 都能用。
1from datetime import date2deliveries = [date(2026, 5, 2), date(2026, 3, 14), date(2026, 4, 1)]3for d in sorted(deliveries):4 print(d)5print(min(deliveries))6today = date(2026, 4, 10)7late = [d for d in deliveries if d < today]8print(f"已经过了 {len(late)} 个")
2026-03-14 2026-04-01 2026-05-02 2026-03-14 已经过了 2 个
这里的 today 是固定的日期,所以结果不会变。真正的程序里会写 today = date.today()。
给代码计时
想知道一段代码跑了多久,在它前后各读一次 time.perf_counter(),再相减。读出来的数本身没有意义,有意义的只是两次的差。
1import time2start = time.perf_counter()3total = 04for n in range(100000):5 total += n6elapsed = time.perf_counter() - start7print(total)8print(elapsed > 0)
4999950000 True
每次跑、每台电脑上用的时间都不一样,所以这一页只检查「确实用了一点时间」。在你的电脑上,print(f"用了 {elapsed:.4f} 秒") 会显示类似:
用了 0.0062 秒
计时用 perf_counter,想知道「现在几点」用 datetime.now():perf_counter 更精确,而且改电脑时钟时它不会跳。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
写全的日期
✓把 d 显示成下面这样:年月日,再加中文的星期几。
2026年07月04日,星期六
1from datetime import date2d = date(2026, 7, 4)3print(d)
还有几天
✓显示 还有,后面跟着从 today 到 festival 的天数,最后是 天。
还有 53 天
1from datetime import date2today = date(2026, 11, 1)3festival = date(2026, 12, 24)4print(festival)
十天后到期
✓读出 text 里的日期,加 10 天,按「年月日」显示出来。
2026年04月07日
1from datetime import datetime, timedelta2text = "2026-03-28"3print(text)
这一课学会了
from datetime import date, datetime, timedelta:某一天、某一天的某个时刻、一段时间。date(2026, 3, 14)造一个日期;不存在的日期会抛出ValueError。date.today()和datetime.now()读电脑的时钟,每次跑都可能不一样。strftime和f"{d:%Y年%m月%d日}"把日期变成文字;strptime把文字读成datetime。- 日期加
timedelta还是日期;日期减日期是timedelta,.days是天数。 - 日期按时间先后比较和排序。
- 在代码前后各读一次
time.perf_counter(),就知道它跑了多久。