Skip to main content

🕰️ Clock Pattern

CI Pipeline Coverage Pipeline Package Version Supported Python Versions Package Downloads

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.

Table of Contents

🔼 Back to top



📥 Installation

You can install Clock Pattern using pip:

pip install clock-pattern

You can install the companion AI-agent skill from skills.sh with Vercel's skills CLI:

npx skills add adriamontoto/clock-pattern

Review the skill source in skills/clock-pattern before installing it in sensitive environments.

🔼 Back to top



📚 Documentation

The root README is the entry point. Deeper guides live in this repository and are linked here:

🔼 Back to top



⚡ Quick Start

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:00

🔼 Back to top



🧩 Why Inject a Clock?

Time 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.

🔼 Back to top



📚 Public API

Use the top-level package for contracts and production helpers, and each feature's testing package for test doubles.

Wall-Clock API

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.

Elapsed-Time API

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.

Test Doubles

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):
    pass

The deadline context requires a Unix main thread:

from clock_pattern import SystemDeadline, SystemMonotonicClock

monotonic_clock = SystemMonotonicClock()
with SystemDeadline(seconds=5, monotonic_clock=monotonic_clock):
    pass

Continue 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.

🔼 Back to top



🌍 Timezone Behavior

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/Madrid

UtcClock 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.

🔼 Back to top



🧪 Testing Time-Sensitive Code

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.

🔼 Back to top



🎄 Real-Life Case: Christmas Detector Service

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()

🔼 Back to top



🤝 Contributing

We love community help! Before you open an issue or pull request, please read:

Thank you for helping make 🕰️ Clock Pattern package awesome! 🌟

🔼 Back to top



🔑 License

This project is licensed under the terms of the MIT license.

🔼 Back to top

Metadata

Release files for clock-pattern 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clock-pattern 0.11.0
File Size Uploaded
clock_pattern-0.11.0.tar.gz 24.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clock-pattern 0.11.0
File Interpreter ABI Platform
clock_pattern-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 77.8 kB

Release files / clock_pattern-0.11.0.tar.gz

Download URL clock_pattern-0.11.0.tar.gz
Size 24.9 kB
Tags Source
SHA-256 checksum
How to use checksums
8381d88c49214b8ae30ae2ee6f0953ee98a915aaaefadca83bdff3f2ec32f045
BLAKE2b-256 checksum
How to use checksums
0cc79b48a8804d43c52392e529ba3a361b51ac7bce231bb06be4bde0281e9681
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / clock_pattern-0.11.0-py3-none-any.whl

Download URL clock_pattern-0.11.0-py3-none-any.whl
Size 52.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
58c08c1bc687a0246784e48e733087343277d68e247adc83a255a3d9183f1677
BLAKE2b-256 checksum
How to use checksums
936ee39f5effec3055ddbb0bb030d8de2f8616efa624b19bf976627bb104031c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.11.0 This release

2 release files

0.9.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page