pendulum:Python 时间处理的优雅解决方案

一、引言

Python 作为一门功能强大且应用广泛的编程语言,凭借其丰富的库和工具生态系统,在众多领域发挥着重要作用。无论是 Web 开发、数据分析与科学、机器学习与人工智能,还是桌面自动化、爬虫脚本、金融量化交易以及教育研究等领域,Python 都展现出了卓越的适应性和高效性。

在日常开发中,时间和日期的处理是一个常见且复杂的任务。Python 标准库中的 datetimetime 模块提供了基本的时间处理功能,但它们的 API 设计不够直观,使用起来较为繁琐,而且在处理时区、本地化和相对时间计算等方面存在一定的局限性。为了解决这些问题,第三方库 pendulum 应运而生,它提供了更加优雅、直观且功能强大的 API,让时间处理变得轻松愉快。

二、pendulum 概述

2.1 用途

pendulum 是一个致力于简化 Python 时间处理的库。它在 Python 标准库的基础上进行了扩展,提供了更加直观、易用的 API,使得时间和日期的创建、操作、格式化和时区转换等任务变得异常简单。无论是处理相对时间(如 “3 天前”、”2 周后”)、时区转换、日期比较,还是进行复杂的时间计算,pendulum 都能轻松应对。

2.2 工作原理

pendulum 的核心是围绕 DateTime 类构建的,该类继承自 Python 标准库中的 datetime.datetime 类,但提供了更多的方法和功能。它通过封装底层的时间处理逻辑,提供了流畅的链式调用 API,让代码更加简洁易读。例如,你可以轻松地创建一个日期时间对象,然后通过链式调用进行各种操作:

import pendulum

# 创建一个表示当前时间的对象
now = pendulum.now()

# 将时间调整为明天,并格式化为 ISO 8601 字符串
tomorrow_iso = now.add(days=1).to_iso8601_string()

# 计算从现在到明天的时间差
diff = now.diff(now.add(days=1))
print(diff.in_hours())  # 输出 24

2.3 优缺点

优点:

  1. 直观的 APIpendulum 的 API 设计非常直观,易于理解和使用,减少了开发者的学习成本。
  2. 时区支持:内置了强大的时区支持,使得时区转换变得简单明了。
  3. 相对时间处理:提供了简洁的方式来处理相对时间,如 “昨天”、”下周” 等。
  4. 链式调用:支持流畅的链式调用语法,使代码更加简洁易读。
  5. 兼容性:与 Python 标准库的 datetime 模块完全兼容,可以无缝集成到现有项目中。

缺点:

  1. 额外依赖:作为第三方库,使用时需要额外安装,增加了项目的依赖管理成本。
  2. 学习曲线:对于已经熟悉 Python 标准库的开发者来说,需要一定的时间来适应 pendulum 的 API 风格。

2.4 License 类型

pendulum 采用 MIT License,这是一种非常宽松的开源许可证,允许用户自由使用、修改和分发代码,只需保留原作者的版权声明即可。这种许可证对于商业和非商业项目都非常友好。

三、pendulum 详细使用指南

3.1 安装 pendulum

使用 pip 可以轻松安装 pendulum

pip install pendulum

安装完成后,就可以在 Python 代码中导入并使用它了。

3.2 创建日期和时间对象

3.2.1 获取当前时间

使用 pendulum.now() 可以获取当前时间的 DateTime 对象:

import pendulum

now = pendulum.now()
print(now)  # 输出当前时间,例如:2025-06-04T14:30:00+08:00

你还可以指定时区:

now_in_utc = pendulum.now('UTC')
print(now_in_utc)  # 输出 UTC 时区的当前时间

3.2.2 创建指定日期和时间

可以使用多种方式创建指定的日期和时间:

# 创建一个指定年月日的日期对象
dt = pendulum.datetime(2025, 6, 4)
print(dt)  # 2025-06-04T00:00:00+00:00

# 创建一个指定年月日时分秒的日期对象
dt = pendulum.datetime(2025, 6, 4, 14, 30, 59)
print(dt)  # 2025-06-04T14:30:59+00:00

