---
title: "定时任务遇到夏令时：识别重复与不存在的本地时间"
date: 2026-11-13
slug: timezone-dst-fold-gap
canonical: https://kernelranch.com/blog/2026/11/timezone-dst-fold-gap
markdown_url: https://kernelranch.com/blog/2026/11/timezone-dst-fold-gap.md
tags: ["python", "timezone", "scheduling"]
---仓库在纽约安排凌晨执行任务。春季切换时，02:30 可能根本不存在。秋季切换时，01:30 可能出现两次。解析用户输入时先枚举可能的 UTC 时刻，再按产品约定处理零个或两个候选。

## 1. 用三条输入验证候选数

保存为 dst_lab.py，执行 python dst_lab.py。运行环境需要可用的 IANA 时区数据。缺失时，可以给部署环境添加 tzdata 依赖。

```python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

def utc_candidates(wall, zone):
    if wall.tzinfo is not None:
        raise ValueError("expected a naive local time")
    found = set()
    for fold in (0, 1):
        aware = wall.replace(tzinfo=zone, fold=fold)
        utc = aware.astimezone(timezone.utc)
        restored = utc.astimezone(zone).replace(tzinfo=None)
        if restored == wall:
            found.add(utc)
    return sorted(found)

ny = ZoneInfo("America/New_York")
examples = [
    ("2024-03-10T02:30", []),
    ("2024-11-03T01:30", ["05:30", "06:30"]),
    ("2024-11-03T03:30", ["08:30"]),
]
for text, expected in examples:
    candidates = utc_candidates(datetime.fromisoformat(text), ny)
    actual = [value.strftime("%H:%M") for value in candidates]
    assert actual == expected
    print(text, "UTC candidates:", actual)
```

本地实测输出如下。

```text
2024-03-10T02:30 UTC candidates: []
2024-11-03T01:30 UTC candidates: ['05:30', '06:30']
2024-11-03T03:30 UTC candidates: ['08:30']
```

## 2. 在创建任务时定义边界行为

| 候选数 | 含义 | 可以采用的契约 |
|---|---|---|
| 0 | 该本地时刻落在时钟向前跳过的区间 | 请用户改时间，或明确顺延规则 |
| 1 | 对应唯一执行时间 | 保存 UTC 时刻 |
| 2 | 回拨造成重复 | 由用户选较早或较晚时刻，并显示 UTC 偏移 |

直接绑定 tzinfo 可以构造带时区对象，但构造成功未验证这个本地时刻真实存在。实验对两个 fold 都做 UTC 往返，剔除回到不同墙上时间的候选，并在 UTC 上去重。

一次预约可以落成 UTC 时刻。对于「每天当地 09:00」，还要持久化当地时区和重复规则，再为每个日期计算执行时间。按上次 UTC 时间加 24 小时，跨夏令时后可能改变当地时刻。

## 小结

界面接受本地时间时，把时区和重复时刻的选择一并纳入输入。测试至少包含春季缺口、秋季回拨和一个普通时刻。

## 数据来源与检索时间

检索时间 2026-11-13。

| 来源 | URL | 对应内容 |
|---|---|---|
| Python zoneinfo | https://docs.python.org/3.11/library/zoneinfo.html | IANA 数据来源与 fold |
| PEP 495 | https://peps.python.org/pep-0495/ | 本地时间歧义及缺失时间 |

## 核验说明

- Python 3.11.9 实测纽约三个历史时刻。业务的顺延和重复触发规则需单独约定。
