Skip to content

datetime 模块 ​

上一章的 time 模块擅长处理时间戳、暂停和运行耗时。实际项目还经常需要表示某一天、安排任务时间、计算截止日期:如何让日期、时刻、时间差和时区信息保持清晰,并能继续参与计算?这些工作更适合使用 datetime 模块。

datetime 模块提供四个常用类:

类表示的内容示例
date日期2026-09-12
time一天中的时间09:30:00
datetime日期和时间2026-09-12 09:30:00
timedelta一段时间7 天、30 分钟

本章采用直接导入类的方式:

python
from datetime import date, datetime, time, timedelta

这里的 time 是 datetime 模块中的时间类,不是上一章学习的 time 模块。

datetime 的对象关系与时间线

日期对象 ​

date 对象只保存年、月、日:

python
from datetime import date

course_day = date(2026, 9, 12)
today = date.today()

print(course_day)
print(today)

date.today() 返回当前系统的本地日期,因此每次运行结果都可能不同。手动创建日期时,年、月、日必须合法,否则会抛出 ValueError。

日期对象的常用属性和方法如下:

python
print(course_day.year)
print(course_day.month)
print(course_day.day)
print(course_day.weekday())
print(course_day.isoweekday())

两种星期方法的编号规则不同:

方法星期一星期日
weekday()06
isoweekday()17

时间对象与日期时间对象 ​

datetime.time 表示一天中的时、分、秒和微秒,不包含具体日期:

python
from datetime import time

class_time = time(9, 30, 15, 500000)

print(class_time)
print(class_time.hour)
print(class_time.minute)
print(class_time.second)
print(class_time.microsecond)

datetime 对象同时保存日期和时间:

python
from datetime import datetime

started_at = datetime(2026, 9, 12, 9, 30, 15)
now = datetime.now()

print(started_at)
print(now)

datetime.now() 返回当前系统的本地日期和时间。没有提供的时、分、秒默认是 0:

python
midnight = datetime(2026, 9, 12)
print(midnight)

运行结果:

text
2026-09-12 00:00:00

字段访问与时间组合 ​

datetime 对象可以读取日期与时间字段,也可以分别取出两个部分:

python
print(started_at.year)
print(started_at.month)
print(started_at.day)
print(started_at.hour)
print(started_at.minute)
print(started_at.second)

print(started_at.date())
print(started_at.time())

datetime.combine() 可以把 date 和 time 合成完整时间:

python
from datetime import date, datetime, time

course_day = date(2026, 9, 12)
class_time = time(9, 30)
started_at = datetime.combine(course_day, class_time)

print(started_at)

运行结果:

text
2026-09-12 09:30:00

不可变对象与 replace() ​

date、time 和 datetime 对象都不能直接修改。replace() 会根据原对象创建一个新对象:

python
from datetime import datetime

started_at = datetime(2026, 9, 12, 9, 30)
delayed_at = started_at.replace(hour=10, minute=15)

print(started_at)
print(delayed_at)

原来的 started_at 不会改变。

日期时间与字符串 ​

日期时间经常需要写入文件、显示在页面上,或者从用户输入中读取。

ISO 格式 ​

标准日期字符串优先使用 isoformat() 和 fromisoformat(),不需要手写格式符:

python
from datetime import date, datetime

started_at = datetime(2026, 9, 12, 9, 30, 15)

print(started_at.isoformat())
print(date.fromisoformat("2026-09-12"))
print(datetime.fromisoformat("2026-09-12 09:30:15"))

运行结果:

text
2026-09-12T09:30:15
2026-09-12
2026-09-12 09:30:15

自定义格式 ​

非 ISO 格式可以使用 strftime() 和 strptime():

python
from datetime import datetime

started_at = datetime(2026, 9, 12, 9, 30, 15, 123456)
text = started_at.strftime("%Y年%m月%d日 %H:%M:%S.%f")
parsed_at = datetime.strptime("2026/09/12 09:30", "%Y/%m/%d %H:%M")

print(text)
print(parsed_at)

上一章已经介绍过常用格式符。本章新增的 %f 表示微秒,可以用于 datetime 的格式化和解析。

字符串与格式不一致,或者日期本身不合法时,strptime() 会抛出 ValueError。

时间差计算 ​

timedelta 表示一段持续时间。创建时可以使用周、天、小时、分钟、秒、毫秒和微秒等参数:

python
from datetime import timedelta

duration = timedelta(days=1, hours=2, minutes=30)
print(duration)

运行结果:

text
1 day, 2:30:00

时间偏移 ​

date 或 datetime 加减 timedelta,会得到新的日期时间对象:

python
from datetime import datetime, timedelta

started_at = datetime(2026, 9, 12, 9, 30)
deadline = started_at + timedelta(days=7, hours=2)
reminder = started_at - timedelta(minutes=30)

print(deadline)
print(reminder)

timedelta 可以乘除数字,但不能与普通数字直接相加:

python
one_hour = timedelta(hours=1)

print(one_hour * 3)
print(one_hour / 2)

如果执行 one_hour + 2,Python 无法判断数字 2 的单位,会抛出 TypeError。

时间间隔 ​

两个 date 或两个 datetime 对象相减,会得到 timedelta:

python
from datetime import datetime

