ProvenPin Install

Recurring schedules with dateutil rrule and zoneinfo DST handling, frozen with time-machine: working versions (tested 2026-10-15)

Build daily wall-clock schedules in a named timezone with dateutil rrule and zoneinfo, then test DST gaps, overlaps and naive datetimes offline with time-machine.

passed4 passed · 0 failed2026-10-15 13:28 UTC
Status
passed
Last passed
2026-10-15 13:28 UTC
Tests
4 / 4
Language
Python
Runtime
Python 3.13.16
pinned versions4
python-dateutil@2.9.0.post0six@1.17.0time-machine@3.5.1pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38742087302

The code

Exactly the files that passed. Your agent can pull them with get_recipe("dateutil-rrule-zoneinfo-time-machine") (install), or see them on GitHub.

requirements.txt
python-dateutil==2.9.0.post0
six==1.17.0
time-machine==3.5.1
pytest==9.1.1
schedule.py
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

from dateutil.rrule import DAILY, rrule

NY = ZoneInfo("America/New_York")

def daily_at(start: datetime, hour: int, minute: int) -> rrule:
    """Every day at hour:minute wall-clock time. start must be timezone-aware."""
    return rrule(DAILY, dtstart=start, byhour=hour, byminute=minute, bysecond=0)

def next_run(rule: rrule, now: datetime) -> datetime:
    return rule.after(now, inc=False)

def to_utc(dt: datetime) -> datetime:
    return dt.astimezone(timezone.utc)
tests/test_schedule.py
from datetime import datetime, timedelta, timezone

import pytest
import time_machine

from schedule import NY, daily_at, next_run, to_utc

START = datetime(2024, 3, 1, tzinfo=NY)

@time_machine.travel(datetime(2024, 3, 9, 17, tzinfo=timezone.utc), tick=False)
def test_wall_clock_preserved_across_spring_forward():
    # 09:00 stays 09:00 local; only the UTC offset changes (EST -5 to EDT -4)
    nxt = next_run(daily_at(START, 9, 0), datetime.now(NY))
    assert nxt.hour == 9
    assert nxt.utcoffset() == timedelta(hours=-4)

def test_nonexistent_local_time_uses_pre_gap_offset():
    # 02:30 does not exist on 2024-03-10; zoneinfo (fold=0) uses the offset before the gap,
    # so 02:30 EST = 07:30 UTC
    nxt = next_run(daily_at(START, 2, 30), datetime(2024, 3, 9, 12, tzinfo=NY))
    assert nxt.hour == 2
    assert to_utc(nxt) == datetime(2024, 3, 10, 7, 30, tzinfo=timezone.utc)

def test_ambiguous_time_fires_once_per_day():
    # 01:30 happens twice on 2024-11-03; rrule yields one occurrence per day, fold=0 (EDT)
    runs = daily_at(START, 1, 30).between(datetime(2024, 11, 2, tzinfo=NY), datetime(2024, 11, 4, tzinfo=NY))
    assert len(runs) == 2
    assert runs[1].utcoffset() == timedelta(hours=-4)

def test_naive_dtstart_is_rejected():
    rule = daily_at(datetime(2024, 3, 1), 9, 0)
    with pytest.raises(TypeError):
        next_run(rule, datetime(2024, 3, 9, tzinfo=NY))
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.