文章目录
-
- 第一部分:为什么要学时间处理?
-
- [1.1 时间在编程中无处不在](#1.1 时间在编程中无处不在)
- [1.2 Python 时间体系总览](#1.2 Python 时间体系总览)
- [第二部分:time 模块](#第二部分:time 模块)
-
- [2.1 Unix 时间戳是什么?](#2.1 Unix 时间戳是什么?)
- [2.2 time.sleep()------让程序暂停](#2.2 time.sleep()——让程序暂停)
- [2.3 计时------测量代码运行时间](#2.3 计时——测量代码运行时间)
- [2.4 time.struct_time------结构化时间](#2.4 time.struct_time——结构化时间)
- [2.5 time 模块常用函数速查](#2.5 time 模块常用函数速查)
- [第三部分:datetime 模块------核心日期时间类](#第三部分:datetime 模块——核心日期时间类)
-
- [3.1 date 类------只有日期](#3.1 date 类——只有日期)
- [3.2 time 类------只有时间](#3.2 time 类——只有时间)
- [3.3 datetime 类------日期 + 时间(最常用!)](#3.3 datetime 类——日期 + 时间(最常用!))
- [第四部分:格式化与解析(strftime / strptime)](#第四部分:格式化与解析(strftime / strptime))
-
- [4.1 strftime------datetime 转字符串](#4.1 strftime——datetime 转字符串)
- [4.2 strptime------字符串转 datetime](#4.2 strptime——字符串转 datetime)
- [4.3 strftime 格式代码速查表](#4.3 strftime 格式代码速查表)
- 第五部分:timedelta------时间差计算
-
- [5.1 timedelta 是什么?](#5.1 timedelta 是什么?)
- [5.2 datetime 加减 timedelta](#5.2 datetime 加减 timedelta)
- [5.3 实用:时间差的人性化显示](#5.3 实用:时间差的人性化显示)
- 第六部分:replace()------替换时间分量
- 第七部分:时区处理
-
- [7.1 时区的基本概念](#7.1 时区的基本概念)
- [7.2 Python 内置时区(timezone)](#7.2 Python 内置时区(timezone))
- [7.3 使用 pytz(处理夏令时等复杂时区,需安装)](#7.3 使用 pytz(处理夏令时等复杂时区,需安装))
- [7.4 naive 和 aware 的互转](#7.4 naive 和 aware 的互转)
- [第八部分:calendar 模块](#第八部分:calendar 模块)
-
- [8.1 常用函数](#8.1 常用函数)
- [8.2 实用:获取某月所有日期](#8.2 实用:获取某月所有日期)
- 第九部分:综合实战案例
-
- [9.1 案例一:代码性能计时器(上下文管理器)](#9.1 案例一:代码性能计时器(上下文管理器))
- [9.2 案例二:日志时间戳系统](#9.2 案例二:日志时间戳系统)
- [9.3 案例三:日期区间统计](#9.3 案例三:日期区间统计)
- [9.4 案例四:业务时间校验](#9.4 案例四:业务时间校验)
- 第十部分:常见陷阱与注意事项
-
- [10.1 陷阱1:datetime 是不可变对象](#10.1 陷阱1:datetime 是不可变对象)
- [10.2 陷阱2:naive 和 aware 不能直接比较](#10.2 陷阱2:naive 和 aware 不能直接比较)
- [10.3 陷阱3:strptime 格式必须完全匹配](#10.3 陷阱3:strptime 格式必须完全匹配)
- [10.4 陷阱4:不同月份天数不同,直接加减月份要小心](#10.4 陷阱4:不同月份天数不同,直接加减月份要小心)
- [10.5 陷阱5:time.sleep 精度问题](#10.5 陷阱5:time.sleep 精度问题)
- 第十一部分:完整速查表
-
- [11.1 time 模块](#11.1 time 模块)
- [11.2 datetime 模块](#11.2 datetime 模块)
- 总结
https://www.quanzhankaige.com/python27/
本文档面向零基础新手,目标是让你真正理解:
time模块:时间戳、计时、程序暂停datetime模块:日期、时间、日期时间的创建与运算- 字符串与日期的相互转换(
strftime/strptime) - 时间差(
timedelta)的计算 - 时区(timezone)的处理
calendar模块:日历相关操作- 实际项目中的时间处理(日志、统计、调度等)
- 常见陷阱与注意事项
配有大量可运行示例,全部从最基础讲起。
第一部分:为什么要学时间处理?
1.1 时间在编程中无处不在
日志系统:每条日志都要记录发生时间
性能测量:代码运行花了多少毫秒?
数据分析:按天/周/月统计销售额
定时任务:每天凌晨2点执行备份
业务逻辑:优惠券是否已过期?用户是否满18岁?
文件操作:按日期命名备份文件
1.2 Python 时间体系总览
Python 时间相关模块:
time 模块(底层)
└─ 主要处理:Unix 时间戳、程序暂停、CPU计时
└─ 代表:time.time()、time.sleep()、time.perf_counter()
datetime 模块(主力)
├─ date 类:只有日期(年月日)
├─ time 类:只有时间(时分秒微秒)
├─ datetime 类:日期 + 时间(最常用!)
└─ timedelta 类:时间差(两个时间点之间的距离)
└─ timezone 类:时区
calendar 模块(辅助)
└─ 主要处理:判断闰年、获取某月天数、生成日历
第二部分:time 模块
2.1 Unix 时间戳是什么?
Unix 时间戳(Unix Timestamp):
从 1970年1月1日 00:00:00 UTC(格林威治时间)起,
到某一时刻经过的 秒数(浮点数)。
举例:
2024-01-01 00:00:00 UTC = 时间戳 1704067200
2024-06-15 12:30:45 UTC = 时间戳 1718451045.0
为什么选1970年1月1日?
Unix 操作系统诞生于1969年,1970年是个整数,方便计算。
python
import time
# 获取当前时间戳(从1970年1月1日到现在的秒数)
ts = time.time()
print(f"当前时间戳:{ts}") # 如:1718451045.123456
print(f"整数部分: {int(ts)}") # 如:1718451045
print(f"类型: {type(ts)}") # <class 'float'>
# 时间戳是浮点数:小数部分是毫秒/微秒精度
print(f"毫秒精度:{ts * 1000:.3f}") # 如:1718451045123.456
2.2 time.sleep()------让程序暂停
python
import time
# 暂停指定秒数(可以是浮点数)
print("开始等待...")
time.sleep(1) # 暂停 1 秒
time.sleep(0.5) # 暂停 0.5 秒(500毫秒)
time.sleep(0.001) # 暂停 0.001 秒(1毫秒)
print("等待结束!")
# 实际应用1:进度条模拟
print("\n正在加载:", end='', flush=True)
for i in range(10):
time.sleep(0.2)
print('█', end='', flush=True)
print(" 完成!")
# 实际应用2:限速(避免频繁请求 API 被封)
urls = ['url1', 'url2', 'url3', 'url4', 'url5']
for i, url in enumerate(urls):
print(f" 请求 {url}...")
# do_request(url) # 实际请求
time.sleep(0.5) # 每次请求间隔0.5秒,避免触发限速
print(f" 完成({i+1}/{len(urls)})")
# 实际应用3:倒计时
def countdown(seconds):
print(f"倒计时开始:{seconds}秒")
for i in range(seconds, 0, -1):
print(f" {i}...", end='\r', flush=True)
time.sleep(1)
print(" 时间到! ")
countdown(5)
2.3 计时------测量代码运行时间
python
import time
# ===== 方式1:time.time()(精度够用,最简单)=====
start = time.time()
# 模拟一些耗时操作
total = sum(range(10_000_000))
elapsed = time.time() - start
print(f"求和结果:{total}")
print(f"耗时(time.time):{elapsed:.4f} 秒")
# ===== 方式2:time.perf_counter()(高精度,推荐计时用)=====
# perf_counter 的精度比 time() 更高,适合性能测量
start = time.perf_counter()
for _ in range(100000):
_ = 2 ** 100
elapsed = time.perf_counter() - start
print(f"耗时(perf_counter):{elapsed * 1000:.3f} 毫秒")
# ===== 方式3:time.process_time()(只计 CPU 时间,不含 sleep)=====
start = time.process_time()
time.sleep(1) # 睡1秒
for _ in range(100000):
pass
cpu_time = time.process_time() - start
print(f"CPU 时间:{cpu_time * 1000:.3f} 毫秒(不含 sleep 的1秒)")
# ===== 方式4:封装成装饰器 =====
def timer(func):
"""计时装饰器:自动测量函数运行时间"""
import functools
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[计时] {func.__name__} 耗时:{elapsed * 1000:.2f} 毫秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.3)
return "完成"
@timer
def fast_function():
return sum(range(1000000))
slow_function()
fast_function()
2.4 time.struct_time------结构化时间
python
import time
# localtime():将时间戳转换为本地时间的结构
ts = time.time()
t = time.localtime(ts)
print(f"struct_time:{t}")
print(f"年:{t.tm_year}")
print(f"月:{t.tm_mon}") # 1~12
print(f"日:{t.tm_mday}") # 1~31
print(f"时:{t.tm_hour}") # 0~23
print(f"分:{t.tm_min}") # 0~59
print(f"秒:{t.tm_sec}") # 0~61(60/61是闰秒)
print(f"星期:{t.tm_wday}") # 0=周一,6=周日
print(f"年内第几天:{t.tm_yday}") # 1~366
print(f"夏令时:{t.tm_isdst}") # -1/0/1
# gmtime():转换为 UTC 时间(格林威治时间)
t_utc = time.gmtime(ts)
print(f"\nUTC 时间:{t_utc.tm_hour}:{t_utc.tm_min}")
# 北京时间 = UTC + 8
print(f"北京时间:{(t_utc.tm_hour + 8) % 24}:{t_utc.tm_min}")
# mktime():struct_time → 时间戳(localtime 的反操作)
t_struct = time.localtime()
ts_back = time.mktime(t_struct)
print(f"\n还原时间戳:{ts_back:.0f}")
# strftime():将 struct_time 格式化为字符串
fmt_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
print(f"格式化时间:{fmt_time}")
# strptime():将字符串解析为 struct_time
parsed = time.strptime('2024-06-15 12:30:45', '%Y-%m-%d %H:%M:%S')
print(f"解析结果:{parsed.tm_year}年{parsed.tm_mon}月{parsed.tm_mday}日")
2.5 time 模块常用函数速查
python
import time
# 当前时间戳(秒,浮点数)
print(time.time())
# 当前时间戳(纳秒,整数,Python 3.7+)
print(time.time_ns())
# 当前本地时间(struct_time)
print(time.localtime())
# 当前 UTC 时间(struct_time)
print(time.gmtime())
# 结构化时间 → 时间戳
print(time.mktime(time.localtime()))
# 格式化当前时间
print(time.strftime('%Y/%m/%d'))
# 高精度计时(推荐)
print(time.perf_counter())
# 只计 CPU 时间
print(time.process_time())
# 程序暂停 N 秒
time.sleep(0.001)
# 本地时区名称(系统设置)
print(time.tzname) # ('CST', 'CDT') 或 ('中国标准时间', ...)
print(time.timezone) # 与 UTC 的差值(秒),东八区是 -28800
第三部分:datetime 模块------核心日期时间类
3.1 date 类------只有日期
python
from datetime import date
# ===== 创建 date 对象 =====
# 方式1:直接指定年月日
d1 = date(2024, 6, 15)
print(d1) # 2024-06-15
print(type(d1)) # <class 'datetime.date'>
# 方式2:今天
today = date.today()
print(f"今天:{today}")
# 方式3:从时间戳创建
import time
d2 = date.fromtimestamp(time.time())
print(f"从时间戳:{d2}")
# 方式4:从字符串解析(Python 3.7+)
d3 = date.fromisoformat('2024-06-15')
print(f"从ISO字符串:{d3}")
# ===== 访问属性 =====
d = date(2024, 6, 15)
print(f"年:{d.year}") # 2024
print(f"月:{d.month}") # 6
print(f"日:{d.day}") # 15
# ===== 常用方法 =====
print(f"ISO格式:{d.isoformat()}") # '2024-06-15'
print(f"星期几:{d.weekday()}") # 0=周一,5=周六,6=周日
print(f"星期几(ISO):{d.isoweekday()}") # 1=周一,7=周日
print(f"星期名:{d.strftime('%A')}") # Saturday
print(f"年/周/星期:{d.isocalendar()}") # (2024, 24, 6)---第24周,周六
# 最小值和最大值
print(f"最早的日期:{date.min}") # 0001-01-01
print(f"最晚的日期:{date.max}") # 9999-12-31
# ===== 日期比较 =====
d_a = date(2024, 1, 1)
d_b = date(2024, 12, 31)
print(d_a < d_b) # True
print(d_a == d_b) # False
# ===== 格式化输出 =====
d = date(2024, 6, 15)
print(d.strftime('%Y年%m月%d日')) # 2024年06月15日
print(d.strftime('%d/%m/%Y')) # 15/06/2024
print(d.strftime('%B %d, %Y')) # June 15, 2024
3.2 time 类------只有时间
注意:这里说的是 datetime.time 类,不是 time 模块!
python
from datetime import time # 导入的是 datetime 模块里的 time 类
# ===== 创建 time 对象 =====
t1 = time(14, 30, 45) # 14:30:45
t2 = time(9, 0) # 09:00:00(秒默认为0)
t3 = time(23, 59, 59, 999999) # 23:59:59.999999(最后一个是微秒)
t4 = time() # 00:00:00
print(t1) # 14:30:45
print(t2) # 09:00:00
print(t3) # 23:59:59.999999
print(t4) # 00:00:00
# ===== 访问属性 =====
t = time(14, 30, 45, 123456)
print(f"时:{t.hour}") # 14
print(f"分:{t.minute}") # 30
print(f"秒:{t.second}") # 45
print(f"微秒:{t.microsecond}")# 123456
# ===== 格式化 =====
print(t.strftime('%H:%M:%S')) # 14:30:45
print(t.strftime('%I:%M %p')) # 02:30 PM(12小时制)
print(t.isoformat()) # 14:30:45.123456
# ===== 比较 =====
t_a = time(9, 0)
t_b = time(17, 30)
print(t_a < t_b) # True(9点 早于 17:30)
# ===== 判断工作时间 =====
def is_work_hours(t):
work_start = time(9, 0)
work_end = time(18, 0)
return work_start <= t <= work_end
now_time = time(10, 30)
print(f"是否工作时间:{is_work_hours(now_time)}") # True
3.3 datetime 类------日期 + 时间(最常用!)
python
from datetime import datetime
# ===== 创建 datetime 对象 =====
# 方式1:直接指定(年 月 日 时 分 秒 微秒)
dt1 = datetime(2024, 6, 15, 14, 30, 45)
dt2 = datetime(2024, 6, 15, 14, 30, 45, 123456) # 含微秒
dt3 = datetime(2024, 1, 1) # 时分秒默认为 0
print(dt1) # 2024-06-15 14:30:45
print(dt2) # 2024-06-15 14:30:45.123456
# 方式2:当前本地时间
now = datetime.now()
print(f"现在:{now}")
# 方式3:当前 UTC 时间
utc_now = datetime.utcnow()
print(f"UTC时间:{utc_now}")
# 方式4:从时间戳创建
import time as time_module
dt4 = datetime.fromtimestamp(time_module.time())
print(f"从时间戳:{dt4}")
# 方式5:从字符串解析
dt5 = datetime.fromisoformat('2024-06-15T14:30:45')
print(f"从ISO字符串:{dt5}")
# 方式6:从 date 和 time 组合
from datetime import date, time
d = date(2024, 6, 15)
t = time(14, 30, 45)
dt6 = datetime.combine(d, t)
print(f"合并:{dt6}")
# ===== 访问属性 =====
dt = datetime(2024, 6, 15, 14, 30, 45, 123456)
print(f"年:{dt.year}") # 2024
print(f"月:{dt.month}") # 6
print(f"日:{dt.day}") # 15
print(f"时:{dt.hour}") # 14
print(f"分:{dt.minute}") # 30
print(f"秒:{dt.second}") # 45
print(f"微秒:{dt.microsecond}")# 123456
# ===== 提取日期和时间部分 =====
print(f"日期部分:{dt.date()}") # 2024-06-15(date 对象)
print(f"时间部分:{dt.time()}") # 14:30:45.123456(time 对象)
# ===== 转换为时间戳 =====
ts = dt.timestamp()
print(f"时间戳:{ts}")
# ===== 星期信息 =====
print(f"星期几:{dt.weekday()}") # 0=周一
print(f"星期几(ISO):{dt.isoweekday()}") # 1=周一
print(f"星期名缩写:{dt.strftime('%a')}") # Sat
print(f"星期名全称:{dt.strftime('%A')}") # Saturday
第四部分:格式化与解析(strftime / strptime)
4.1 strftime------datetime 转字符串
strftime(string format time):将时间格式化为字符串
python
from datetime import datetime
dt = datetime(2024, 6, 15, 14, 30, 45)
# ===== 常用格式代码 =====
print(dt.strftime('%Y')) # 2024(四位年份)
print(dt.strftime('%y')) # 24(两位年份)
print(dt.strftime('%m')) # 06(两位月份)
print(dt.strftime('%d')) # 15(两位日期)
print(dt.strftime('%H')) # 14(24小时制,两位)
print(dt.strftime('%I')) # 02(12小时制,两位)
print(dt.strftime('%M')) # 30(分钟,两位)
print(dt.strftime('%S')) # 45(秒,两位)
print(dt.strftime('%f')) # 000000(微秒,6位)
print(dt.strftime('%p')) # PM(AM/PM)
print(dt.strftime('%A')) # Saturday(星期全称)
print(dt.strftime('%a')) # Sat(星期缩写)
print(dt.strftime('%B')) # June(月份全称)
print(dt.strftime('%b')) # Jun(月份缩写)
print(dt.strftime('%j')) # 167(年内第几天,1-366)
print(dt.strftime('%W')) # 24(年内第几周,周一为第一天)
print(dt.strftime('%Z')) # 时区名(如 CST)
print(dt.strftime('%z')) # 时区偏移(如 +0800)
# ===== 常用格式组合 =====
print(dt.strftime('%Y-%m-%d')) # 2024-06-15
print(dt.strftime('%Y/%m/%d')) # 2024/06/15
print(dt.strftime('%Y年%m月%d日')) # 2024年06月15日
print(dt.strftime('%H:%M:%S')) # 14:30:45
print(dt.strftime('%I:%M %p')) # 02:30 PM
print(dt.strftime('%Y-%m-%d %H:%M:%S')) # 2024-06-15 14:30:45
print(dt.strftime('%Y%m%d_%H%M%S')) # 20240615_143045(文件名常用)
print(dt.strftime('%d %B %Y')) # 15 June 2024
print(dt.strftime('%A, %B %d, %Y')) # Saturday, June 15, 2024
# ===== 实际应用:生成带时间戳的文件名 =====
now = datetime.now()
log_filename = now.strftime('log_%Y%m%d_%H%M%S.txt')
backup_filename = now.strftime('backup_%Y-%m-%d.tar.gz')
report_filename = now.strftime('report_%Y年%m月%d日.xlsx')
print(log_filename) # log_20240615_143045.txt
print(backup_filename) # backup_2024-06-15.tar.gz
print(report_filename) # report_2024年06月15日.xlsx
4.2 strptime------字符串转 datetime
strptime(string parse time):将字符串解析为 datetime 对象
python
from datetime import datetime
# ===== 基础用法 =====
# 格式字符串必须和输入字符串完全对应!
s1 = '2024-06-15 14:30:45'
dt1 = datetime.strptime(s1, '%Y-%m-%d %H:%M:%S')
print(dt1) # 2024-06-15 14:30:45
print(type(dt1)) # <class 'datetime.datetime'>
s2 = '15/06/2024'
dt2 = datetime.strptime(s2, '%d/%m/%Y')
print(dt2) # 2024-06-15 00:00:00
s3 = '2024年06月15日'
dt3 = datetime.strptime(s3, '%Y年%m月%d日')
print(dt3) # 2024-06-15 00:00:00
s4 = 'June 15, 2024 2:30 PM'
dt4 = datetime.strptime(s4, '%B %d, %Y %I:%M %p')
print(dt4) # 2024-06-15 14:30:00
s5 = '20240615143045'
dt5 = datetime.strptime(s5, '%Y%m%d%H%M%S')
print(dt5) # 2024-06-15 14:30:45
# ===== 常见错误 =====
try:
# 格式不匹配会报 ValueError
datetime.strptime('2024-6-15', '%Y-%m-%d') # 月份没有前导零
except ValueError as e:
print(f"解析失败:{e}")
# 修复:用 %-m(Linux)或手动处理
# 或者用更灵活的 dateutil 库(需安装)
# ===== 实际应用:处理不同格式的日期字符串 =====
def flexible_parse_date(date_str):
"""
尝试多种格式解析日期字符串
"""
formats = [
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%Y/%m/%d %H:%M:%S',
'%Y/%m/%d',
'%d/%m/%Y',
'%m/%d/%Y',
'%Y%m%d',
'%Y年%m月%d日',
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期:{date_str!r}")
# 测试
test_dates = [
'2024-06-15',
'2024/06/15',
'15/06/2024',
'20240615',
'2024年06月15日',
'2024-06-15 14:30:45',
]
for s in test_dates:
dt = flexible_parse_date(s)
print(f"{s:30s} → {dt.strftime('%Y-%m-%d')}")
4.3 strftime 格式代码速查表
┌──────┬─────────────────────────────────────────────────────┐
│ 代码 │ 含义与示例 │
├──────┼─────────────────────────────────────────────────────┤
│ %Y │ 四位年份:2024 │
│ %y │ 两位年份:24 │
│ %m │ 两位月份(01-12):06 │
│ %d │ 两位日期(01-31):15 │
│ %H │ 24小时制小时(00-23):14 │
│ %I │ 12小时制小时(01-12):02 │
│ %M │ 两位分钟(00-59):30 │
│ %S │ 两位秒(00-59):45 │
│ %f │ 六位微秒(000000-999999):123456 │
│ %p │ AM / PM │
│ %A │ 星期全称:Saturday │
│ %a │ 星期缩写:Sat │
│ %B │ 月份全称:June │
│ %b │ 月份缩写:Jun │
│ %j │ 年内第几天(001-366):167 │
│ %W │ 年内第几周(00-53,周一为第一天) │
│ %U │ 年内第几周(00-53,周日为第一天) │
│ %Z │ 时区名:CST │
│ %z │ UTC偏移:+0800 │
│ %% │ 字面量的百分号 % │
└──────┴─────────────────────────────────────────────────────┘
第五部分:timedelta------时间差计算
5.1 timedelta 是什么?
timedelta(时间增量)代表两个时间点之间的"距离"
内部存储为:天数(days)+ 秒数(seconds)+ 微秒数(microseconds)
可以用来:
① 两个 datetime 相减,得到 timedelta(时间差)
② datetime 加减 timedelta,得到新的 datetime(时间偏移)
python
from datetime import timedelta
# ===== 创建 timedelta =====
td1 = timedelta(days=7) # 7天
td2 = timedelta(hours=3) # 3小时
td3 = timedelta(days=1, hours=2, minutes=30) # 1天2小时30分
td4 = timedelta(weeks=2) # 2周(等于14天)
td5 = timedelta(seconds=3661) # 3661秒
print(td1) # 7 days, 0:00:00
print(td2) # 3:00:00
print(td3) # 1 day, 2:30:00
print(td4) # 14 days, 0:00:00
print(td5) # 1:01:01(3661秒 = 1小时1分1秒)
# ===== timedelta 的属性 =====
td = timedelta(days=1, hours=2, minutes=30, seconds=15)
print(f"天数:{td.days}") # 1
print(f"秒数(不含天):{td.seconds}") # 9015(2*3600 + 30*60 + 15)
print(f"微秒:{td.microseconds}") # 0
print(f"总秒数:{td.total_seconds()}") # 95415.0(1*86400 + 9015)
# ===== timedelta 运算 =====
td_a = timedelta(days=3)
td_b = timedelta(days=1)
print(td_a + td_b) # 4 days, 0:00:00
print(td_a - td_b) # 2 days, 0:00:00
print(td_a * 2) # 6 days, 0:00:00
print(td_a / 3) # 1 day, 0:00:00
print(-td_a) # -3 days, 0:00:00(负的时间差)
print(abs(-td_a)) # 3 days, 0:00:00(取绝对值)
5.2 datetime 加减 timedelta
python
from datetime import datetime, timedelta
now = datetime(2024, 6, 15, 14, 30, 45)
print(f"当前时间:{now}")
# ===== 加法:在某个时间点上往后推 =====
tomorrow = now + timedelta(days=1)
next_week = now + timedelta(weeks=1)
after_2_hours = now + timedelta(hours=2)
after_90_min = now + timedelta(minutes=90)
next_year = now + timedelta(days=365)
print(f"明天: {tomorrow}") # 2024-06-16 14:30:45
print(f"下周: {next_week}") # 2024-06-22 14:30:45
print(f"2小时后: {after_2_hours}") # 2024-06-15 16:30:45
print(f"90分钟后:{after_90_min}") # 2024-06-15 16:00:45
print(f"明年今天:{next_year}") # 2025-06-15 14:30:45
# ===== 减法:在某个时间点上往前推 =====
yesterday = now - timedelta(days=1)
last_week = now - timedelta(weeks=1)
two_hours_ago = now - timedelta(hours=2)
print(f"昨天: {yesterday}") # 2024-06-14 14:30:45
print(f"上周: {last_week}") # 2024-06-08 14:30:45
print(f"2小时前: {two_hours_ago}") # 2024-06-15 12:30:45
# ===== 两个 datetime 相减,得到 timedelta =====
dt_start = datetime(2024, 1, 1, 9, 0, 0)
dt_end = datetime(2024, 6, 15, 17, 30, 0)
diff = dt_end - dt_start
print(f"\n时间差:{diff}") # 166 days, 8:30:00
print(f"相差天数:{diff.days}") # 166
print(f"相差总秒数:{diff.total_seconds():.0f}") # 14370600
print(f"相差小时数:{diff.total_seconds()/3600:.1f}") # 3991.7
# ===== 实际应用 =====
# 计算年龄
def calculate_age(birth_date: datetime) -> dict:
today = datetime.now()
diff = today - birth_date
total_days = diff.days
years = total_days // 365
months = (total_days % 365) // 30
days = (total_days % 365) % 30
return {'years': years, 'months': months, 'days': days,
'total_days': total_days}
birth = datetime(1995, 8, 23)
age = calculate_age(birth)
print(f"\n年龄:{age['years']}岁{age['months']}个月{age['days']}天")
print(f"活了:{age['total_days']} 天")
# 判断是否过期
def is_expired(expire_datetime: datetime) -> bool:
return datetime.now() > expire_datetime
coupon_expire = datetime(2024, 12, 31, 23, 59, 59)
print(f"\n优惠券已过期:{is_expired(coupon_expire)}")
# 离过期还有多少时间
remaining = coupon_expire - datetime.now()
if remaining.total_seconds() > 0:
days_left = remaining.days
hours_left = remaining.seconds // 3600
minutes_left = (remaining.seconds % 3600) // 60
print(f"还剩:{days_left}天{hours_left}小时{minutes_left}分钟")
5.3 实用:时间差的人性化显示
python
from datetime import datetime, timedelta
def humanize_timedelta(td: timedelta) -> str:
"""将时间差转换为人类可读的文字"""
total_seconds = int(td.total_seconds())
if total_seconds < 0:
return "刚刚"
elif total_seconds < 60:
return f"{total_seconds}秒前"
elif total_seconds < 3600:
return f"{total_seconds // 60}分钟前"
elif total_seconds < 86400:
hours = total_seconds // 3600
return f"{hours}小时前"
elif total_seconds < 86400 * 7:
days = total_seconds // 86400
return f"{days}天前"
elif total_seconds < 86400 * 30:
weeks = total_seconds // (86400 * 7)
return f"{weeks}周前"
elif total_seconds < 86400 * 365:
months = total_seconds // (86400 * 30)
return f"{months}个月前"
else:
years = total_seconds // (86400 * 365)
return f"{years}年前"
def time_ago(past_datetime: datetime) -> str:
"""显示某个时间距现在多久"""
diff = datetime.now() - past_datetime
return humanize_timedelta(diff)
# 测试
test_times = [
datetime.now() - timedelta(seconds=30),
datetime.now() - timedelta(minutes=5),
datetime.now() - timedelta(hours=3),
datetime.now() - timedelta(days=2),
datetime.now() - timedelta(weeks=2),
datetime.now() - timedelta(days=60),
datetime.now() - timedelta(days=400),
]
for t in test_times:
print(f"{t.strftime('%Y-%m-%d %H:%M')} → {time_ago(t)}")
输出(示例):
2024-06-15 14:30 → 30秒前
2024-06-15 14:25 → 5分钟前
2024-06-15 11:30 → 3小时前
2024-06-13 14:30 → 2天前
2024-06-01 14:30 → 2周前
2024-04-16 14:30 → 2个月前
2023-05-11 14:30 → 1年前
第六部分:replace()------替换时间分量
python
from datetime import datetime
dt = datetime(2024, 6, 15, 14, 30, 45)
# replace 返回新的 datetime 对象(原对象不变)
dt_new_year = dt.replace(year=2025)
dt_new_hour = dt.replace(hour=9, minute=0, second=0)
dt_start_day = dt.replace(hour=0, minute=0, second=0, microsecond=0)
dt_end_day = dt.replace(hour=23, minute=59, second=59, microsecond=999999)
print(f"原始: {dt}")
print(f"换年份: {dt_new_year}")
print(f"换时间: {dt_new_hour}")
print(f"当天开始:{dt_start_day}")
print(f"当天结束:{dt_end_day}")
# ===== 实际应用:获取本周/本月的开始和结束 =====
from datetime import date, timedelta
def get_week_range(dt=None):
"""获取包含指定日期的那一周的开始(周一)和结束(周日)"""
if dt is None:
dt = datetime.now()
# weekday():0=周一,6=周日
monday = dt - timedelta(days=dt.weekday())
sunday = monday + timedelta(days=6)
week_start = monday.replace(hour=0, minute=0, second=0, microsecond=0)
week_end = sunday.replace(hour=23, minute=59, second=59, microsecond=999999)
return week_start, week_end
def get_month_range(dt=None):
"""获取包含指定日期的那个月的开始和结束"""
import calendar
if dt is None:
dt = datetime.now()
# 月份第一天
month_start = dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
# 月份最后一天:下个月1号再减1天
last_day = calendar.monthrange(dt.year, dt.month)[1]
month_end = dt.replace(day=last_day, hour=23, minute=59,
second=59, microsecond=999999)
return month_start, month_end
now = datetime(2024, 6, 15, 14, 30)
ws, we = get_week_range(now)
print(f"\n本周:{ws.strftime('%Y-%m-%d')} 到 {we.strftime('%Y-%m-%d')}")
ms, me = get_month_range(now)
print(f"本月:{ms.strftime('%Y-%m-%d')} 到 {me.strftime('%Y-%m-%d')}")
第七部分:时区处理
7.1 时区的基本概念
UTC(协调世界时):国际时间标准,等同于 GMT(格林威治时间)
北京时间(CST):UTC + 8小时
naive datetime(无时区的时间):
datetime.now() 返回的就是 naive datetime
没有时区信息,就像没有单位的数字
aware datetime(有时区的时间):
带有 tzinfo 属性的 datetime
可以准确表示全球任何地点的绝对时刻
适合多时区的系统(全球电商、国际会议等)
7.2 Python 内置时区(timezone)
python
from datetime import datetime, timezone, timedelta
# ===== UTC 时区 =====
utc = timezone.utc
# 创建有时区信息的 datetime
dt_utc = datetime.now(tz=utc)
print(f"UTC时间:{dt_utc}")
print(f"时区信息:{dt_utc.tzinfo}") # UTC
# ===== 自定义时区(东八区)=====
cst = timezone(timedelta(hours=8), name='CST') # 北京/上海时间
jst = timezone(timedelta(hours=9), name='JST') # 东京时间
est = timezone(timedelta(hours=-5), name='EST') # 纽约时间
# 获取各时区的当前时间
now_cst = datetime.now(tz=cst)
now_jst = datetime.now(tz=jst)
now_est = datetime.now(tz=est)
print(f"\n当前时间(各时区):")
print(f" 北京(CST):{now_cst.strftime('%Y-%m-%d %H:%M:%S %Z')}")
print(f" 东京(JST):{now_jst.strftime('%Y-%m-%d %H:%M:%S %Z')}")
print(f" 纽约(EST):{now_est.strftime('%Y-%m-%d %H:%M:%S %Z')}")
# ===== 时区转换 =====
# 将一个时区的时间转换为另一个时区
meeting_time_cst = datetime(2024, 6, 15, 14, 0, tzinfo=cst)
meeting_time_utc = meeting_time_cst.astimezone(utc)
meeting_time_est = meeting_time_cst.astimezone(est)
meeting_time_jst = meeting_time_cst.astimezone(jst)
print(f"\n会议时间(北京 {meeting_time_cst.strftime('%H:%M')}):")
print(f" UTC:{meeting_time_utc.strftime('%H:%M')}")
print(f" 纽约:{meeting_time_est.strftime('%H:%M')}")
print(f" 东京:{meeting_time_jst.strftime('%H:%M')}")
7.3 使用 pytz(处理夏令时等复杂时区,需安装)
bash
pip install pytz
python
import pytz
from datetime import datetime
# 使用标准 IANA 时区名称
tz_beijing = pytz.timezone('Asia/Shanghai') # 北京时间
tz_tokyo = pytz.timezone('Asia/Tokyo')
tz_new_york = pytz.timezone('America/New_York') # 自动处理夏令时!
tz_london = pytz.timezone('Europe/London')
tz_utc = pytz.UTC
# 获取当前时间
now_beijing = datetime.now(tz_beijing)
now_ny = datetime.now(tz_new_york)
now_london = datetime.now(tz_london)
print(f"北京:{now_beijing.strftime('%Y-%m-%d %H:%M %Z%z')}")
print(f"纽约:{now_ny.strftime('%Y-%m-%d %H:%M %Z%z')}")
print(f"伦敦:{now_london.strftime('%Y-%m-%d %H:%M %Z%z')}")
# ===== 列出所有时区 =====
all_zones = pytz.all_timezones
asia_zones = [z for z in all_zones if z.startswith('Asia/')]
print(f"\n亚洲时区(部分):{asia_zones[:10]}")
# ===== 夏令时自动处理 =====
summer = datetime(2024, 7, 1, 12, 0) # 7月(纽约是夏令时 EDT = UTC-4)
winter = datetime(2024, 1, 1, 12, 0) # 1月(纽约是标准时 EST = UTC-5)
summer_ny = tz_new_york.localize(summer)
winter_ny = tz_new_york.localize(winter)
print(f"\n夏天纽约:{summer_ny.strftime('%H:%M %Z%z')}") # EDT -04:00
print(f"冬天纽约:{winter_ny.strftime('%H:%M %Z%z')}") # EST -05:00
# pytz 自动判断是夏令时还是标准时,offset 不同!
7.4 naive 和 aware 的互转
python
from datetime import datetime, timezone, timedelta
# naive(无时区)→ aware(有时区):假设它是某个时区的时间
naive_dt = datetime(2024, 6, 15, 14, 30) # naive
# 方法1:replace(直接附加时区,不做转换)
cst = timezone(timedelta(hours=8))
aware_dt = naive_dt.replace(tzinfo=cst)
print(f"aware:{aware_dt}") # 2024-06-15 14:30:00+08:00
# 方法2:pytz.localize(推荐,能正确处理夏令时)
import pytz
tz = pytz.timezone('Asia/Shanghai')
aware_dt2 = tz.localize(naive_dt)
print(f"aware(pytz):{aware_dt2}")
# aware(有时区)→ naive(无时区):去掉时区信息
naive_back = aware_dt.replace(tzinfo=None)
print(f"naive:{naive_back}")
# 或者
naive_back2 = aware_dt2.replace(tzinfo=None)
第八部分:calendar 模块
8.1 常用函数
python
import calendar
# ===== 判断闰年 =====
print(calendar.isleap(2024)) # True(2024是闰年)
print(calendar.isleap(2023)) # False
print(calendar.isleap(2000)) # True(整百年能被400整除才是闰年)
print(calendar.isleap(1900)) # False(1900不是闰年)
# ===== 某年有多少个闰年(计数)=====
print(calendar.leapdays(2000, 2025)) # 2000-2024年中的闰年数:7
# ===== 某月有几天 =====
# monthrange 返回 (该月第一天是星期几, 该月有几天)
# 星期:0=周一,6=周日
year, month = 2024, 2
first_weekday, total_days = calendar.monthrange(year, month)
print(f"\n{year}年{month}月:")
print(f" 第一天是:{'一二三四五六日'[first_weekday]}曜日")
print(f" 共 {total_days} 天")
# 快速获取某月天数
def days_in_month(year, month):
return calendar.monthrange(year, month)[1]
for m in range(1, 13):
print(f" {m}月:{days_in_month(2024, m)}天", end=" ")
if m % 4 == 0:
print()
# ===== 打印日历 =====
print()
print(calendar.month(2024, 6)) # 文本格式的月历
# ===== 整年日历 =====
# print(calendar.calendar(2024)) # 打印2024年全年日历(很长)
# ===== 设置一周从哪天开始 =====
# 默认从周一(0)开始
calendar.setfirstweekday(6) # 设为从周日开始(美国习惯)
print(calendar.month(2024, 6))
calendar.setfirstweekday(0) # 恢复从周一开始(中国/欧洲习惯)
8.2 实用:获取某月所有日期
python
import calendar
from datetime import date
def get_all_dates_in_month(year, month):
"""获取某年某月的所有日期"""
total_days = calendar.monthrange(year, month)[1]
return [date(year, month, d) for d in range(1, total_days + 1)]
def get_workdays_in_month(year, month):
"""获取某年某月的所有工作日(周一到周五)"""
all_dates = get_all_dates_in_month(year, month)
return [d for d in all_dates if d.weekday() < 5] # 0-4是周一到周五
def get_weekends_in_month(year, month):
"""获取某年某月的所有周末"""
all_dates = get_all_dates_in_month(year, month)
return [d for d in all_dates if d.weekday() >= 5]
year, month = 2024, 6
workdays = get_workdays_in_month(year, month)
weekends = get_weekends_in_month(year, month)
print(f"2024年6月共{calendar.monthrange(year,month)[1]}天")
print(f"工作日:{len(workdays)}天")
print(f"周末: {len(weekends)}天")
print(f"\n工作日列表(前5个):{[str(d) for d in workdays[:5]]}")
print(f"周末列表:{[str(d) for d in weekends]}")
第九部分:综合实战案例
9.1 案例一:代码性能计时器(上下文管理器)
python
import time
from contextlib import contextmanager
class Timer:
"""功能完整的计时器,支持多次计时和统计"""
def __init__(self, name=''):
self.name = name
self.records = [] # 存储每次计时结果
def __enter__(self):
self._start = time.perf_counter()
return self
def __exit__(self, *args):
elapsed = time.perf_counter() - self._start
self.records.append(elapsed)
label = f"[{self.name}] " if self.name else ""
print(f"{label}耗时:{elapsed*1000:.3f} 毫秒")
return False
@property
def last(self):
return self.records[-1] if self.records else 0
@property
def total(self):
return sum(self.records)
@property
def average(self):
return self.total / len(self.records) if self.records else 0
def report(self):
if not self.records:
print("没有计时记录")
return
print(f"\n=== 计时报告:{self.name} ===")
print(f" 运行次数:{len(self.records)}")
print(f" 最短耗时:{min(self.records)*1000:.3f} ms")
print(f" 最长耗时:{max(self.records)*1000:.3f} ms")
print(f" 平均耗时:{self.average*1000:.3f} ms")
print(f" 总耗时: {self.total*1000:.3f} ms")
# 使用示例
timer = Timer("列表生成测试")
for _ in range(5):
with timer:
result = [i**2 for i in range(100000)]
timer.report()
# 对比两种写法的性能
print("\n=== 性能对比:列表推导式 vs 循环 ===")
t1 = Timer("列表推导式")
t2 = Timer("for循环")
for _ in range(10):
with t1:
result1 = [i * 2 for i in range(50000)]
with t2:
result2 = []
for i in range(50000):
result2.append(i * 2)
t1.report()
t2.report()
print(f"\n列表推导式快 {t2.average / t1.average:.1f} 倍")
9.2 案例二:日志时间戳系统
python
from datetime import datetime
import time
class Logger:
"""带时间戳的简单日志器"""
LEVELS = {'DEBUG': 0, 'INFO': 1, 'WARNING': 2, 'ERROR': 3, 'CRITICAL': 4}
def __init__(self, name, level='INFO', time_format='%Y-%m-%d %H:%M:%S'):
self.name = name
self.min_level = self.LEVELS.get(level, 1)
self.time_format = time_format
self._start_time = time.perf_counter()
def _log(self, level, message):
if self.LEVELS.get(level, 0) < self.min_level:
return
now = datetime.now()
elapsed = time.perf_counter() - self._start_time
timestamp = now.strftime(self.time_format)
level_str = f"{level:8s}"
print(f"[{timestamp}] [{level_str}] [{self.name}] {message} "
f"(+{elapsed:.3f}s)")
def debug(self, msg): self._log('DEBUG', msg)
def info(self, msg): self._log('INFO', msg)
def warning(self, msg): self._log('WARNING', msg)
def error(self, msg): self._log('ERROR', msg)
def critical(self, msg): self._log('CRITICAL', msg)
# 使用示例
logger = Logger('MyApp', level='DEBUG')
logger.info("应用启动")
time.sleep(0.1)
logger.debug("读取配置文件")
time.sleep(0.05)
logger.info("数据库连接成功")
time.sleep(0.2)
logger.warning("内存使用超过70%")
time.sleep(0.1)
logger.error("文件读取失败:/data/input.csv")
9.3 案例三:日期区间统计
python
from datetime import datetime, timedelta, date
from collections import defaultdict
def group_by_period(records, period='day'):
"""
按时间周期对记录进行分组统计
参数:
records - [(datetime, value), ...] 格式的数据列表
period - 'day' / 'week' / 'month' / 'year'
返回:
{期间标签: 合计值} 的字典
"""
result = defaultdict(float)
for dt, value in records:
if period == 'day':
key = dt.strftime('%Y-%m-%d')
elif period == 'week':
# 取该周周一的日期作为周标签
monday = dt - timedelta(days=dt.weekday())
key = f"{monday.strftime('%Y-W%W')}({monday.strftime('%m/%d')}周)"
elif period == 'month':
key = dt.strftime('%Y-%m')
elif period == 'year':
key = dt.strftime('%Y')
else:
raise ValueError(f"不支持的 period:{period}")
result[key] += value
return dict(sorted(result.items()))
# 生成模拟销售数据(60天)
import random
random.seed(42)
start_date = datetime(2024, 4, 1)
sales_data = []
for i in range(60):
dt = start_date + timedelta(days=i)
# 工作日销售额更高
base = 1500 if dt.weekday() < 5 else 800
sales = base + random.gauss(0, 200)
sales_data.append((dt, max(0, sales)))
# 按不同粒度统计
print("===== 按日统计(前7天)=====")
daily = group_by_period(sales_data, 'day')
for k, v in list(daily.items())[:7]:
bar = '█' * int(v / 100)
print(f" {k}:¥{v:8.2f} {bar}")
print("\n===== 按周统计 =====")
weekly = group_by_period(sales_data, 'week')
for k, v in weekly.items():
bar = '█' * int(v / 500)
print(f" {k}:¥{v:9.2f} {bar}")
print("\n===== 按月统计 =====")
monthly = group_by_period(sales_data, 'month')
for k, v in monthly.items():
print(f" {k}:¥{v:10.2f}")
9.4 案例四:业务时间校验
python
from datetime import datetime, time, date, timedelta
def is_business_hours(dt: datetime = None) -> tuple:
"""
判断是否在工作时间内
返回:(bool, str) 是否工作时间,以及原因说明
"""
if dt is None:
dt = datetime.now()
# 法定节假日(简化版,实际应从 API 获取)
holidays_2024 = {
date(2024, 1, 1), # 元旦
date(2024, 2, 10), # 春节
date(2024, 2, 11), # 春节
date(2024, 2, 12), # 春节
date(2024, 4, 4), # 清明
date(2024, 5, 1), # 劳动节
date(2024, 6, 10), # 端午
date(2024, 9, 17), # 中秋
date(2024, 10, 1), # 国庆
date(2024, 10, 7), # 国庆
}
today = dt.date()
current = dt.time()
work_start = time(9, 0)
work_end = time(18, 0)
lunch_s = time(12, 0)
lunch_e = time(13, 30)
if today in holidays_2024:
return False, "法定节假日"
if dt.weekday() >= 5:
return False, f"周{'末六日'[dt.weekday()-5]}"
if current < work_start:
wait = datetime.combine(today, work_start) - dt
mins = int(wait.total_seconds() / 60)
return False, f"未到上班时间(还需等待 {mins} 分钟)"
if current > work_end:
return False, "已过下班时间(明天 9:00 再来)"
if lunch_s <= current < lunch_e:
wait = datetime.combine(today, lunch_e) - dt
mins = int(wait.total_seconds() / 60)
return False, f"午休时间({mins} 分钟后恢复)"
return True, "工作时间内"
# 测试多个时间点
test_times = [
datetime(2024, 6, 15, 8, 30), # 周六早上
datetime(2024, 6, 17, 8, 30), # 周一,未上班
datetime(2024, 6, 17, 10, 0), # 周一,正常工作时间
datetime(2024, 6, 17, 12, 30), # 午休
datetime(2024, 6, 17, 18, 30), # 已下班
datetime(2024, 10, 1, 10, 0), # 国庆
]
for t in test_times:
ok, reason = is_business_hours(t)
status = "✅ 可服务" if ok else "❌ 不可服务"
print(f"{t.strftime('%m-%d %H:%M %a')} {status} ({reason})")
第十部分:常见陷阱与注意事项
10.1 陷阱1:datetime 是不可变对象
python
from datetime import datetime
dt = datetime(2024, 6, 15, 14, 30)
# ❌ 错误:以为可以直接修改属性
# dt.hour = 9 → AttributeError: can't set attribute
# ✅ 正确:用 replace() 生成新对象
dt_new = dt.replace(hour=9, minute=0)
print(dt) # 2024-06-15 14:30:00(原对象不变)
print(dt_new) # 2024-06-15 09:00:00(新对象)
10.2 陷阱2:naive 和 aware 不能直接比较
python
from datetime import datetime, timezone
# naive datetime(无时区)
dt_naive = datetime(2024, 6, 15, 14, 30)
# aware datetime(有时区)
dt_aware = datetime(2024, 6, 15, 14, 30, tzinfo=timezone.utc)
# ❌ 不能比较!
try:
print(dt_naive < dt_aware)
except TypeError as e:
print(f"错误:{e}")
# can't compare offset-naive and offset-aware datetimes
# ✅ 解决方法:要么都加时区,要么都去掉时区
# 方法1:去掉 aware 的时区
dt_a_naive = dt_aware.replace(tzinfo=None)
print(dt_naive == dt_a_naive) # True
# 方法2:给 naive 加上时区
from datetime import timedelta
cst = timezone(timedelta(hours=8))
dt_n_aware = dt_naive.replace(tzinfo=cst)
print(dt_n_aware, dt_aware) # 可以比较,但时区不同结果不同
10.3 陷阱3:strptime 格式必须完全匹配
python
from datetime import datetime
# ❌ 格式不匹配
try:
datetime.strptime('2024-6-5', '%Y-%m-%d') # 月/日没有前导零
except ValueError as e:
print(f"失败:{e}")
# ✅ 正确写法(月/日有前导零)
dt = datetime.strptime('2024-06-05', '%Y-%m-%d')
print(dt)
# ✅ 或者用 dateutil(更灵活,需安装)
# pip install python-dateutil
# from dateutil import parser
# dt = parser.parse('2024-6-5') # 自动识别多种格式
10.4 陷阱4:不同月份天数不同,直接加减月份要小心
python
from datetime import datetime, timedelta
# ❌ 想加一个月,直接加30天不准确
dt = datetime(2024, 1, 31)
wrong = dt + timedelta(days=30)
print(wrong) # 2024-03-01(但我们想要 2024-02-29!)
# ❌ 直接修改 month 值超出范围会报错
try:
dt.replace(month=2, day=31) # 2月没有31号!
except ValueError as e:
print(f"错误:{e}")
# ✅ 正确方法1:用 dateutil.relativedelta(处理月份最安全)
# pip install python-dateutil
# from dateutil.relativedelta import relativedelta
# next_month = dt + relativedelta(months=1)
# print(next_month) # 2024-02-29(自动处理月末)
# ✅ 正确方法2:手动处理
def add_months(dt, months):
"""安全地给 datetime 加 N 个月"""
import calendar
month = dt.month + months
year = dt.year + (month - 1) // 12
month = (month - 1) % 12 + 1
# 防止日期超出该月最大天数
max_day = calendar.monthrange(year, month)[1]
day = min(dt.day, max_day)
return dt.replace(year=year, month=month, day=day)
print(add_months(datetime(2024, 1, 31), 1)) # 2024-02-29
print(add_months(datetime(2024, 1, 31), 2)) # 2024-03-31
print(add_months(datetime(2024, 1, 31), 13)) # 2025-02-28
10.5 陷阱5:time.sleep 精度问题
python
import time
# time.sleep 的精度受操作系统调度影响
# 实际睡眠时间可能比指定时间稍长
start = time.perf_counter()
time.sleep(0.1)
actual = time.perf_counter() - start
print(f"要求 0.1s,实际 {actual:.4f}s")
# 可能是 0.1005s 或 0.1012s 等
# 在需要精确计时的场景,用 busy-wait(忙等待):
def precise_sleep(duration):
"""精确睡眠(CPU 密集型,不建议长时间使用)"""
start = time.perf_counter()
while time.perf_counter() - start < duration:
pass
start = time.perf_counter()
precise_sleep(0.1)
actual = time.perf_counter() - start
print(f"精确睡眠:{actual:.6f}s") # 极接近 0.1
第十一部分:完整速查表
11.1 time 模块
time.time() → 当前时间戳(秒,float)
time.time_ns() → 当前时间戳(纳秒,int)
time.sleep(n) → 暂停 n 秒
time.perf_counter() → 高精度计时(推荐性能测量用)
time.process_time() → 进程 CPU 时间(不含 sleep)
time.localtime([ts]) → 时间戳 → 本地 struct_time
time.gmtime([ts]) → 时间戳 → UTC struct_time
time.mktime(struct) → struct_time → 时间戳
time.strftime(fmt, t) → struct_time → 格式化字符串
time.strptime(s, fmt) → 字符串 → struct_time
time.tzname → 本地时区名称元组
11.2 datetime 模块
📌 date 类
date(year, month, day) → 创建日期
date.today() → 今天
date.fromtimestamp(ts) → 时间戳 → date
date.fromisoformat('2024-01-01')→ ISO字符串 → date
d.year / d.month / d.day → 属性
d.weekday() → 0=周一,6=周日
d.isoweekday() → 1=周一,7=周日
d.strftime(fmt) → 格式化为字符串
d.isoformat() → '2024-01-01'
📌 time 类(datetime.time)
time(hour, minute, second, microsecond) → 创建时间
t.hour / t.minute / t.second / t.microsecond → 属性
t.strftime(fmt) → 格式化
📌 datetime 类
datetime(y,m,d,H,M,S,μs) → 创建
datetime.now() → 当前本地时间(naive)
datetime.now(tz) → 当前时间(aware)
datetime.utcnow() → 当前 UTC 时间
datetime.fromtimestamp(ts) → 时间戳 → datetime
datetime.fromisoformat(s) → ISO字符串 → datetime
datetime.strptime(s, fmt) → 字符串 → datetime(解析)
datetime.combine(date, time) → 合并日期和时间
dt.strftime(fmt) → 格式化为字符串
dt.timestamp() → 转为时间戳
dt.date() / dt.time() → 提取日期/时间部分
dt.replace(year=..., hour=...) → 替换某个分量(返回新对象)
dt.astimezone(tz) → 转换时区
dt.weekday() / dt.isoweekday() → 星期几
📌 timedelta 类
timedelta(days, seconds, microseconds, hours, minutes, weeks)
td.days / td.seconds / td.microseconds → 属性
td.total_seconds() → 转为总秒数
datetime + timedelta → 往后偏移
datetime - timedelta → 往前偏移
datetime - datetime → 相减得 timedelta
📌 timezone 类
timezone.utc → UTC 时区
timezone(timedelta(hours=8)) → 创建自定义时区
总结
学完本章,你应该掌握:
time模块 :time()获取时间戳、sleep()暂停程序、perf_counter()高精度计时datetime三大类 :date(日期)、time(时间)、datetime(日期+时间)strftime:将 datetime 格式化为字符串(%Y-%m-%d %H:%M:%S)strptime:将字符串解析为 datetime(格式必须完全匹配)timedelta:表示时间差,支持加减运算- 时区处理 :naive vs aware,
astimezone()转换时区,pytz处理复杂时区 calendar模块:判断闰年、获取某月天数、生成日历- 常见陷阱:datetime 不可变、naive/aware 不能比较、月份加减要小心
最常用的三行代码:
python
from datetime import datetime, timedelta
now = datetime.now() # 当前时间
tomorrow = now + timedelta(days=1) # 明天
fmt_str = now.strftime('%Y-%m-%d %H:%M:%S') # 格式化
dt = datetime.strptime('2024-06-15', '%Y-%m-%d') # 解析