# 从时间戳创建
dt = pendulum.from_timestamp(1643872259)
print(dt)  # 2022-01-30T14:30:59+00:00

# 从字符串解析
dt = pendulum.parse('2025-06-04T14:30:59')
print(dt)  # 2025-06-04T14:30:59+00:00

pendulum.parse() 方法非常灵活,可以解析多种格式的日期字符串:

dt = pendulum.parse('2025-06-04')
print(dt)  # 2025-06-04T00:00:00+00:00

dt = pendulum.parse('2025/06/04 14:30')
print(dt)  # 2025-06-04T14:30:00+00:00

dt = pendulum.parse('June 4, 2025', strict=False)
print(dt)  # 2025-06-04T00:00:00+00:00

3.2.3 创建相对时间

pendulum 提供了简洁的方式来创建相对时间:

# 创建昨天的日期
yesterday = pendulum.yesterday()
print(yesterday)  # 2025-06-03T00:00:00+00:00

# 创建明天的日期
tomorrow = pendulum.tomorrow()
print(tomorrow)  # 2025-06-05T00:00:00+00:00

# 创建今天开始的时间
today_start = pendulum.today()
print(today_start)  # 2025-06-04T00:00:00+00:00

3.3 操作日期和时间

3.3.1 加减时间

使用 add()subtract() 方法可以轻松地对日期和时间进行加减操作:

now = pendulum.now()

# 增加一天
tomorrow = now.add(days=1)

# 减少一小时
an_hour_ago = now.subtract(hours=1)

# 链式调用:增加2天3小时45分钟
future = now.add(days=2, hours=3, minutes=45)

# 也可以使用快捷方法
next_week = now.next(pendulum.MONDAY)  # 下一个星期一
last_month = now.subtract(months=1)

3.3.2 修改特定部分

可以直接修改日期和时间的特定部分:

dt = pendulum.now()

# 修改年份
dt = dt.set(year=2026)

# 同时修改月、日、小时
dt = dt.set(month=12, day=25, hour=18)

print(dt)  # 输出修改后的日期时间

3.3.3 比较日期和时间

pendulum 对象可以直接进行比较:

dt1 = pendulum.datetime(2025, 6, 4)
dt2 = pendulum.datetime(2025, 6, 5)

print(dt1 < dt2)  # True
print(dt1 == dt2)  # False
print(dt1 > dt2)  # False

# 检查是否在某个时间段内
now = pendulum.now()
start = now.subtract(days=1)
end = now.add(days=1)

print(now.between(start, end))  # True

3.4 时区处理

pendulum 内置了强大的时区支持:

# 创建一个带有时区的日期时间对象
dt = pendulum.datetime(2025, 6, 4, 14, 30, tz='Asia/Shanghai')
print(dt)  # 2025-06-04T14:30:00+08:00

# 时区转换
dt_in_utc = dt.in_timezone('UTC')
print(dt_in_utc)  # 2025-06-04T06:30:00+00:00

# 获取当前时区
local_tz = pendulum.local_timezone()
print(local_tz)  # 输出当前系统时区,如 Asia/Shanghai

3.5 格式化和解析

3.5.1 格式化为字符串

pendulum 对象可以方便地格式化为各种字符串:

dt = pendulum.now()

# 格式化为 ISO 8601 字符串
iso_str = dt.to_iso8601_string()
print(iso_str)  # 例如:2025-06-04T14:30:00+08:00

# 格式化为 RFC 2822 字符串
rfc_str = dt.to_rfc2822_string()
print(rfc_str)  # 例如:Wed, 04 Jun 2025 14:30:00 +0800

# 使用自定义格式
custom_str = dt.format('YYYY-MM-DD HH:mm:ss')
print(custom_str)  # 例如:2025-06-04 14:30:00

# 本地化格式
fr_str = dt.format('LLLL', locale='fr')
print(fr_str)  # 例如:mercredi 4 juin 2025 14:30

