专题:Python进阶编程系统学习
关键词:Python, datetime, date, time, timedelta, 时区, strftime, dateutil, pytz
一、datetime模块概述
Python内置的datetime模块是处理日期和时间的标准库,提供了五个核心类:date(日期)、time(时间)、datetime(日期时间)、timedelta(时间间隔)和timezone(时区)。这些类协同工作,覆盖了绝大多数日期时间处理场景。相比于第三方库,标准库的优势在于无需额外安装,但某些高级功能(如复杂时区数据库、工作日计算)需要借助dateutil或zoneinfo。
核心概念:在Python中,日期时间对象分为"naive"(朴素,不带时区信息)和"aware"(感知,带时区信息)两种。官方推荐在生产环境中始终使用aware对象以避免时区混淆问题。
| 类名 | 描述 | 示例 |
date | 表示年、月、日 | date(2026, 5, 5) |
time | 表示时、分、秒、微秒 | time(14, 30, 0) |
datetime | 组合日期和时间 | datetime(2026, 5, 5, 14, 30) |
timedelta | 时间间隔/差值 | timedelta(days=7) |
timezone | 固定偏移时区 | timezone(timedelta(hours=8)) |
二、日期时间创建与属性访问
2.1 创建date对象
from datetime import date
# 基本创建:年、月、日
d1 = date(2026, 5, 5)
print(d1) # 2026-05-05
# 获取当前本地日期
today = date.today()
print(today) # 2026-05-05
# 从时间戳创建
d2 = date.fromtimestamp(1774886400)
print(d2)
# 从ISO格式字符串创建
d3 = date.fromisoformat("2026-05-05")
print(d3)
# 替换属性生成新对象
d4 = d1.replace(year=2027)
print(d4) # 2027-05-05
2.2 创建time对象
from datetime import time
# 时、分、秒、微秒、时区信息
t1 = time(14, 30, 45)
print(t1) # 14:30:45
t2 = time(14, 30, 45, 123456)
print(t2) # 14:30:45.123456
# 属性访问
print(t1.hour, t1.minute, t1.second, t1.microsecond)
# 14 30 45 0
2.3 创建datetime对象
from datetime import datetime
# 同时指定日期和时间
dt1 = datetime(2026, 5, 5, 14, 30, 0)
print(dt1) # 2026-05-05 14:30:00
# 获取当前日期时间
now = datetime.now() # 本地时间
utc_now = datetime.utcnow() # UTC时间(不推荐,返回naive对象)
print(now)
# 获取当前UTC aware时间(推荐方式)
from datetime import timezone
utc_aware = datetime.now(timezone.utc)
print(utc_aware) # 2026-05-05 06:30:00+00:00
# combine():合并date和time对象
d = date(2026, 5, 5)
t = time(14, 30)
dt2 = datetime.combine(d, t)
print(dt2) # 2026-05-05 14:30:00
# 从字符串解析
dt3 = datetime.strptime("2026-05-05 14:30:00", "%Y-%m-%d %H:%M:%S")
print(dt3)
# 从ISO格式字符串解析(Python 3.7+)
dt4 = datetime.fromisoformat("2026-05-05T14:30:00")
print(dt4)
2.4 属性访问
from datetime import datetime
dt = datetime(2026, 5, 5, 14, 30, 45, 123456)
print(dt.year) # 2026
print(dt.month) # 5
print(dt.day) # 5
print(dt.hour) # 14
print(dt.minute) # 30
print(dt.second) # 45
print(dt.microsecond) # 123456
# 获取周几(Monday=0, Sunday=6)
print(dt.weekday()) # 1(周二)
# 获取周几(Monday=1, Sunday=7)
print(dt.isoweekday()) # 2(周二)
# 获取年月日(元组)
print(dt.timetuple()) # time.struct_time
# 获取ISO日历元组(年、周数、周几)
print(dt.isocalendar()) # (2026, 19, 2)
# 提取date和time部分
print(dt.date()) # 2026-05-05
print(dt.time()) # 14:30:45.123456
提示:isocalendar()返回的ISO周数在某些业务场景(如财务报表按周统计)中非常有用。ISO标准中,一周从周一开始,每年第一周是包含该年第一个周四的周。
三、timedelta与日期时间运算
3.1 timedelta基本用法
timedelta表示两个日期时间之间的差值,支持天、秒、微秒、毫秒、分钟、小时和周等参数。
from datetime import timedelta, datetime
# 创建时间间隔
td1 = timedelta(days=7) # 7天
td2 = timedelta(hours=3, minutes=30) # 3小时30分钟
td3 = timedelta(weeks=2, days=3) # 2周3天 = 17天
# 查看timedelta的内部表示
print(td1.days) # 7
print(td1.seconds) # 0
print(td1.total_seconds()) # 604800.0(7*24*3600)
3.2 日期时间加减
from datetime import datetime, timedelta
now = datetime(2026, 5, 5, 14, 30)
# 加减时间
future = now + timedelta(days=10) # 10天后
past = now - timedelta(weeks=2) # 2周前
next_hour = now + timedelta(hours=1) # 1小时后
half_day_later = now + timedelta(hours=12) # 12小时后
print(future) # 2026-05-15 14:30:00
print(past) # 2026-04-21 14:30:00
# 连续运算
result = now + timedelta(days=30) - timedelta(hours=5)
print(result) # 2026-06-04 09:30:00
# 计算两个日期之间的差值,结果也是timedelta
d1 = datetime(2026, 5, 5)
d2 = datetime(2026, 1, 1)
delta = d1 - d2
print(delta.days) # 124
print(delta.total_seconds()) # 10713600.0
3.3 日期时间比较
from datetime import datetime, date
d1 = date(2026, 5, 5)
d2 = date(2026, 6, 1)
print(d1 < d2) # True
print(d1 == d2) # False
print(d1 <= d2) # True
# 判断日期范围
target = date(2026, 5, 15)
start = date(2026, 5, 1)
end = date(2026, 5, 31)
if start <= target <= end:
print("Target is within May 2026")
# 查找最接近的日期
dates = [date(2026, 5, 10), date(2026, 5, 20), date(2026, 6, 1)]
target = date(2026, 5, 15)
closest = min(dates, key=lambda x: abs((x - target).days))
print(closest) # 2026-05-10(最接近5月15日)
最佳实践:比较操作会自动处理秒、微秒级别的精度。在比较时,确保两个对象要么都是naive,要么都是aware(且使用相同时区),否则会引发TypeError。
四、格式化与解析
4.1 strftime:日期时间转字符串
from datetime import datetime
dt = datetime(2026, 5, 5, 14, 30, 45)
# 常用格式
print(dt.strftime("%Y-%m-%d")) # 2026-05-05
print(dt.strftime("%Y-%m-%d %H:%M:%S")) # 2026-05-05 14:30:45
print(dt.strftime("%Y年%m月%d日")) # 2026年05月05日
print(dt.strftime("%A, %B %d, %Y")) # Tuesday, May 05, 2026
print(dt.strftime("%I:%M %p")) # 02:30 PM
# 中文环境星期、月份
print(dt.strftime("%A")) # Tuesday
print(dt.strftime("%a")) # Tue
print(dt.strftime("%B")) # May
print(dt.strftime("%b")) # May
# 周数和年中的第几天
print(dt.strftime("%W")) # 19(今年第19周,周一为一周开始)
print(dt.strftime("%j")) # 125(今年第125天)
| 指令 | 含义 | 示例 |
%Y | 4位年份 | 2026 |
%y | 2位年份 | 26 |
%m | 2位月份(01-12) | 05 |
%d | 2位日期(01-31) | 05 |
%H | 24小时制(00-23) | 14 |
%I | 12小时制(01-12) | 02 |
%M | 分钟(00-59) | 30 |
%S | 秒(00-59) | 45 |
%p | AM/PM | PM |
%A | 完整星期名称 | Tuesday |
%a | 缩写星期名称 | Tue |
%B | 完整月份名称 | May |
%b | 缩写月份名称 | May |
%j | 年中的第几天(001-366) | 125 |
%W | 年中的第几周(周一为第一天) | 19 |
%w | 周几的数字表示(0=周日,6=周六) | 2 |
%z | UTC偏移(±HHMM) | +0800 |
%Z | 时区名称 | CST |
%% | 百分号字面量 | % |
4.2 strptime:字符串解析为日期时间
from datetime import datetime
# 标准格式解析
dt1 = datetime.strptime("2026-05-05 14:30:00", "%Y-%m-%d %H:%M:%S")
print(dt1) # 2026-05-05 14:30:00
# 中文格式解析
dt2 = datetime.strptime("2026年05月05日 14:30:45", "%Y年%m月%d日 %H:%M:%S")
print(dt2)
# 12小时制解析
dt3 = datetime.strptime("05/05/2026 02:30 PM", "%m/%d/%Y %I:%M %p")
print(dt3) # 2026-05-05 14:30:00
# 带时区偏移的解析
dt4 = datetime.strptime("2026-05-05T14:30:00+0800", "%Y-%m-%dT%H:%M:%S%z")
print(dt4) # 2026-05-05 14:30:00+08:00
# 容错解析常见日期格式(自定义函数)
def parse_date(date_str):
formats = [
"%Y-%m-%d",
"%Y/%m/%d",
"%m/%d/%Y",
"%Y年%m月%d日",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%S",
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期字符串: {date_str}")
print(parse_date("2026/05/05")) # 2026-05-05 00:00:00
print(parse_date("2026年05月05日")) # 2026-05-05 00:00:00
注意:strptime在格式不匹配时会抛出ValueError。对于未知格式的字符串,建议先尝试多种可能的格式,或者使用dateutil.parser进行智能解析。
五、时区处理
5.1 使用内置timezone
Python 3.2+ 内置的timezone类只能表示固定偏移的时区,不支持夏令时(DST)自动切换。适合简单的UTC偏移场景。
from datetime import datetime, timezone, timedelta
# 定义时区
utc = timezone.utc
cst = timezone(timedelta(hours=8)) # 中国标准时间 UTC+8
est = timezone(timedelta(hours=-5)) # 美国东部时间 UTC-5
# 为naive datetime添加时区信息
dt_naive = datetime(2026, 5, 5, 14, 30)
dt_utc = dt_naive.replace(tzinfo=utc)
dt_cst = dt_naive.replace(tzinfo=cst)
print(dt_utc) # 2026-05-05 14:30:00+00:00
print(dt_cst) # 2026-05-05 14:30:00+08:00
# 时区转换:从CST转到UTC
dt_cst = datetime(2026, 5, 5, 14, 30, tzinfo=cst)
dt_utc_converted = dt_cst.astimezone(utc)
print(dt_utc_converted) # 2026-05-05 06:30:00+00:00
# 转换为其他时区
dt_est = dt_cst.astimezone(est)
print(dt_est) # 2026-05-05 01:30:00-05:00
5.2 使用zoneinfo(Python 3.9+)
zoneinfo是Python 3.9引入的正式时区库,使用IANA时区数据库,支持DST自动切换,是处理时区的推荐方案。
from datetime import datetime
from zoneinfo import ZoneInfo # Python 3.9+
# 使用时区名称创建aware datetime
shanghai = ZoneInfo("Asia/Shanghai")
nyc = ZoneInfo("America/New_York")
tokyo = ZoneInfo("Asia/Tokyo")
london = ZoneInfo("Europe/London")
dt_sh = datetime(2026, 5, 5, 14, 30, tzinfo=shanghai)
print(dt_sh) # 2026-05-05 14:30:00+08:00
# 不同时区之间转换
dt_ny = dt_sh.astimezone(nyc)
dt_tokyo = dt_sh.astimezone(tokyo)
print(dt_ny) # 2026-05-05 02:30:00-04:00
print(dt_tokyo) # 2026-05-05 15:30:00+09:00
# 获取当前时间在指定时区
now_sh = datetime.now(shanghai)
now_ny = datetime.now(nyc)
now_utc = datetime.now(ZoneInfo("UTC"))
print(now_sh) # 2026-05-05 22:48:12.xxxxxx+08:00
print(now_ny) # 2026-05-05 10:48:12.xxxxxx-04:00
# 列出所有可用时区
import zoneinfo
zones = zoneinfo.available_timezones()
print(len(zones)) # 约600个时区名称
print("Asia/Shanghai" in zones) # True
5.3 使用pytz(Python 3.9之前的方案)
pytz是第三方库,在zoneinfo出现之前被广泛使用。需要注意的是,pytz的使用方式与标准库有所不同,不能直接用tzinfo参数创建datetime对象。
import pytz
from datetime import datetime
# pytz的正确使用方式
tz_sh = pytz.timezone("Asia/Shanghai")
tz_ny = pytz.timezone("America/New_York")
# 正确:localize方法
dt_naive = datetime(2026, 5, 5, 14, 30)
dt_sh = tz_sh.localize(dt_naive)
print(dt_sh) # 2026-05-05 14:30:00+08:00
# 错误:直接使用tzinfo参数(pytz会出错!)
# dt_wrong = datetime(2026, 5, 5, 14, 30, tzinfo=tz_sh) # 不推荐
# 时区转换
dt_ny = dt_sh.astimezone(tz_ny)
print(dt_ny) # 2026-05-05 02:30:00-04:00
# 处理夏令时
# pytz的normalize方法自动处理DST
dt_us = pytz.timezone("US/Eastern").localize(
datetime(2026, 3, 8, 2, 30)
)
print(dt_us) # 2026-03-08 03:30:00-04:00(DST自动调整)
版本选择建议:Python 3.9+ 用户优先使用zoneinfo(标准库,无需安装);Python 3.8及以下版本需要使用pytz;简单的固定偏移场景使用内置timezone即可。
六、dateutil库高级用法
6.1 relativedelta:更灵活的时间运算
relativedelta弥补了timedelta不支持按"月"或"年"加减的缺陷,还支持按工作日、周几等语义化操作。
from datetime import datetime
from dateutil.relativedelta import relativedelta
dt = datetime(2026, 5, 5)
# 按月加减(timedelta做不到)
next_month = dt + relativedelta(months=1)
print(next_month) # 2026-06-05
last_year = dt - relativedelta(years=1)
print(last_year) # 2025-05-05
# 月末处理(自动处理不同月份天数差异)
dt_jan31 = datetime(2026, 1, 31)
dt_feb = dt_jan31 + relativedelta(months=1)
print(dt_feb) # 2026-02-28(自动调整为2月最后一天,而非越界)
# 月末修正策略
dt_feb_last = dt_jan31 + relativedelta(months=1, day=31)
print(dt_feb_last) # 2026-02-28
# 工作日计算
# 下周一
next_monday = dt + relativedelta(weekday=0)
print(next_monday) # 2026-05-11(如果5月5日是周二)
# 本月最后一个周五
last_friday = dt + relativedelta(day=31, weekday=4(-1))
print(last_friday)
# 计算两个日期之间的差值(年、月、日)
d1 = datetime(2020, 3, 15)
d2 = datetime(2026, 5, 5)
rd = relativedelta(d2, d1)
print(f"{rd.years}年{rd.months}月{rd.days}日")
# 6年1月20日
6.2 dateutil.parser:智能字符串解析
from dateutil import parser
# 自动识别各种常见日期格式
print(parser.parse("2026-05-05"))
# 2026-05-05 00:00:00
print(parser.parse("05/05/2026"))
# 2026-05-05 00:00:00
print(parser.parse("May 5, 2026 2:30 PM"))
# 2026-05-05 14:30:00
print(parser.parse("2026-05-05T14:30:00+08:00"))
# 2026-05-05 14:30:00+08:00
print(parser.parse("5th May 2026"))
# 2026-05-05 00:00:00
# 相对日期解析(英文)
print(parser.parse("today")) # 2026-05-05
print(parser.parse("yesterday")) # 2026-05-04
print(parser.parse("next Monday")) # 最近的下周一
# 模糊解析(忽略无关文字)
print(parser.parse("The meeting is on May 5, 2026 at 2pm", fuzzy=True))
# 2026-05-05 14:00:00
6.3 rrule:重复规则与日期生成
from datetime import datetime
from dateutil.rrule import rrule, DAILY, WEEKLY, MONTHLY, MO, TU, WE, TH, FR
# 每日重复,生成10天
start = datetime(2026, 5, 1)
daily = list(rrule(DAILY, count=5, dtstart=start))
for d in daily:
print(d.strftime("%Y-%m-%d %A"))
# 2026-05-01 Friday
# 2026-05-02 Saturday
# 2026-05-03 Sunday
# 2026-05-04 Monday
# 2026-05-05 Tuesday
# 每周一、三、五,持续4周
weekly = list(rrule(WEEKLY, byweekday=(MO, WE, FR), count=12, dtstart=start))
for d in weekly:
print(d.strftime("%Y-%m-%d %A"))
# 每月第一个周一
first_monday = rrule(MONTHLY, byweekday=MO(1), count=6, dtstart=start)
for d in first_monday:
print(d.strftime("%Y-%m-%d %A"))
# 每月最后一个工作日
last_business = rrule(MONTHLY, byweekday=(MO, TU, WE, TH, FR)(-1), count=6, dtstart=start)
for d in last_business:
print(d.strftime("%Y-%m-%d %A"))
七、时间戳转换
7.1 时间戳与datetime互转
时间戳(Unix timestamp)是指从1970年1月1日UTC开始的秒数(忽略闰秒),是跨语言通用的时间表示方式。
from datetime import datetime, timezone
import time
# datetime转时间戳
dt = datetime(2026, 5, 5, 14, 30, tzinfo=timezone.utc)
ts = dt.timestamp()
print(ts) # 1777552200.0
# 时间戳转datetime(UTC)
dt_from_ts = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt_from_ts) # 2026-05-05 14:30:00+00:00
# 时间戳转datetime(本地时区)
# 不指定tz时,使用系统本地时区
dt_local = datetime.fromtimestamp(ts)
print(dt_local) # 2026-05-05 22:30:00(东八区)
# 时间戳转UTC naive时间(已废弃,不推荐)
# dt_utcnaive = datetime.utcfromtimestamp(ts) # Python 3.12+ 已废弃
# 使用time模块获取时间戳
current_ts = time.time()
print(current_ts) # 当前时间戳
# struct_time转时间戳
t = time.mktime(time.localtime())
print(t)
# 毫秒时间戳处理
millis = int(dt.timestamp() * 1000)
print(millis) # 1777552200000
millis_to_dt = datetime.fromtimestamp(millis / 1000.0, tz=timezone.utc)
print(millis_to_dt) # 恢复为原时间
注意事项:datetime.fromtimestamp()返回的是本地时间的naive对象(如果不传tz参数),而datetime.utcfromtimestamp()在Python 3.12+已被标记为废弃。推荐始终传递tz=timezone.utc以获得明确的aware对象。
7.2 常见时间戳应用场景
from datetime import datetime, timezone
import time
# 场景1:API响应中的时间戳解析
api_timestamp = "1714896000"
dt = datetime.fromtimestamp(int(api_timestamp), tz=timezone.utc)
print(dt.strftime("%Y-%m-%d %H:%M:%S UTC"))
# 场景2:计算当前时间戳(毫秒)用于日志/数据库
def current_millis():
return int(time.time() * 1000)
print(current_millis())
# 场景3:时间戳与人类可读格式互转工具
def ts_to_readable(ts, tz=timezone.utc):
return datetime.fromtimestamp(ts, tz=tz).strftime("%Y-%m-%d %H:%M:%S %Z")
def readable_to_ts(date_str, fmt="%Y-%m-%d %H:%M:%S", tz=timezone.utc):
dt = datetime.strptime(date_str, fmt).replace(tzinfo=tz)
return dt.timestamp()
print(ts_to_readable(1777552200))
# 2026-05-05 14:30:00 UTC
print(readable_to_ts("2026-05-05 14:30:00"))
# 1777552200.0
八、工作日计算实用技巧
8.1 自定义工作日历
from datetime import date, timedelta
from dateutil.rrule import rrule, DAILY, MO, TU, WE, TH, FR
# 计算两个日期之间的工作日数量
def count_business_days(start, end):
return len(list(rrule(DAILY, dtstart=start, until=end,
byweekday=(MO, TU, WE, TH, FR))))
start = date(2026, 5, 1)
end = date(2026, 5, 31)
print(count_business_days(start, end)) # 21个工作日(假设5月1日为周五)
# 计算从今天起N个工作日后的日期
def add_business_days(start, n):
# 简单版本:跳过周末
current = start
while n > 0:
current += timedelta(days=1)
if current.weekday() < 5: # 周一到周五
n -= 1
return current
print(add_business_days(date(2026, 5, 1), 10))
# 7个工作日后的日期
# 使用rrule实现更灵活的版本(跳过节假日)
def add_business_days_rrule(start, n, holidays=[]):
biz_days = rrule(DAILY, byweekday=(MO, TU, WE, TH, FR),
dtstart=start + timedelta(days=1))
count = 0
for d in biz_days:
if d.date() not in holidays:
count += 1
if count == n:
return d.date()
return None
holidays = [date(2026, 5, 4)] # 假设5月4日是法定假日
result = add_business_days_rrule(date(2026, 5, 1), 3, holidays)
print(result)
8.2 日期范围生成
from datetime import date, timedelta
# 生成某一周的所有日期
def week_dates(year, week_number):
# 使用ISO周数计算
jan4 = date(year, 1, 4) # 一定在第一周的日期
start_of_week1 = jan4 - timedelta(days=jan4.isoweekday() - 1)
start = start_of_week1 + timedelta(weeks=week_number - 1)
return [start + timedelta(days=i) for i in range(7)]
print(week_dates(2026, 19))
# [2026-05-04, 2026-05-05, 2026-05-06, 2026-05-07, 2026-05-08, 2026-05-09, 2026-05-10]
# 生成指定月份的所有日期
def month_dates(year, month):
import calendar
_, days = calendar.monthrange(year, month)
return [date(year, month, d) for d in range(1, days + 1)]
print(month_dates(2026, 5))
# [2026-05-01, ..., 2026-05-31] 共31天
# 日期范围生成器(可指定步长)
def date_range(start, end, step=timedelta(days=1)):
current = start
while current <= end:
yield current
current += step
# 遍历5月每天
for d in date_range(date(2026, 5, 1), date(2026, 5, 10)):
print(d.strftime("%Y-%m-%d %A"))
实用技巧:对于需要排除国定假日的复杂工作日计算,建议维护一个节假日列表(可通过开源项目如chinese-calendar获取中国法定假日),然后结合上述rrule方法实现。
九、实际应用场景
9.1 日志时间戳处理
from datetime import datetime, timezone, timedelta
import re
# 日志解析:提取ISO格式时间戳并按小时聚合
log_lines = [
"[2026-05-05T08:15:30+08:00] INFO 用户登录成功",
"[2026-05-05T08:45:12+08:00] WARNING 连接超时",
"[2026-05-05T09:01:05+08:00] ERROR 数据库连接失败",
"[2026-05-05T09:30:00+08:00] INFO 服务重启完成",
]
def parse_log_timestamp(line):
match = re.search(r"\[(.*?)\]", line)
if match:
return datetime.fromisoformat(match.group(1))
# 按小时分组统计
hourly_stats = {}
for line in log_lines:
dt = parse_log_timestamp(line)
if dt:
hour_key = dt.strftime("%Y-%m-%d %H:00")
hourly_stats[hour_key] = hourly_stats.get(hour_key, 0) + 1
print(hourly_stats)
# {'2026-05-05 08:00': 2, '2026-05-05 09:00': 2}
9.2 过期时间判断
from datetime import datetime, timedelta, timezone
# Token过期判断
def is_token_expired(expires_at_str, format="%Y-%m-%d %H:%M:%S"):
expires = datetime.strptime(expires_at_str, format)
expires = expires.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
return now > expires
print(is_token_expired("2026-05-01 00:00:00")) # True
print(is_token_expired("2026-12-31 00:00:00")) # False
# Session有效期判断(15分钟过期)
def is_session_valid(last_activity, max_minutes=15):
now = datetime.now(timezone.utc)
elapsed = now - last_activity
return elapsed < timedelta(minutes=max_minutes)
last_active = datetime.now(timezone.utc) - timedelta(minutes=10)
print(is_session_valid(last_active)) # True(10分钟<15分钟)
9.3 跨时区会议时间
from datetime import datetime
from zoneinfo import ZoneInfo
# 上海团队和纽约团队约定会议时间
# 上海时间14:00开会,纽约同事需要知道本地时间
shanghai_time = datetime(2026, 5, 5, 14, 0, 0,
tzinfo=ZoneInfo("Asia/Shanghai"))
nyc_time = shanghai_time.astimezone(ZoneInfo("America/New_York"))
london_time = shanghai_time.astimezone(ZoneInfo("Europe/London"))
tokyo_time = shanghai_time.astimezone(ZoneInfo("Asia/Tokyo"))
print(f"上海: {shanghai_time.strftime('%Y-%m-%d %H:%M %Z')}")
print(f"纽约: {nyc_time.strftime('%Y-%m-%d %H:%M %Z')}")
print(f"伦敦: {london_time.strftime('%Y-%m-%d %H:%M %Z')}")
print(f"东京: {tokyo_time.strftime('%Y-%m-%d %H:%M %Z')}")
# 上海: 2026-05-05 14:00 CST
# 纽约: 2026-05-05 02:00 EDT
# 伦敦: 2026-05-05 07:00 BST
# 东京: 2026-05-05 15:00 JST
# 检查是否在每个人的工作时间(9:00-18:00)
def is_work_hours(dt):
return 9 <= dt.hour < 18
print(f"上海工作时间: {is_work_hours(shanghai_time)}") # True
print(f"纽约工作时间: {is_work_hours(nyc_time)}") # False(凌晨2点)
# 结论:14:00 CST对纽约团队不友好,建议调整
十、性能优化与最佳实践
10.1 批量处理优化
from datetime import datetime
import time
# 避免在循环中重复创建时区对象
# ❌ 不推荐:每次循环都创建新对象
def bad_way(timestamps):
from zoneinfo import ZoneInfo
results = []
for ts in timestamps:
dt = datetime.fromtimestamp(ts, tz=ZoneInfo("Asia/Shanghai"))
results.append(dt)
return results
# ✅ 推荐:复用时区对象
def good_way(timestamps):
from zoneinfo import ZoneInfo
tz = ZoneInfo("Asia/Shanghai") # 只创建一次
results = []
for ts in timestamps:
dt = datetime.fromtimestamp(ts, tz=tz)
results.append(dt)
return results
# 批量strptime优化:使用列表推导
date_strings = ["2026-05-01", "2026-05-02", "2026-05-03"]
dates = [datetime.strptime(s, "%Y-%m-%d") for s in date_strings]
10.2 常见误区与陷阱
错误做法
- 将naive和aware datetime直接比较
- 使用
datetime.utcnow()(返回naive对象)
- 在pytz中直接使用
tzinfo参数
- 用
timedelta(months=1)(不支持)
- 假设
fromtimestamp()返回UTC时间
正确做法
- 统一使用aware datetime并转换为相同时区再比较
- 使用
datetime.now(timezone.utc)
- 使用pytz的
localize()方法
- 使用
relativedelta(months=1)
- 始终指定tz参数
Python 3.12+ 废弃警告:datetime.utcfromtimestamp()、datetime.utcnow()已被标记为废弃,将在未来版本中移除。所有新代码应使用带tz=timezone.utc的fromtimestamp()和now()替代。
十一、核心要点总结
- 五大核心类:date、time、datetime、timedelta、timezone构成Python时间处理的基础
- naive vs aware:生产环境始终使用aware datetime(带时区信息),避免时区混淆
- timedelta局限:不支持按月/年运算,需要借助
dateutil.relativedelta
- 时区方案:Python 3.9+ 用
zoneinfo,旧版本用pytz,固定偏移用内置timezone
- 格式化:
strftime用于输出,strptime用于解析,dateutil.parser用于智能解析
- 日期生成:
dateutil.rrule是生成重复日期的最强工具
- 时间戳:始终使用aware对象的
timestamp()和fromtimestamp(ts, tz=...)
- 工作日计算:组合使用
rrule、weekday()、isoweekday()和节假日列表
- 废弃API:避免使用
utcnow()和utcfromtimestamp()
- 性能优化:时区对象和格式字符串尽量复用,避免在循环中重复创建
推荐阅读:Python官方文档中关于datetime模块的PEP 615(zoneinfo的引入)和PEP 495(处理模糊时间)是深入了解Python时间处理的权威资料。