- 后端
【免费下载链接】pendulum
Python datetimes made easy
Pendulum 是"Python datetimes made easy"这一理念的开源实现,其 CHANGELOG.md 完整记录了从 2.0.0(2018 年)到 3.2.0(2026 年)的每一个重要节点。本文以该变更日志为脉络,结合仓库源码(Rust 扩展、Python 核心模块、测试与打包配置),系统拆解 Pendulum 在 API 命名、时区系统、解析格式化、Duration/Interval 语义、国际化与测试基础设施上的演进逻辑,帮助读者理解"为什么现在的 API 长这样",并为 2.x → 3.x 的升级迁移提供可落地的对照清单。
版本速览:八个主要版本的技术主线
Pendulum 的版本史可以用几条主线概括:API 语义收敛(Pendulum→DateTime、Interval→Duration、Period→Interval)、性能层重写(C 扩展 → Rust/PyO3 扩展)、时区栈换血(pytz → 标准库zoneinfo)、Python 版本支持策略收紧(逐步淘汰 2.7/3.4–3.8,拥抱 3.10–3.14),以及国际化体系从自定义数据切换到 CLDR。
| 版本 | 发布时间 | 关键里程碑 |
|---|---|---|
| 2.0.0 | 2018-05-08 | API 大重构:Pendulum→DateTime,Interval→Duration;引入年/月级 Duration、ISO 8601 时长与区间解析、CLDR 本地化、local()/naive()辅助函数 |
| 2.1.0 | 2020-03-07 | 完整类型标注与 PEP-561 合规;is_anniversary();多个时区过渡与from_format()修复 |
| 2.1.1 | 2020-07-13 | from_format()时区匹配、负 timedelta 相减、DST 过渡期now()等修复 |
| 3.0.0a1 | 2022-11-23 | 放弃 Python 2.7/3.5/3.6;Timezone改用zoneinfo.ZoneInfo;Period→Interval;新时间旅行测试辅助 |
| 3.0.0b1 | 2023-10-01 | 扩展层全面改写为 Rust(PyO3);instance()支持全部原生类型;DST 下start_of()/end_of()修复 |
| 3.0.0 | 2023-12-16 | 依赖约束放宽;测试辅助改为testextra 可选安装;week_of_month边界修复 |
| 3.1.0 | 2025-04-19 | 支持 Python 3.13、移除 3.8;UA/BG 语言包;timezones()改用系统 tzdata |
| 3.2.0 | 2026-01-30 | 支持 Python 3.14;PyPy 下限提升到 3.11;移除pytz依赖;PyO3 升级 0.27;HI 语言包 |
从打包配置看,当前版本由 pyproject.toml 声明requires-python = ">=3.10",classifiers 覆盖 Python 3.10–3.14,运行时依赖仅剩python-dateutil与tzdata两项,与"移除 pytz、拥抱标准库 zoneinfo"的演进方向完全一致。
架构里程碑:扩展层从 C 到 Rust 的重写
PyO3 与 maturin 构建体系
3.0.0b1 起,Pendulum 的性能敏感代码从 C 扩展迁移到 Rust,通过 rust/Cargo.toml 中的pyo3 = "0.27"与cdylibcrate 类型构建,模块名为_pendulum。构建后端在 pyproject.toml 中指定为maturin,并在[tool.maturin]段声明module-name = "pendulum._pendulum"。
Rust 侧代码按职责拆分在 rust/src 下:
parsing.rs:ISO 8601 解析核心;helpers.rs与python/helpers.rs:通用辅助逻辑;python/types/duration.rs、python/types/precise_diff.rs、python/types/timezone.rs:与 Python 层Duration、precise_diff、FixedTimezone对应的加速实现(见 rust/src/python/types/mod.rs)。
3.2.0 中"Fixed incorrect date offset calculation in Rust extensions(PR #918)"正是对这套 Rust 扩展的日期偏移计算修正;PyO3 升级到 0.27(PR #922)则保证了新工具链下的 ABI 兼容与稳定性。在纯 Python 环境(如 PyPy)下,Pendulum 仍提供等价实现兜底,这一点也体现在 3.1.0 对"pure Python wheels"的专门修复(PR #889)上。
从源码看 Duration 的直观归一化
Rust 扩展与 Python 层职责互补。以 src/pendulum/duration.py 的Duration.__new__为例,它通过timedelta.__new__(..., days + years * 365 + months * 30, ...)兼容原生timedelta的构造方式,再基于total_seconds()反推weeks/remaining_days/hours/minutes等"直观单位",实现了 2.0.0 引入的"年/月级 Duration"语义:pendulum.duration(years=1, months=2)会被拆解为_years、_months与剩余的天、时、分、秒分量。这正是"替换标准 timedelta 但提供更直觉化属性"的底层机制。
API 语义收敛:两次重要的类名更名
CHANGELOG 中记录了两轮影响深远的更名,理解它们对排查旧代码至关重要:
- 2.0.0:
Pendulum类更名为DateTime,Interval类更名为Duration,并移除了create()、utcnow()辅助函数;strict关键字参数更名为exact。 - 3.0.0a1:
Period类更名为Interval,period辅助函数更名为interval,同时移除旧测试辅助test()与set_test_now()。
在 src/pendulum/init.py 的__all__中可以看到收敛后的最终形态:DateTime、Date、Time、Duration、Interval五大核心类型,配合datetime、date、time、duration、interval、instance、parse、now等函数式入口。2.0.0 还记录了属性的方法化改造:local、utc、is_dst从属性变为is_local()、is_utc()、is_dst()方法。
另一个语义统一点是 3.0.0b1 的"day of week convention 全代码库一致化"(PR #731)——配合 src/pendulum/day.py 中的WeekDay枚举与 src/pendulum/init.py 导出的MONDAY~SUNDAY常量,星期语义在Date/DateTime各修饰方法中保持一致。
时区系统的三次换血
CHANGELOG 揭示了 Pendulum 时区栈的完整演进:
- 早期(2.0.x):自研 TZ 数据文件读取(
localtime/clock文件),CHANGELOG 中反复出现"修复某些系统读取 clock 文件/时区文件失败"的条目(2.0.2、2.0.3、2.0.5)。 - 3.0.0a1(PR #569):
Timezone类改为依托标准库zoneinfo.ZoneInfo,从根本上告别自维护时区数据。 - 3.2.0:彻底移除
pytz依赖(PR #911);3.1.0 修复pendulum.tz.timezones()改用系统 tzdata(PR #801);3.2.0 用pathlib读取 Unix TZ 数据(PR #742)。
当前实现集中在 src/pendulum/tz 目录:timezone.py定义Timezone与FixedTimezone,local_timezone.py负责本地时区探测,windows.py处理 Windows 时区映射。同时 src/pendulum/init.py 的timezone()入口支持字符串 IANA 名称(Timezone)、整数固定偏移(FixedTimezone)与UTC三种形态;_safe_timezone()(src/pendulum/init.py)还能把zoneinfo、pytz风格的 tzinfo 对象归一化处理——这是 3.x 后"同时兼容新旧生态"的兼容层证据。
与 2.1.1"修复 DST 过渡期now()返回错误结果"、3.0.0b1"修复 DST 下start_of()/end_of()"(PR #713)等条目呼应,时区修复一直是变更日志的高频主题,也印证了 Pendulum 在处理跳过时间(skipped time)、重复时间(fold 属性)等 DST 边界上的持续投入。
解析与格式化:高频修复区
CHANGELOG 中解析格式化相关条目最多,值得使用者重点关注的几类:
from_format()严格性:2.0.4/2.1.0/2.1.1 连续修复"转义元素不被识别"(2.0.4)、"接受多余文本"(PR #372)、"非法时区被匹配"(PR #374);2.0.5 修复 ISO 周日期解析、2.0.4 增加xtoken 与两位数日期支持。parse()行为:3.2.0 修复pendulum.parse('now', tz='...')忽略时区参数(PR #701)与parse未标记为导出(PR #693);2.0.0 起exact默认关闭,不再回退到dateutil宽松解析。- ISO 8601 时长/区间:2.0.0 引入 ISO 8601 时长与区间解析;3.1.0/3.2.0 修复非法区间字符串解析(PR #843、PR #860);3.2.0 修复纯 Python 实现中空
Duration未报错的问题(PR #903)。相关实现见 src/pendulum/parsing/iso8601.py,其中parse_iso8601最终通过Duration(years=..., months=..., weeks=..., ...)构造返回(src/pendulum/parsing/iso8601.py),与 Rust 侧parsing.rs形成双实现。
3.2.0 的"优化re.方法使用(PR #741)"与"locales 和 pytest 改为懒加载(PR #926)"属于性能与启动开销优化——后者在 src/pendulum/init.py 中有直接体现:Traveller通过@cache惰性导入,避免未使用测试功能时强制加载 pytest。
Duration 与 Interval 的深度修复史
Duration(继承timedelta)与Interval(继承Duration)是 Pendulum 的核心价值之一,CHANGELOG 记录了它们的大量边界修复:
- 分量正确性:2.1.1 修复含年/月的
total_units()计算错误(PR #482);2.0.2 修复负Period的weeks属性;3.0.0 修复"添加时长时小时与天处理错误"(PR #775)。 - 拷贝语义:3.0.0 修复深拷贝
DateTime时fold属性丢失(PR #776);3.2.0 修复Interval深拷贝(PR #850)与Duration深拷贝漏掉 weeks(PR #933)。 - 输出与比较:2.1.0 起空
Duration的in_words()返回0 milliseconds;3.2.0 修复in_words()的复数化 bug(PR #826);2.1.0 修复Period与timedelta等的比较问题(PR #427)。
in_words()的本地化输出逻辑见 src/pendulum/duration.py:按 year→month→week→day→hour→minute→second 顺序组装,通过pendulum.locale(locale)获取翻译并用复数规则选择词形;空时长则回退到秒/微秒表述。Interval的构造则在 src/pendulum/interval.py 中校验 start/end 类型一致性与 naive/aware 混用,并支持absolute=True自动交换起止点。
国际化:CLDR 驱动的 locale 体系
2.0.0 起 Pendulum 采用基于 CLDR 数据的新本地化系统,CHANGELOG 中每个版本都伴随 locale 新增或修正:
- 新增语言包:
sk/ja/he/sv(3.0.0a1)、nl/it/id/nb/nn(2.1.0)、pl(2.1.1)、ru(2.0.5)、ua/bg(3.1.0)、hi(3.2.0)。 - 翻译修正:3.1.0 修复韩语
before/after翻译(PR #858)、修正 Kyiv 拼写(PR #885)。
每个 locale 在 src/pendulum/locales 下由两个文件组成:locale.py(CLDR 数据,自动生成不可手改)与custom.py(CLDR 未覆盖的 Pendulum 自定义数据)。in_words()、diff_for_humans()等人类可读输出正是通过这套体系完成多语言化,测试覆盖在 tests/localization 目录中逐语言验证。
测试基础设施:从内置辅助到时间旅行
3.0.0a1 引入新的时间旅行测试辅助(PR #626),同时移除旧test()/set_test_now();3.0.0 起这些辅助改为通过testextra 可选安装(PR #778),依赖约束为 pyproject.toml 中的time-machine>=3.0.0,<4.0.0(PyPy 除外,3.2.0 为time-machine增加了上限,PR #931)。
当前入口定义在 src/pendulum/testing/traveller.py,公开 API 为freeze、travel、travel_to、travel_back,在 src/pendulum/init.py 中通过_traveller()懒加载导出,对应测试见 tests/testing/test_time_travel.py。这一设计使得测试代码可以在不依赖全局状态的前提下模拟任意时刻,配合 tests/fixtures/tz 中的时区夹具验证 DST 等边界行为。
兼容性与升级要点:2.x → 3.x 对照清单
综合 CHANGELOG 的 breaking changes,从 2.x 升级到 3.x 需要关注:
- 类名:
Pendulum→DateTime(2.0.0)、Interval(旧时长)→Duration(2.0.0)、Period→Interval(3.0.0a1),注意同名不同义。 - 辅助函数:
period()→interval();create()/utcnow()已删除;strict参数改名exact且默认关闭 dateutil 回退。 - 属性→方法:
is_local()、is_utc()、is_dst()不再是属性。 - 测试 API:
test()/set_test_now()移除,改用freeze/travel/travel_to/travel_back,且需安装pendulum[test]。 - Python 版本:当前要求 3.10+(PyPy 3.11+,见 3.2.0),2.7/3.4–3.8 均已被移除。
- 时区行为:
Timezone底层基于zoneinfo.ZoneInfo,pytz依赖已彻底移除,依赖tzdata包提供系统缺失的时区数据。
小结
从 CHANGELOG.md 的条目密度可以看出,Pendulum 的演进始终围绕三条主线:把"直觉化的日期时间 API"打磨到极致(Duration 单位拆分、diff_for_humans、in_words本地化)、在性能与正确性之间反复校准(Rust 扩展 + 大量 DST/时区修复)、以及持续拥抱 Python 生态的现代标准(zoneinfo、PEP-561、懒加载、可选的测试依赖)。对使用者而言,这份变更日志既是升级手册,也是理解"为什么 API 设计成现在这样"的第一手资料。
- 后端
【免费下载链接】pendulum
Python datetimes made easy
相关推荐
Ripple 运行时演进全解:从 CHANGELOG 追溯 Ripple 框架的版本史与源码实证
Ripple 运行时演进全解:从 CHANGELOG 追溯 Ripple 框架的版本史与源码实证 packages/ripple/CHANGELOG.md 是
前端Web框架SSRLuxon 3.x 版本演进全解析:从 CHANGELOG 到源码的日期时间库升级指南
Luxon 3.x 版本演进全解析:从 CHANGELOG 到源码的日期时间库升级指南 Luxon 是一个面向 JavaScript 的日期与时间处理库,采用不
开发工具Emotion 版本演进全览:从 CHANGELOG 透视 CSS-in-JS 库的架构与 API 变迁
Emotion 版本演进全览:从 CHANGELOG 透视 CSS in JS 库的架构与 API 变迁 Emotion 是一个面向高性能样式组合设计的 CSS
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考