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/

相关推荐
qiumingxun2 小时前
Redis——使用 python 操作 redis 之从 hmse 迁移到 hset
数据库·redis·python
2401_873544922 小时前
使用XGBoost赢得Kaggle比赛
jvm·数据库·python
m0_569881472 小时前
进阶技巧与底层原理
jvm·数据库·python
Highcharts.js2 小时前
Highcharts for Python|用 Pythonic 的方式构建AI数据可视化图表
前端·人工智能·python·信息可视化·数据科学·highcharts·ai可视化
m0_726965982 小时前
关于conda
开发语言·python·conda
xxjj998a2 小时前
Python 爬虫实战案例 - 获取社交平台事件热度并进行影响分析
开发语言·爬虫·python
大尚来也2 小时前
Java 线程池深度解析:ThreadPoolExecutor 七大参数与核心原理
java·python·算法
卡尔特斯2 小时前
uv 精简使用教程
python·ai编程
子豪-中国机器人2 小时前
python AI自动化
java·前端·python