27.Python datetime 与 time 完全指南

文章目录

    • 第一部分:为什么要学时间处理?
      • [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))    → 创建自定义时区

总结

学完本章,你应该掌握:

  1. time 模块time()获取时间戳、sleep()暂停程序、perf_counter()高精度计时
  2. datetime 三大类date(日期)、time(时间)、datetime(日期+时间)
  3. strftime :将 datetime 格式化为字符串(%Y-%m-%d %H:%M:%S
  4. strptime:将字符串解析为 datetime(格式必须完全匹配)
  5. timedelta:表示时间差,支持加减运算
  6. 时区处理 :naive vs aware,astimezone() 转换时区,pytz 处理复杂时区
  7. calendar 模块:判断闰年、获取某月天数、生成日历
  8. 常见陷阱: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') # 解析

https://www.quanzhankaige.com/python27/

相关推荐
笨鸟先飞,勤能补拙2 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能2 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂3 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study3 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境4 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境4 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1234 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102165 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb005 小时前
一个项目带你入门AI应用开发01
python
0566465 小时前
Python康复训练——常用标准库
开发语言·python·学习