Skip to main content

🕰️ Clock Pattern

CI Pipeline Coverage Pipeline Package Version Supported Python Versions Package Downloads Project Documentation

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:

This project's DeepWiki documentation is also available for generated repository navigation.

🔼 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, audit fields, elapsed-time calculations.
today() date Calendar rules, billing days, holiday checks, date-only decisions.

🔼 Back to top



📚 Available Clocks

The package offers several clock implementations to suit different needs:

Clock 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.
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.
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.
FixedClock from clock_pattern.clocks.testing import FixedClock Test clock that always returns the same datetime and derived date.
MockClock from clock_pattern.clocks.testing import MockClock Test clock with prepared return values and call assertions.

Use the top-level package for production clocks and clock_pattern.clocks.testing for test-only clocks.

🔼 Back to top



🌍 Timezone Behavior

SystemClock accepts either an IANA timezone string or a tzinfo instance. It stores the timezone with ZoneInfo and uses it 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()

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clock_pattern-0.9.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clock_pattern-0.9.0-py3-none-any.whl (21.1 kB view details)

Uploaded Python 3

File details

Details for the file clock_pattern-0.9.0.tar.gz.

File metadata

  • Download URL: clock_pattern-0.9.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for clock_pattern-0.9.0.tar.gz
Algorithm Hash digest
SHA256 ff4f274b0827250e9179e37a466df0ff4d98354d28946eccf5c0985dd1efae32
MD5 9a34efe7c8abb29419785898bb8870c9
BLAKE2b-256 4c4dc894242e2ff1fd9631c2f5737c7c1325c5288309c2645e71b60a7bf9ec7c

See more details on using hashes here.

Provenance

The following attestation bundles were made for clock_pattern-0.9.0.tar.gz:

Publisher: ci.yaml on adriamontoto/clock-pattern

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clock_pattern-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: clock_pattern-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 21.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for clock_pattern-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 365140eddaa0afd95a196c30d0da3531f6e4f43e26718ba6cbc490f10ec45743
MD5 f3bf283a1dd1b8223e58de4989a298ae
BLAKE2b-256 30b39097b0610c0b48bb2eb397225978d5fb08b2a302c4a890cf7f2a6ef879c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for clock_pattern-0.9.0-py3-none-any.whl:

Publisher: ci.yaml on adriamontoto/clock-pattern

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page