The Clock Pattern is a Python π package that turns time into an injectable dependency π§©. Instead of scattering
datetime.now() or date.today() through application code, domain services depend on a small Clock interface. That
keeps time-sensitive logic deterministic in tests, makes timezone choices explicit, and lets production code swap clock
implementations without touching business rules.
- π₯ Installation
- π Documentation
- β‘ Quick Start
- π§© Why Inject a Clock?
- π Public API
- π Timezone Behavior
- π§ͺ Testing Time-Sensitive Code
- π Real-Life Case: Christmas Detector Service
- π€ Contributing
- π License
You can install Clock Pattern using pip:
pip install clock-patternYou can install the companion AI-agent skill from skills.sh with Vercel's skills CLI:
npx skills add adriamontoto/clock-patternReview the skill source in skills/clock-pattern before installing it in sensitive
environments.
The root README is the entry point. Deeper guides live in this repository and are linked here:
docs/README.md: Documentation hub.docs/usage/README.md: Core usage patterns and service composition.docs/timezones/README.md: Timezone behavior, UTC defaults, and date-boundary guidance.docs/examples/README.md: Token expiration, cache TTL, polling, and retry recipes.docs/testing/README.md:FixedClock,MockClock, and deterministic test patterns.
Inject a Clock into code that needs the current time. Production code can pass a real clock, while tests can pass a
fixed or mock clock.
from clock_pattern import Clock, UtcClock
class TimestampService:
def __init__(self, *, clock: Clock) -> None:
self._clock = clock
def issued_at(self) -> str:
return self._clock.now().isoformat()
service = TimestampService(clock=UtcClock())
print(service.issued_at())Use SystemClock when
you need a specific timezone:
from clock_pattern import SystemClock
clock = SystemClock(timezone='Europe/Madrid')
print(clock.now())
# >>> 2025-06-16 15:57:26.210964+02:00Time is global state. Reading it directly from the operating system makes behavior depend on the moment a test happens to run, the machine timezone, daylight-saving transitions, and the speed of the test suite.
Clock Pattern keeps those decisions explicit:
- Domain code depends on
Clock, not on Python's global datetime functions. - Tests can choose exact dates and datetimes without monkeypatching built-in modules.
- Production wiring decides whether the application uses UTC or another timezone.
- Custom clocks can be introduced for logical time, simulation, replay, or high-precision infrastructure.
The package exposes two methods:
| Method | Returns | Typical use |
|---|---|---|
now() |
datetime |
Timestamps, expiration windows, and audit fields. |
today() |
date |
Calendar rules, billing days, holiday checks, date-only decisions. |
Clock Pattern also includes injectable helpers for elapsed-duration behavior: monotonic clocks, sleepers, stopwatches, deadlines, pollers, and retriers. These use monotonic seconds instead of wall-clock datetimes so system clock changes do not affect timeout or retry behavior.
Use the top-level package for contracts and production helpers, and each feature's testing package for test doubles.
| API | Import path | Purpose |
|---|---|---|
Clock |
from clock_pattern import Clock |
Abstract contract for code that needs now() or today(). |
SystemClock |
from clock_pattern import SystemClock |
Production clock backed by system time in a configured timezone. |
UtcClock |
from clock_pattern import UtcClock |
Production clock fixed to UTC. |
| API | Import path | Purpose |
|---|---|---|
MonotonicClock |
from clock_pattern import MonotonicClock |
Abstract contract for elapsed-time sources. |
SystemMonotonicClock |
from clock_pattern import SystemMonotonicClock |
Production monotonic clock for elapsed-time measurement. |
Sleeper / SleeperAsync |
from clock_pattern import Sleeper, SleeperAsync |
Abstract contracts for injectable sync and async sleeping. |
SystemSleeper / SystemSleeperAsync |
from clock_pattern import SystemSleeper, SystemSleeperAsync |
Injectable sync and async sleeping. |
Stopwatch |
from clock_pattern import Stopwatch |
Measure elapsed seconds with .start(), .end(), or a context manager. |
Deadline |
from clock_pattern import Deadline |
Abstract contract for injectable deadline state. |
SystemDeadline |
from clock_pattern import SystemDeadline |
Monotonic deadline with an interrupting Unix main-thread context. |
TimeoutExpiredError |
from clock_pattern import TimeoutExpiredError |
Error raised when a deadline or poll timeout expires. |
Poller / PollerAsync |
from clock_pattern import Poller, PollerAsync |
Abstract contracts for condition polling. |
SystemPoller / SystemPollerAsync |
from clock_pattern import SystemPoller, SystemPollerAsync |
Production polling implementations. |
Retrier / RetrierAsync |
from clock_pattern import Retrier, RetrierAsync |
Abstract contracts for retrying operations. |
SystemRetrier / SystemRetrierAsync |
from clock_pattern import SystemRetrier, SystemRetrierAsync |
Production retry implementations. |
| API | Import path | Purpose |
|---|---|---|
FixedClock |
from clock_pattern.clocks.testing import FixedClock |
Test clock that stays fixed until explicitly set or advanced. |
MockClock |
from clock_pattern.clocks.testing import MockClock |
Test clock with prepared return values and call assertions. |
MockMonotonicClock |
from clock_pattern.monotonic_clocks.testing import MockMonotonicClock |
Controllable elapsed-time source with call assertions. |
MockDeadline |
from clock_pattern.deadlines.testing import MockDeadline |
Controllable deadline with expiry call assertions. |
MockSleeper / MockSleeperAsync |
from clock_pattern.sleepers.testing import MockSleeper, MockSleeperAsync |
Sleeping test doubles that advance a mock monotonic clock. |
MockPoller / MockPollerAsync |
from clock_pattern.pollers.testing import MockPoller, MockPollerAsync |
Polling test doubles with call assertions. |
MockRetrier / MockRetrierAsync |
from clock_pattern.retriers.testing import MockRetrier, MockRetrierAsync |
Retry test doubles with prepared results. |
from clock_pattern import Stopwatch, SystemMonotonicClock, SystemSleeper
monotonic_clock = SystemMonotonicClock()
sleeper = SystemSleeper(monotonic_clock=monotonic_clock)
with Stopwatch(monotonic_clock=monotonic_clock) as stopwatch:
pass
with sleeper.minimum_duration(seconds=0.001):
passThe deadline context requires a Unix main thread:
from clock_pattern import SystemDeadline, SystemMonotonicClock
monotonic_clock = SystemMonotonicClock()
with SystemDeadline(seconds=5, monotonic_clock=monotonic_clock):
passContinue with polling and retries on all platforms:
from clock_pattern import SystemMonotonicClock, SystemPoller, SystemRetrier, SystemSleeper
monotonic_clock = SystemMonotonicClock()
sleeper = SystemSleeper(monotonic_clock=monotonic_clock)
poller = SystemPoller(sleeper=sleeper, monotonic_clock=monotonic_clock)
poller.poll_until(condition=lambda: True, timeout_seconds=5, interval_seconds=0.1)
SystemRetrier(sleeper=sleeper).retry(
operation=lambda: 'done',
attempts=3,
delay_seconds=0.2,
max_delay_seconds=2,
backoff=2,
jitter=True,
)SystemDeadline context managers use SIGALRM to interrupt Python code and interruptible system calls. Context use is
limited to Unix main-thread execution, cannot be nested or share an existing alarm, and may be delayed by C code that
does not return control to the Python interpreter. Deadline properties and raise_if_expired() remain cooperative when
used outside a context manager. TimeoutExpiredError.elapsed_seconds exposes the measured elapsed duration reported by
either timeout path.
SystemClock accepts either an IANA timezone string or a tzinfo instance. It converts strings to ZoneInfo, preserves
tzinfo instances directly, and uses the resulting timezone for both now() and today().
from datetime import UTC
from clock_pattern import SystemClock
utc_clock = SystemClock(timezone=UTC)
madrid_clock = SystemClock(timezone='Europe/Madrid')
print(utc_clock.timezone)
# >>> UTC
print(madrid_clock.timezone)
# >>> Europe/MadridUtcClock is a convenience clock for the common production choice of UTC.
today() is calculated in the clock timezone. Around midnight, SystemClock(timezone='UTC').today() and
SystemClock(timezone='America/New_York').today() may return different dates. For more details, see
docs/timezones/README.md.
Use FixedClock when the test only needs a stable instant:
from datetime import datetime
from clock_pattern.clocks.testing import FixedClock
clock = FixedClock(instant=datetime(year=2025, month=1, day=1, hour=10, minute=30))
assert clock.now().isoformat() == '2025-01-01T10:30:00+00:00'
assert clock.today().isoformat() == '2025-01-01'Use MockClock when the test also needs to prove that time was requested:
from datetime import date
from clock_pattern.clocks.testing import MockClock
clock = MockClock()
clock.prepare_today_method_return_value(today=date(year=2025, month=1, day=7))
assert clock.today() == date(year=2025, month=1, day=7)
clock.assert_today_method_was_called_once()
clock.assert_now_method_was_not_called()FixedClock.advance(delta=timedelta(...)) moves time forward explicitly; set(instant=...) replaces it, including backward jumps. Both keep now() and today() consistent.
More testing recipes are available in docs/testing/README.md.
This service checks whether the current date falls within a Christmas holiday range. The service depends on Clock, so
production code can use UtcClock
and tests can use MockClock
without changing the service.
from datetime import date
from clock_pattern import Clock, UtcClock
from clock_pattern.clocks.testing import MockClock
class ChristmasDetectorService:
def __init__(self, *, clock: Clock) -> None:
self._clock = clock
self._christmas_start = date(year=2024, month=12, day=24)
self._christmas_end = date(year=2025, month=1, day=6)
def is_christmas(self) -> bool:
return self._christmas_start <= self._clock.today() <= self._christmas_end
clock = UtcClock()
service = ChristmasDetectorService(clock=clock)
print(service.is_christmas())
# >>> False
def test_christmas_detector_is_christmas() -> None:
clock = MockClock()
service = ChristmasDetectorService(clock=clock)
today = date(year=2024, month=12, day=25)
clock.prepare_today_method_return_value(today=today)
assert service.is_christmas() is True
clock.assert_today_method_was_called_once()
def test_christmas_detector_is_not_christmas() -> None:
clock = MockClock()
service = ChristmasDetectorService(clock=clock)
today = date(year=2025, month=1, day=7)
clock.prepare_today_method_return_value(today=today)
assert service.is_christmas() is False
clock.assert_today_method_was_called_once()We love community help! Before you open an issue or pull request, please read:
Thank you for helping make π°οΈ Clock Pattern package awesome! π
This project is licensed under the terms of the MIT license.