3.5.2 从字符串解析

前面已经介绍过 pendulum.parse() 方法,它还支持指定时区和严格模式:

# 解析带时区的字符串
dt = pendulum.parse('2025-06-04T14:30:00+08:00')
print(dt)  # 2025-06-04T14:30:00+08:00

# 指定时区解析
dt = pendulum.parse('2025-06-04 14:30:00', tz='Asia/Shanghai')
print(dt)  # 2025-06-04T14:30:00+08:00

# 严格模式
try:
    dt = pendulum.parse('June 4, 2025', strict=True)
except ValueError:
    print("解析失败,因为严格模式下无法识别该格式")

dt = pendulum.parse('June 4, 2025', strict=False)
print(dt)  # 2025-06-04T00:00:00+00:00

3.6 时间差计算

计算两个时间点之间的差值是时间处理中的常见需求,pendulum 提供了简单而强大的方法:

dt1 = pendulum.datetime(2025, 6, 1)
dt2 = pendulum.datetime(2025, 6, 10)

# 计算时间差
delta = dt2 - dt1
print(delta.days)  # 9

# 使用 diff() 方法
delta = dt1.diff(dt2)
print(delta.in_days())  # 9
print(delta.in_hours())  # 216

# 相对时间表示
print(delta.for_humans())  # 9 days

for_humans() 方法非常实用,可以将时间差转换为人类可读的格式:

now = pendulum.now()
future = now.add(days=3, hours=2, minutes=15)

delta = future - now
print(delta.for_humans())  # 3 days 2 hours 15 minutes

past = now.subtract(weeks=1)
delta = now - past
print(delta.for_humans())  # 1 week ago

3.7 周期和迭代

pendulum 可以创建时间周期,并对其进行迭代:

start = pendulum.datetime(2025, 1, 1)
end = pendulum.datetime(2025, 1, 10)

# 创建一个周期
period = pendulum.period(start, end)

# 迭代周期内的每一天
for dt in period.range('days'):
    print(dt.to_date_string())

# 计算周期内的天数
print(period.days)  # 9

# 检查某个时间点是否在周期内
print(pendulum.datetime(2025, 1, 5) in period)  # True

四、实际案例

4.1 任务调度系统中的时间计算

假设你正在开发一个任务调度系统,需要根据用户设置的时间间隔来安排任务执行。以下是一个使用 pendulum 的示例:

import pendulum
from typing import List, Dict

class TaskScheduler:
    def __init__(self):
        self.tasks = []

    def add_task(self, name: str, interval: Dict[str, int], start_time: pendulum.DateTime = None):
        """添加一个定时任务

        Args:
            name: 任务名称
            interval: 时间间隔,如 {"days": 1, "hours": 2}
            start_time: 开始时间,默认为当前时间
        """
        if start_time is None:
            start_time = pendulum.now()

        task = {
            "name": name,
            "interval": interval,
            "start_time": start_time,
            "next_run": start_time
        }
        self.tasks.append(task)

    def get_next_tasks(self, count: int = 5) -> List[Dict]:
        """获取接下来要执行的任务

        Args:
            count: 获取的任务数量

        Returns:
            排序后的任务列表
        """
        # 按下次执行时间排序
        sorted_tasks = sorted(self.tasks, key=lambda t: t["next_run"])
        return sorted_tasks[:count]

    def execute_task(self, task: Dict):
        """执行任务并更新下次执行时间"""
        print(f"执行任务: {task['name']}")

        # 更新下次执行时间
        next_run = task["next_run"].add(**task["interval"])
        task["next_run"] = next_run

    def run_pending(self):
        """运行所有待执行的任务"""
        now = pendulum.now()
        for task in self.tasks:
            if task["next_run"] <= now:
                self.execute_task(task)

# 使用示例
scheduler = TaskScheduler()

# 添加任务
scheduler.add_task(
    "每日数据备份", 
    {"days": 1}, 
    pendulum.datetime(2025, 6, 4, 23, 59)  # 每天 23:59 执行
)

