后端【免费下载链接】pendulumPython datetimes made easy项目地址https://gitcode.com/gh_mirrors/pe/pendulum点击查看免费下载导读时区是任何 datetime 库都无法回避的复杂话题尤其在夏令时DST切换的弹簧前拨、秋季回拨时刻本地时间会出现不存在的时刻Skipped Time和重复出现的时刻Repeated Time。本文以 Pendulum 官方文档 docs/docs/timezones.md 为骨架结合源码src/pendulum/tz/timezone.py、src/pendulum/datetime.py与测试用例tests/tz/test_timezone.py系统讲解 Pendulum 如何自动归一化过渡时刻、如何通过dst_rule控制行为、如何跨时区切换以及直接使用标准库datetime时的限制与规避方案。读完本文你将能正确处理任何时区边界场景写出不会翻车的时区敏感代码。归一化创建时刻的自动修正Pendulum 在创建DateTime实例时会针对给定时区做归一化处理自动处理可能发生的 DST 过渡。以Europe/Paris为例2013 年 3 月 31 日凌晨 2:00巴黎时钟拨快 1 小时因此02:30这个本地时刻根本不存在2013 年 10 月 27 日凌晨 2:00 时钟回拨 1 小时02:30一天内出现两次。 import pendulum pendulum.datetime(2013, 3, 31, 2, 30, tzEurope/Paris) # 2013 年 3 月 31 日的 2:30 不存在 # pendulum 会返回实际存在的时间即 3:3002:00 2013-03-31T03:30:0002:00 pendulum.datetime(2013, 10, 27, 2, 30, tzEurope/Paris) # 这里 2:30 在当天出现两次pendulum 默认假定过渡已经发生 2013-10-27T02:30:0001:00从源码看归一化逻辑位于 Timezone.convert()当输入的datetime是 naive无时区信息时Pendulum 会比较过渡前fold0与过渡后fold1的 UTC offset——若offset_after offset_before判定为跳过的时间Skipped Time自动把时刻向前调整到合法时间若offset_before offset_after判定为重复的时间Repeated Time保持时刻不变并按当前 fold 对应的 offset 绑定时区。测试 tests/tz/test_timezone.py 中的test_pendulum_create_skipped与test_pendulum_create_repeated正是对这一行为的验证前者把2013-03-31 02:30归一化为03:3002:00后者保持2013-10-27 02:3001:00。用 dst_rule 控制归一化行为默认的自动假定未必符合所有业务场景因此 Pendulum 提供dst_rule参数其取值定义在 src/pendulum/tz/timezone.py 与 src/pendulum/tz/init.py并作为模块级常量导出常量值含义pendulum.PRE_TRANSITIONpre采用过渡前的 offset即DST 尚未生效pendulum.POST_TRANSITIONpost采用过渡后的 offset即DST 已生效pendulum.TRANSITION_ERRORerror遇到不存在的时刻或歧义时刻时抛出异常 import pendulum pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tzEurope/Paris, dst_rulependulum.PRE_TRANSITION) 2013-03-31T01:30:0001:00 pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tzEurope/Paris, dst_rulependulum.PRE_TRANSITION) 2013-10-27T02:30:0002:00 pendulum.datetime(2013, 3, 31, 2, 30, 0, 0, tzEurope/Paris, dst_rulependulum.TRANSITION_ERROR) # NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist pendulum.datetime(2013, 10, 27, 2, 30, 0, 0, tzEurope/Paris, dst_rulependulum.TRANSITION_ERROR) # AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous.两个异常的类定义位于 src/pendulum/tz/exceptions.py均继承自TimezoneError本身是ValueError的子类NonExistingTime消息为The datetime {} does not exist.AmbiguousTime消息为The datetime {} is ambiguous.。测试 tests/tz/test_timezone.py 中的test_skipped_time_with_error与test_repeated_time_with_error分别用raise_on_unknown_timesTrue验证了这两类异常。注意dst_rule只影响实例创建时刻的归一化。创建之后再进行时间加减、穿越过渡时刻时行为始终一致见下一节。从实现角度补充在DateTime.create()src/pendulum/datetime.py中Pendulum 先用fold1构造标准库datetime再调用tz.convert(dt, raise_on_unknown_timesraise_on_unknown_times)完成归一化dst_rule会被映射为对应的 fold 值并最终反映在归一化结果上。穿越过渡在过渡时刻附近加减时间那么当你在DateTime实例上加减时间、恰好撞上过渡时刻时会发生什么Pendulum 凭借前一个实例提供的上下文fold 状态会自动采用正确行为并应用过渡。 import pendulum dt pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tzEurope/Paris) 2013-03-31T01:59:59.99999901:00 dt dt.add(microseconds1) 2013-03-31T03:00:0002:00 dt.subtract(microseconds1) 2013-03-31T01:59:59.99999901:00春季向前拨钟时01:59:59.99999901:00加 1 微秒后物理上应跳过02:00直接到达03:0002:00再减 1 微秒又精确回到01:59:59.99999901:00加、减完全可逆。秋季回拨时刻同理但需要结合PRE_TRANSITION指定你在第一段02:00 dt pendulum.datetime(2013, 10, 27, 2, 59, 59, 999999, tzEurope/Paris, dst_rulependulum.PRE_TRANSITION) 2013-10-27T02:59:59.99999902:00 dt dt.add(microseconds1) 2013-10-27T02:00:0001:00 dt dt.subtract(microseconds1) 2013-10-27T02:59:59.99999902:0002:59:59.99999902:00第一段加 1 微秒后时钟回拨 1 小时物理时间前进 1 小时但本地钟面回退到02:00:0001:00第二段再减 1 微秒又回到第一段。这就是文档中强调的穿越过渡时加减时间始终保持一致行为的体现——这也是 Pendulum 生态内DateTime比标准库datetime更可靠的核心原因详见下文直接使用 timezone 库一节。切换时区in_timezone() 与 in_tz()切换DateTime实例的时区非常简单调用in_timezone()即可更简洁的别名是in_tz()。 in_paris pendulum.datetime(2016, 8, 7, 22, 24, 30, tzEurope/Paris) 2016-08-07T22:24:3002:00 in_paris.in_timezone(America/New_York) 2016-08-07T16:24:30-04:00 in_paris.in_tz(Asia/Tokyo) 2016-08-08T05:24:3009:00注意切换是物理时刻不变、钟面时间变化巴黎 8 月 7 日 22:24UTC02:00即 UTC 20:24对应纽约 16:24UTC-04:00、东京次日 05:24UTC09:00且跨过了自然日边界。从源码看DateTime.in_timezone() 先把传入的时区经pendulum._safe_timezone()规范化为Timezone或FixedTimezone对象若实例当前无时区信息naive会先replace(fold1)再调用tz.convert(dt)完成换算。in_tz() 只是in_timezone()的薄封装二者行为完全一致。直接使用 timezone 库与标准库 datetime 协同适用范围与警告时间在 Pendulum 生态内工作得最好但也可以配合标准库datetime使用只是存在若干限制尤其是过渡时刻附近的加减运算。警告应避免在 Python 3.6 的环境下直接使用 timezone 库。原因是 Pendulum 高度依赖fold属性Python 3.6 引入。在 Pendulum 生态内它能正常工作正是因为DateTime类内部对fold做了回填backport而标准库datetime在旧版本中没有fold无法表达歧义时刻的两义性。fold 决定过渡规则直接使用标准库datetime时fold属性的值默认被用作过渡规则的依据fold0表示过渡前fold1表示过渡后 from datetime import datetime from pendulum import timezone paris timezone(Europe/Paris) dt datetime(2013, 3, 31, 2, 30) # 默认 fold 为 0即采用过渡前规则 dt paris.convert(dt) dt.isoformat() 2013-03-31T01:30:0001:00 dt datetime(2013, 3, 31, 2, 30, fold1) dt paris.convert(dt) dt.isoformat() 2013-03-31T03:30:0002:00对照 Timezone.convert() 的源码逻辑naive 输入会分别计算fold0与fold1两个 offset若判定为跳过时间且未开启raise_on_unknown_times就按dt.fold的方向调整到合法时刻——fold0时倒退 1 小时得到01:3001:00fold1时前进 1 小时得到03:3002:00。用 dst_rule 替代 fold不依赖fold时也可以直接传入dst_rule关键字参数。这在你想对不存在的时刻和歧义时刻主动抛错时尤其有用 import pendulum dt datetime(2013, 3, 31, 2, 30) # 默认 fold 为 0 dt paris.convert(dt, dst_rulependulum.PRE_TRANSITION) dt.isoformat() 2013-03-31T01:30:0001:00 dt paris.convert(dt, dst_rulependulum.POST_TRANSITION) dt.isoformat() 2013-03-31T03:30:0002:00 paris.convert(dt, dst_rulependulum.TRANSITION_ERROR) # NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist这一用法与 tests/tz/test_timezone.py 中tz.convert(dt, dst_rulependulum.POST_TRANSITION)/PRE_TRANSITION的断言完全一致可直接作为可靠参考。标准库加减 timedelta 的陷阱上述转换行为符合预期但一旦对结果加减timedelta问题就来了 from datetime import datetime, timedelta from pendulum import timezone dt datetime(2013, 3, 31, 1, 59, 59, 999999) dt paris.convert(dt) dt.isoformat() 2013-03-31T01:59:59.99999901:00 dt dt timedelta(microseconds1) dt.isoformat() 2013-03-31T02:00:0001:00标准库datetime的算术运算只做纯钟面加法01:00基础上钟面加 1 微秒不会感知 DST 过渡——结果02:00:0001:00实际对应一个不存在的本地时刻。期望值应该是2013-03-31T03:00:0002:00。解决办法很简单加减之后再调用一次convert()让 Pendulum 重新归一化 dt tz.convert(dt) dt.isoformat() 2013-03-31T03:00:0002:00这正是文档强调的timezone 库与 Pendulum 生态协同使用时过渡附近加减需二次 convert的核心原因也是 Timezone.convert() 对 aware datetime 走astimezone()分支、对 naive 走归一化分支的典型应用场景。从 Timezone 直接构造归一化 datetime最后也可以从Timezone对象通过datetime()方法直接得到一个已归一化的datetime import pendulum tz pendulum.timezone(Europe/Paris) dt tz.datetime(2013, 3, 31, 2, 30) dt.isoformat() 2013-03-31T03:30:0002:00从源码看Timezone.datetime() 内部以fold1构造标准库datetime后直接交给convert()归一化因此02:30这个不存在的时刻自动变为03:3002:00。FixedTimezone.datetime()src/pendulum/tz/timezone.py行为相同——固定 offset 时区没有 DST归一化只是绑定tzinfo。此外timezone(UTC)在模块加载时被预置为全局常量UTCsrc/pendulum/tz/timezone.py可通过pendulum.UTC直接引用。小结Pendulum 创建实例时自动归一化过渡时刻春季跳过的时间前移、秋季重复的时间默认取过渡后段可用dst_rulePRE_TRANSITION/POST_TRANSITION/TRANSITION_ERROR精确控制或对歧义/不存在时刻抛NonExistingTime/AmbiguousTime。在 Pendulum 生态内加减时间穿越过渡时刻是安全的行为可逆且一致。in_timezone()/in_tz()负责物理时刻不变的时区切换。直接配合标准库datetime使用时fold或dst_rule决定归一化方向加减timedelta后需再次convert()修正旧版 Python 3.6则应避免直接使用 timezone 库。时区处理没有银弹但理解归一化 fold dst_rule这套机制后你可以放心地把最棘手的 DST 边界交给 Pendulum。赞分享后端【免费下载链接】pendulumPython datetimes made easy项目地址https://gitcode.com/gh_mirrors/pe/pendulum点击查看免费下载相关推荐Luxon时区处理完全实战IANA时区、setZone与DST转换一站式搞定Luxon时区处理完全实战IANA时区、setZone与DST转换一站式搞定 Luxon 是一款为 JavaScript 打造的日期时间处理库其最大亮点就是开发工具Apache Airflow Cron 时间表 DST 转换日修复深入解析重复小时与折返Fold时段的调度语义Apache Airflow Cron 时间表 DST 转换日修复深入解析重复小时与折返Fold时段的调度语义 导读 本文围绕 Apache Airflo后端任务调度工作流自动化数据编排批处理数据工程流程编排Pendulum Interval 完全指南理解两个 DateTime 之间的时间区间Pendulum Interval 完全指南理解两个 DateTime 之间的时间区间 导读 Interval 是 Pendulum 中表示两个日期时间实例后端上一篇蓝奏云直链解析指南3 个参数把分享链接变成直链下一篇STIX Two Math 字体教程3 步解决论文公式排不齐的问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?