started_at = datetime(2026, 9, 12, 9, 30)
finished_at = datetime(2026, 9, 12, 11, 15)
duration = finished_at - started_at

print(duration)
print(duration.total_seconds())

运行结果:

text
1:45:00
6300.0

需要完整秒数时,应使用 total_seconds()。duration.seconds 只表示去掉整天后剩余的秒数,不一定等于整个时间差。

日期时间比较 ​

同类且时区状态兼容的日期时间对象可以比较先后:

python
from datetime import datetime

start = datetime(2026, 9, 12, 9, 30)
deadline = datetime(2026, 9, 12, 12, 0)
current = datetime(2026, 9, 12, 10, 0)

print(current < deadline)
print(start <= current <= deadline)

朴素时间与时区时间 ​

datetime.now() 返回的对象通常不包含时区信息,这类对象称为朴素时间:

python
from datetime import datetime

local_now = datetime.now()
print(local_now.tzinfo)

输出的 None 表示对象没有保存时区。仅看到 2026-09-12 09:30:00,无法判断它表示北京时间还是其他地区的时间。

带时区时间 ​

Python 3.14 可以使用 UTC 获取带时区的 UTC 时间:

python
from datetime import UTC, datetime

utc_now = datetime.now(UTC)
local_now = datetime.now().astimezone()

print(utc_now)
print(local_now)
  • datetime.now(UTC) 返回带时区的 UTC 时间。
  • datetime.now().astimezone() 返回带系统本地时区的时间。

不要再使用 datetime.utcnow()。该方法从 Python 3.12 起已弃用,并且返回没有时区信息的对象。

时区规则比较复杂。本章只要求能够区分朴素时间和带时区时间;涉及跨地区业务时,应优先保存带时区的 UTC 时间,在显示时再转换成本地时间。

日期时间与时间戳 ​

时间戳本身不保存时区,但转换成日期时间时,需要决定按本地时间还是 UTC 显示:

python
from datetime import UTC, datetime

timestamp = 0

local_time = datetime.fromtimestamp(timestamp)
utc_time = datetime.fromtimestamp(timestamp, UTC)

print(local_time)
print(utc_time)
print(utc_time.timestamp())

其中:

  • 省略时区参数时,fromtimestamp() 返回本地朴素时间,结果受系统时区影响。
  • 传入 UTC 时,返回带时区的 UTC 时间。
  • timestamp() 可以把 datetime 对象转回时间戳。

不要使用从 Python 3.12 起已弃用的 datetime.utcfromtimestamp()。应使用 datetime.fromtimestamp(timestamp, UTC)。

对于朴素 datetime,timestamp() 会按照系统本地时间解释它;带时区对象的含义更明确。

综合案例:计算任务截止时间 ​

需求: 为一项课程资料整理任务记录创建时间、计算截止时间,并根据检查时刻判断任务是否超时。

功能分析: 使用带时区的 datetime 表示时刻,使用 timedelta 计算截止时间,使用比较运算和格式化输出任务状态。

下面创建一个带时区的任务,计算截止时间,并判断检查时刻是否已经超时:

python
from datetime import UTC, datetime, timedelta


def create_task(title, created_at, duration_minutes):
    return {
        "title": title,
        "created_at": created_at,
        "deadline": created_at + timedelta(minutes=duration_minutes),
    }


def show_task(task, checked_at):
    time_format = "%Y-%m-%d %H:%M:%S %Z"
    status = "已超时" if checked_at > task["deadline"] else "进行中"

    print(f"任务名称:{task['title']}")
    print(f"创建时间:{task['created_at'].strftime(time_format)}")
    print(f"截止时间:{task['deadline'].strftime(time_format)}")
    print(f"检查状态:{status}")


created_at = datetime(2026, 9, 12, 1, 30, tzinfo=UTC)
task = create_task("整理课程资料", created_at, 90)

checked_at = datetime(2026, 9, 12, 2, 45, tzinfo=UTC)
show_task(task, checked_at)

运行结果:

text
任务名称:整理课程资料
创建时间:2026-09-12 01:30:00 UTC
截止时间:2026-09-12 03:00:00 UTC
检查状态:进行中

这个案例串联了带时区的 datetime、timedelta、时间比较和格式化输出。

小结 ​

  • date 表示日期,time 表示一天中的时间,datetime 表示完整日期时间。
  • timedelta 表示时间差,可以参与日期时间加减,也可以乘除数字。
  • ISO 格式优先使用 isoformat() 和 fromisoformat()。
  • 自定义格式使用 strftime() 和 strptime()。
  • 朴素 datetime 不包含时区信息,带时区对象的时刻含义更明确。
  • Python 3.14 使用 datetime.now(UTC) 获取 UTC 时间。
  • 使用 fromtimestamp() 时,应明确需要本地时间还是 UTC 时间。

练习 ​

  1. 创建自己的生日 date 对象,输出年月日和星期。
  2. 使用 datetime.combine() 把明天的日期和 08:30 合并成完整时间。
  3. 把字符串 "2028/05/01 08:30" 解析成 datetime,再转换为 ISO 格式。
  4. 使用 timedelta(days=7) 计算今天一周后的日期。
  5. 创建两个 datetime 对象,计算它们相差多少秒。
  6. 修改综合案例的检查时间,让任务状态变成“已超时”。