scheduler.add_task(
    "每周报告生成", 
    {"weeks": 1}, 
    pendulum.datetime(2025, 6, 7, 15, 0)  # 每周日 15:00 执行
)

scheduler.add_task(
    "每小时监控检查", 
    {"hours": 1}
)

# 查看接下来要执行的任务
next_tasks = scheduler.get_next_tasks()
for task in next_tasks:
    print(f"任务: {task['name']}, 下次执行时间: {task['next_run']}")

# 运行待执行的任务
scheduler.run_pending()

4.2 数据分析中的时间序列处理

在数据分析中,经常需要处理时间序列数据。以下是一个使用 pendulum 处理时间序列数据的示例:

import pendulum
import random
import pandas as pd
import matplotlib.pyplot as plt

# 生成模拟时间序列数据
def generate_time_series(start_date: str, end_date: str, freq: str = 'D') -> pd.DataFrame:
    """生成模拟时间序列数据

    Args:
        start_date: 开始日期
        end_date: 结束日期
        freq: 频率,如 'D' 表示天,'H' 表示小时

    Returns:
        包含时间序列数据的 DataFrame
    """
    # 创建日期范围
    date_range = pd.date_range(start=start_date, end=end_date, freq=freq)

    # 生成随机数据
    values = [random.randint(100, 1000) for _ in range(len(date_range))]

    # 创建 DataFrame
    df = pd.DataFrame({
        'date': date_range,
        'value': values
    })

    return df

# 分析时间序列数据
def analyze_time_series(df: pd.DataFrame) -> None:
    """分析时间序列数据并可视化

    Args:
        df: 包含时间序列数据的 DataFrame
    """
    # 将日期列转换为 pendulum 日期
    df['pendulum_date'] = df['date'].apply(lambda x: pendulum.instance(x.to_pydatetime()))

    # 提取月份和星期几
    df['month'] = df['pendulum_date'].apply(lambda x: x.month_name())
    df['day_of_week'] = df['pendulum_date'].apply(lambda x: x.day_of_week)

    # 按月份分组统计
    monthly_stats = df.groupby('month')['value'].sum().reset_index()

    # 按星期几分组统计
    weekday_stats = df.groupby('day_of_week')['value'].mean().reset_index()

    # 转换星期几为名称
    weekday_names = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    weekday_stats['day_name'] = weekday_stats['day_of_week'].apply(lambda x: weekday_names[x])

    # 可视化
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

    # 月度统计
    ax1.bar(monthly_stats['month'], monthly_stats['value'])
    ax1.set_title('月度统计')
    ax1.set_xlabel('月份')
    ax1.set_ylabel('总和')
    ax1.tick_params(axis='x', rotation=45)

    # 周度统计
    ax2.bar(weekday_stats['day_name'], weekday_stats['value'])
    ax2.set_title('周度统计')
    ax2.set_xlabel('星期')
    ax2.set_ylabel('平均值')

    plt.tight_layout()
    plt.show()

# 使用示例
# 生成一年的每日数据
df = generate_time_series('2024-01-01', '2024-12-31')

# 分析数据
analyze_time_series(df)

五、总结

pendulum 是一个功能强大、使用方便的 Python 时间处理库,它弥补了 Python 标准库在时间处理方面的不足,提供了更加直观、优雅的 API。通过本文的介绍,我们了解了 pendulum 的基本概念、工作原理、优缺点以及详细的使用方法,并通过实际案例展示了它在任务调度和数据分析等场景中的应用。

无论是处理简单的日期计算,还是复杂的时区转换和时间序列分析,pendulum 都能帮助你轻松应对。如果你还在为 Python 中的时间处理而烦恼,不妨尝试一下 pendulum,相信它会给你带来惊喜。

六、相关资源

  • Pypi地址:https://pypi.org/project/pendulum
  • Github地址:https://github.com/sdispater/pendulum
  • 官方文档地址:https://pendulum.eustace.io/docs/

关注我,每天分享一个实用的Python自动化工具。