7 minutes
日期与时间处理
Python 中的日期和时间
日期和时间处理是编程中常见的需求——日志记录、数据统计、任务调度、业务逻辑计算,几乎每个项目都离不开它。Python 提供了多个内置模块来处理日期和时间,其中 datetime 是最核心、最常用的模块。
datetime 模块核心类
datetime 模块包含以下几个核心类:
| 类 | 说明 | 示例 |
|---|---|---|
date |
日期(年、月、日) | date(2026, 5, 21) |
time |
时间(时、分、秒、微秒) | time(14, 30, 0) |
datetime |
日期+时间 | datetime(2026, 5, 21, 14, 30) |
timedelta |
时间差 | timedelta(days=7) |
timezone |
时区 | timezone(timedelta(hours=8)) |
创建日期和时间对象
from datetime import date, time, datetime, timedelta
# 创建日期
d = date(2026, 5, 21)
print(d) # 2026-05-21
print(d.year) # 2026
print(d.month) # 5
print(d.day) # 21
# 创建时间
t = time(14, 30, 15, 500000)
print(t) # 14:30:15.500000
print(t.hour) # 14
print(t.minute) # 30
print(t.second) # 15
# 创建日期时间
dt = datetime(2026, 5, 21, 14, 30, 0)
print(dt) # 2026-05-21 14:30:00
获取当前时间
from datetime import date, datetime
# 当前日期
today = date.today()
print(today) # 2026-05-21
# 当前日期时间
now = datetime.now()
print(now) # 2026-05-21 14:30:00.123456
# 当前 UTC 时间
utc_now = datetime.utcnow()
print(utc_now) # 2026-05-21 06:30:00.123456(假设 UTC+8)
日期格式化:strftime 和 strptime
strftime — 日期转字符串
strftime 将日期时间对象格式化为字符串:
from datetime import datetime
now = datetime.now()
# 常用格式化
print(now.strftime("%Y-%m-%d")) # 2026-05-21
print(now.strftime("%Y年%m月%d日")) # 2026年05月21日
print(now.strftime("%H:%M:%S")) # 14:30:00
print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2026-05-21 14:30:00
print(now.strftime("%A")) # Thursday
print(now.strftime("%a")) # Thu
print(now.strftime("%B")) # May
print(now.strftime("%j")) # 141(年中第几天)
strptime — 字符串转日期
strptime 将字符串解析为日期时间对象:
from datetime import datetime
# 解析常见格式
dt1 = datetime.strptime("2026-05-21", "%Y-%m-%d")
print(dt1) # 2026-05-21 00:00:00
dt2 = datetime.strptime("2026/05/21 14:30:00", "%Y/%m/%d %H:%M:%S")
print(dt2) # 2026-05-21 14:30:00
dt3 = datetime.strptime("2026年05月21日", "%Y年%m月%d日")
print(dt3) # 2026-05-21 00:00:00
格式化符号速查表
| 符号 | 说明 | 示例 |
|---|---|---|
%Y |
4位年份 | 2026 |
%y |
2位年份 | 26 |
%m |
月份(01-12) | 05 |
%d |
日期(01-31) | 21 |
%H |
小时(00-23) | 14 |
%I |
小时(01-12) | 02 |
%M |
分钟(00-59) | 30 |
%S |
秒(00-59) | 00 |
%f |
微秒(6位) | 123456 |
%A |
星期全名 | Thursday |
%a |
星期缩写 | Thu |
%B |
月份全名 | May |
%b |
月份缩写 | May |
%j |
年中第几天 | 141 |
%w |
星期几(0=周日) | 4 |
%Z |
时区名 | CST |
timedelta:日期时间算术
timedelta 表示两个日期时间之间的差值,可以用于日期运算:
from datetime import datetime, timedelta, date
now = datetime.now()
# 加减天数
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
print(f"昨天: {yesterday}")
print(f"明天: {tomorrow}")
# 加减小时/分钟/秒
one_hour_later = now + timedelta(hours=1)
half_hour_ago = now - timedelta(minutes=30)
print(f"一小时后: {one_hour_later}")
print(f"半小时前: {half_hour_ago}")
# 组合使用
next_week = now + timedelta(weeks=1, hours=2)
print(f"下周同一时间(加2小时): {next_week}")
# 计算时间差
start = datetime(2026, 1, 1)
end = datetime(2026, 5, 21)
delta = end - start
print(f"相差天数: {delta.days}") # 相差天数
print(f"相差秒数: {delta.total_seconds()}") # 总秒数
timedelta 的实用计算
from datetime import date
def days_until_birthday(birthday: date) -> int:
"""计算距离下一次生日还有多少天"""
today = date.today()
# 今年的生日
next_birthday = date(today.year, birthday.month, birthday.day)
# 如果今年生日已过,计算到明年
if next_birthday < today:
next_birthday = date(today.year + 1, birthday.month, birthday.day)
return (next_birthday - today).days
# 计算距离下一次生日
birthday = date(1990, 10, 1)
print(f"距离生日还有 {days_until_birthday(birthday)} 天")
时区处理
时区感知 vs 时区 naive
datetime 对象分为两类:
- naive(朴素):不含时区信息
- aware(感知):包含时区信息
from datetime import datetime, timezone, timedelta
# naive datetime
naive = datetime.now()
print(naive.tzinfo) # None
# aware datetime(UTC)
utc_dt = datetime.now(timezone.utc)
print(utc_dt.tzinfo) # UTC
# 创建带时区的 datetime
tz_shanghai = timezone(timedelta(hours=8))
shanghai_dt = datetime(2026, 5, 21, 14, 30, tzinfo=tz_shanghai)
print(shanghai_dt) # 2026-05-21 14:30:00+08:00
使用 zoneinfo(Python 3.9+)
Python 3.9 引入了 zoneinfo 模块,使用 IANA 时区数据库:
from datetime import datetime
from zoneinfo import ZoneInfo
# 获取不同时区的时间
now_utc = datetime.now(ZoneInfo("UTC"))
now_shanghai = datetime.now(ZoneInfo("Asia/Shanghai"))
now_tokyo = datetime.now(ZoneInfo("Asia/Tokyo"))
now_newyork = datetime.now(ZoneInfo("America/New_York"))
now_london = datetime.now(ZoneInfo("Europe/London"))
print(f"UTC: {now_utc}")
print(f"上海: {now_shanghai}")
print(f"东京: {now_tokyo}")
print(f"纽约: {now_newyork}")
print(f"伦敦: {now_london}")
# 时区转换
shanghai_time = datetime(2026, 5, 21, 14, 30, tzinfo=ZoneInfo("Asia/Shanghai"))
# 转换为纽约时间
ny_time = shanghai_time.astimezone(ZoneInfo("America/New_York"))
print(f"上海 14:30 = 纽约 {ny_time.strftime('%H:%M')}")
常见时区名称(IANA)
Asia/Shanghai 中国标准时间 (UTC+8)
Asia/Tokyo 日本标准时间 (UTC+9)
Asia/Singapore 新加坡时间 (UTC+8)
Asia/Kolkata 印度标准时间 (UTC+5:30)
Europe/London 格林威治时间 (UTC+0/UTC+1)
Europe/Berlin 中欧时间 (UTC+1/UTC+2)
America/New_York 美东时间 (UTC-5/UTC-4)
America/Chicago 美中时间 (UTC-6/UTC-5)
America/Los_Angeles 美西时间 (UTC-8/UTC-7)
Australia/Sydney 澳大利亚东部时间 (UTC+10/UTC+11)
UTC 协调世界时
pytz 兼容方案
对于 Python 3.8 及更早版本,可以使用 pytz:
# pip install pytz
import pytz
from datetime import datetime
tz_shanghai = pytz.timezone("Asia/Shanghai")
shanghai_now = datetime.now(tz_shanghai)
print(shanghai_now)
# 时区转换
utc_time = datetime.now(pytz.UTC)
shanghai_time = utc_time.astimezone(pytz.timezone("Asia/Shanghai"))
print(f"UTC: {utc_time}")
print(f"上海: {shanghai_time}")
使用 dateutil 进行高级解析
dateutil 库可以解析自然语言格式的日期(需单独安装):
pip install python-dateutil
from dateutil import parser
from dateutil.relativedelta import relativedelta
# 自动解析各种格式
print(parser.parse("2026-05-21"))
print(parser.parse("05/21/2026"))
print(parser.parse("May 21, 2026"))
print(parser.parse("21 May 2026 14:30:00"))
print(parser.parse("2026-05-21T14:30:00Z"))
print(parser.parse("明天", fuzzy=True)) # 部分支持中文
# relativedelta: 比 timedelta 更强大
from datetime import date
today = date.today()
# 计算下个月的同一天
next_month = today + relativedelta(months=1)
print(f"下个月: {next_month}")
# 计算下周五
next_friday = today + relativedelta(weekday=4) # 4 = Friday
print(f"下周五: {next_friday}")
# 计算两个日期之间的年月日差
start = date(2020, 1, 1)
end = date(2026, 5, 21)
delta = relativedelta(end, start)
print(f"相差 {delta.years} 年 {delta.months} 月 {delta.days} 天")
时间戳与 time 模块
datetime 与时间戳互转
from datetime import datetime
import time
# 当前时间戳
ts = time.time()
print(f"当前时间戳: {ts}")
# 时间戳转 datetime
dt = datetime.fromtimestamp(ts)
print(f"时间戳转日期: {dt}")
# datetime 转时间戳
ts_back = dt.timestamp()
print(f"日期转时间戳: {ts_back}")
# UTC 时间戳转 datetime
utc_dt = datetime.utcfromtimestamp(ts)
print(f"UTC 时间: {utc_dt}")
time 模块常用函数
import time
# sleep:暂停执行
print("开始等待...")
time.sleep(2) # 暂停2秒
print("继续执行")
# 性能计时
start = time.perf_counter()
# 执行一些操作
sum(range(1_000_000))
end = time.perf_counter()
print(f"执行耗时: {end - start:.6f}秒")
# 格式化当前时间
print(time.strftime("%Y-%m-%d %H:%M:%S"))
# 解析时间字符串
struct_time = time.strptime("2026-05-21", "%Y-%m-%d")
print(struct_time.tm_year) # 2026
print(struct_time.tm_mon) # 5
print(struct_time.tm_yday) # 141
calendar 模块
import calendar
# 打印月历
cal = calendar.month(2026, 5)
print(cal)
# 判断闰年
print(calendar.isleap(2024)) # True
print(calendar.isleap(2025)) # False
# 某个月的天数
print(calendar.monthrange(2026, 5)) # (4, 31) => 周4开始,共31天
# 某天是星期几
print(calendar.weekday(2026, 5, 21)) # 3 (0=周一, 3=周四)
实战案例
1. 任务调度计算
from datetime import datetime, timedelta
from typing import Optional
def calculate_next_run(
current_time: Optional[datetime] = None,
interval_hours: int = 6,
work_hours: tuple = (9, 18)
) -> datetime:
"""
计算下一次任务执行时间。
只在工作时间(9:00-18:00)内执行,非工作时间顺延到次日9点。
"""
if current_time is None:
current_time = datetime.now()
next_time = current_time + timedelta(hours=interval_hours)
# 如果不在工作时间内,调整到下一个工作日的 9:00
work_start = next_time.replace(hour=9, minute=0, second=0, microsecond=0)
work_end = next_time.replace(hour=18, minute=0, second=0, microsecond=0)
if next_time > work_end:
# 顺延到下一个工作日
next_time = work_start + timedelta(days=1)
elif next_time < work_start:
next_time = work_start
return next_time
now = datetime(2026, 5, 21, 15, 0, 0)
print(f"当前: {now}")
print(f"下次执行: {calculate_next_run(now)}")
2. 年龄计算
from datetime import date
def calculate_age(birth_date: date) -> int:
"""计算实足年龄"""
today = date.today()
age = today.year - birth_date.year
# 如果今年生日还没过,减一岁
if (today.month, today.day) < (birth_date.month, birth_date.day):
age -= 1
return age
# 测试
birth = date(1990, 10, 1)
print(f"出生日期: {birth}")
print(f"当前年龄: {calculate_age(birth)} 岁")
3. 工作日计算
from datetime import date, timedelta
def add_business_days(start: date, days: int) -> date:
"""给指定日期加上 N 个工作日"""
current = start
added = 0
while added < days:
current += timedelta(days=1)
if current.weekday() < 5: # 周一到周五
added += 1
return current
def count_business_days(start: date, end: date) -> int:
"""计算两个日期之间的工作日数量"""
count = 0
current = start
while current <= end:
if current.weekday() < 5:
count += 1
current += timedelta(days=1)
return count
today = date(2026, 5, 21) # 周四
print(f"{today} 起10个工作日: {add_business_days(today, 10)}")
print(f"5月工作日数: {count_business_days(date(2026, 5, 1), date(2026, 5, 31))}")
4. 时区转换工具
from datetime import datetime
from zoneinfo import ZoneInfo
from typing import Optional
def convert_timezone(
dt_str: str,
from_tz: str = "Asia/Shanghai",
to_tz: str = "America/New_York",
fmt: str = "%Y-%m-%d %H:%M:%S"
) -> Optional[str]:
"""时区转换工具"""
try:
# 解析输入时间
naive_dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M")
# 标记源时区
source_dt = naive_dt.replace(tzinfo=ZoneInfo(from_tz))
# 转换为目标时区
target_dt = source_dt.astimezone(ZoneInfo(to_tz))
return target_dt.strftime(fmt)
except Exception as e:
return f"转换失败: {e}"
print(convert_timezone("2026-05-21 14:30", "Asia/Shanghai", "America/New_York"))
# 2026-05-21 02:30 (上海14:30 = 纽约02:30)
print(convert_timezone("2026-05-21 14:30", "Asia/Shanghai", "Europe/London"))
# 2026-05-21 07:30
5. 日志时间解析
from datetime import datetime
from zoneinfo import ZoneInfo
import re
def parse_log_timestamp(log_line: str) -> datetime:
"""解析日志中的时间戳并转换为上海时间"""
# 匹配常见日志格式: [21/May/2026:10:30:25 +0000]
pattern = r"\[(\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2})\s([+-]\d{4})\]"
match = re.search(pattern, log_line)
if not match:
raise ValueError("无法解析日志时间戳")
dt_str, tz_str = match.groups()
# 解析时间
dt = datetime.strptime(dt_str, "%d/%b/%Y:%H:%M:%S")
# 解析时区偏移
tz_hours = int(tz_str[:3])
tz_minutes = int(tz_str[0] + tz_str[3:])
tz_offset = __import__("datetime").timedelta(hours=tz_hours, minutes=tz_minutes)
from zoneinfo import ZoneInfo
# 标记时区
dt_with_tz = dt.replace(tzinfo=__import__("datetime").timezone(tz_offset))
# 转换为上海时间
shanghai_tz = ZoneInfo("Asia/Shanghai")
return dt_with_tz.astimezone(shanghai_tz)
log = '192.168.1.1 - - [21/May/2026:10:30:25 +0000] "GET /api HTTP/1.1" 200'
parsed = parse_log_timestamp(log)
print(f"原始日志时间 (UTC): 10:30:25")
print(f"转换后 (上海时间): {parsed}")
6. 日期范围生成器
from datetime import datetime, timedelta
from typing import Iterator
def date_range(start: datetime, end: datetime, step: timedelta = timedelta(days=1)) -> Iterator[datetime]:
"""生成日期范围"""
current = start
while current <= end:
yield current
current += step
# 生成一周的日期
start = datetime(2026, 5, 18)
end = datetime(2026, 5, 24)
print("本周日期:")
for dt in date_range(start, end):
print(f" {dt.strftime('%A')}: {dt.strftime('%Y-%m-%d')}")
常见陷阱与注意事项
陷阱1: naive 和 aware datetime 不能混用
from datetime import datetime, timezone, timedelta
naive = datetime.now()
aware = datetime.now(timezone.utc)
# BAD: 会抛出 TypeError
# result = aware - naive
# GOOD: 统一转为同一种
result = aware - naive.replace(tzinfo=timezone.utc) # 或
result = aware.replace(tzinfo=None) - naive
陷阱2:datetime.now() 返回本地时间
from datetime import datetime, timezone
# 不要用 datetime.now() 来表示"当前时间"用于存储
# 推荐使用 UTC 时间存储
now_utc = datetime.now(timezone.utc)
# 展示时再转换为本地时间
local_tz = __import__("zoneinfo").ZoneInfo("Asia/Shanghai")
local_time = now_utc.astimezone(local_tz)
陷阱3:strptime 的性能
from datetime import datetime
# BAD: 在循环中重复解析相同格式
dates = ["2026-05-21", "2026-05-22", "2026-05-23"]
for d in dates:
dt = datetime.strptime(d, "%Y-%m-%d") # 每次都要解析格式字符串
# GOOD: 先编译格式
fmt = "%Y-%m-%d"
for d in dates:
dt = datetime.strptime(d, fmt)
小结
日期时间处理是每个 Python 程序员必备的技能。本章我们系统学习了 datetime 模块的五大核心类(date、time、datetime、timedelta、timezone),掌握了 strftime/strptime 的格式化和解析方法,了解了 timedelta 的日期算术运算、时区处理(naive vs aware、zoneinfo 的使用)、时间戳互转,以及 calendar 模块和 dateutil 库的高级功能。通过任务调度、年龄计算、工作日计算、时区转换、日志解析和日期范围生成六个实战案例,你应该已经能将日期时间知识应用到实际项目中。最后记住一个关键原则:存储用 UTC,展示转本地——这能避免绝大多数时区相关的 BUG。
Summary: datetime 类、时区、timedelta、格式化与实战案例。