
Photo by Aron Visuals on Unsplash
Zeitwerkzeug – Contextual Time Scheduling for Python
Time is a tool. Most schedulers treat it as a fixed timestamp – a rigid point on a line. But in the real world, time changes depending on where you are, who you are, and what's happening.
Today I'm excited to announce the alpha release of Zeitwerkzeug (German for "time tool") – a Python library that models time through:
☀️ Solar geometry – sunrise, sunset, golden hour, and custom angles
🧑💼 Human personas – wake/sleep rhythms, weekend shifts, proportional time blocks
🌦️ Environmental conditions – weather, sun altitude, and logical combinators
⚡ Async execution – drifting schedules that recalibrate over time
It's built for automation, IoT, and context‑aware scheduling – anywhere you need to adapt to changing conditions.
The Problem with Clock‑Based Scheduling
Most scheduling systems assume time is absolute. You tell them: "Run at 7:00 AM every day." But what if you want to water your garden when the sun rises, but only if it's not cloudy? Or what if you need to schedule tasks around a night‑shift worker's sleep cycle?
Traditional cron jobs can't handle:
- Solar‑dependent events (dawn, dusk) that shift daily.
- Human rhythms (wake/sleep) that vary on weekends.
- Live conditions (weather, visibility) that determine if a task makes sense.
Zeitwerkzeug solves this by treating time as something that can be resolved lazily – a schedule is defined as a target (sunrise, 2 hours after waking, etc.) and then evaluated at runtime with real context.
How It Works
1. Solar‑Based Triggers
Define a schedule that follows the sun:
from zeitwerkzeug import Location, schedule, SolarEvent
location = Location(lat=34.6937, lon=135.5020, timezone="Asia/Tokyo")
# Daily sunrise trigger
trigger = schedule.at(SolarEvent.SUNRISE, location=location)
You can also use golden hour, sunset, or custom angles:
from zeitwerkzeug.astro import SolarAngle
golden_hour = SolarAngle(altitude=-4.0, rising=True)
trigger = schedule.at(golden_hour, location=location)
2. Human Personas
Model a person's daily rhythm:
from zeitwerkzeug.personas import StandardWorker
from datetime import timedelta
persona = StandardWorker(tz="Asia/Tokyo")
# Run 2 hours after waking
trigger = schedule.at(
lambda t: persona.wake_datetime(t) + timedelta(hours=2)
)
Built‑in personas: StandardWorker, NightShift. You can also create custom profiles.
3. Context‑Aware Conditions
Add conditions that must be true before execution:
from zeitwerkzeug import SunAltitudeAbove, TimeWindow, All
from datetime import time
location = Location(lat=34.6937, lon=135.5020, timezone="Asia/Tokyo")
trigger = schedule.at(SolarEvent.SUNSET, location=location).require(
All((SunAltitudeAbove(location, min_altitude=-6.0), # civil twilight
TimeWindow(start=time(18, 0), end=time(23, 0), tz="Asia/Tokyo"),))
)
4. Weather Integration (Open‑Meteo)
Check cloud cover before watering:
from zeitwerkzeug import ClearWeather
trigger = schedule.at(SolarEvent.SUNRISE, location=location).require(
ClearWeather(lat=location.lat, lon=location.lon, max_cloud_cover=40)
)
The free tier is rate‑limited with built‑in safety margins. Commercial API keys are also supported.
5. Retry Policies
Handle failures gracefully:
from datetime import timedelta
trigger = schedule.at(SolarEvent.SUNRISE, location=location).on_fail(
retry_interval=timedelta(minutes=15),
max_attempts=3,
limit=timedelta(hours=2),
)
6. Async Execution Loop
The ExecutionLoop runs the scheduler asynchronously:
from zeitwerkzeug import FuzzyCron, ExecutionLoop
from datetime import UTC, datetime
cron = FuzzyCron()
cron.register(water_plants, trigger)
loop = ExecutionLoop(registry=cron, max_concurrency=5)
await loop.run(until=datetime(2026, 1, 1, tzinfo=UTC))
It automatically recalibrates at midnight, handles concurrency, and keeps a history of executions.
Real‑World Example: Garden Irrigation
Here's a complete snippet that waters the garden at sunrise, but only if cloud cover is under 40%:
#!/usr/bin/env python3
import asyncio
from datetime import timedelta
from zeitwerkzeug import (
Location, schedule, FuzzyCron, ExecutionLoop,
SolarEvent, All, SunAltitudeAbove, ClearWeather,
)
LOCATION = Location(lat=34.6937, lon=135.5020, timezone="Asia/Tokyo")
def water_plants(ctx):
print(f"💧 Watering at {ctx.triggered_at} (attempt {ctx.attempt})")
async def main():
trigger = (
schedule.at(SolarEvent.SUNRISE, location=LOCATION)
.require(
All((ClearWeather(lat=LOCATION.lat, lon=LOCATION.lon, max_cloud_cover=40),
SunAltitudeAbove(LOCATION, min_altitude=-6.0),))
)
.on_fail(
retry_interval=timedelta(minutes=15),
max_attempts=3,
limit=timedelta(hours=2),
)
)
cron = FuzzyCron()
cron.register(water_plants, trigger)
loop = ExecutionLoop(registry=cron)
await loop.run()
if __name__ == "__main__":
asyncio.run(main())
Installation
pip install zeitwerkzeug
For weather:
pip install "zeitwerkzeug[weather]"
Project Status
Alpha release (v0.0.3) – the API is stable for experiments, but breaking changes may occur before 1.0. We're looking for early adopters to test use cases and provide feedback.
- GitHub: https://github.com/bidyut18/zeitwerkzeug
- PyPI: https://pypi.org/project/zeitwerkzeug/
- Documentation: (coming soon – for now, see the README and examples)
Top comments (0)