Skip to content

Latest commit

Β 

History

History
316 lines (218 loc) Β· 12.4 KB

File metadata and controls

316 lines (218 loc) Β· 12.4 KB

πŸ•°οΈ 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

πŸ”Ό 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.
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