GA Clock is a controllable Python clock with realtime, accelerated, fixed, scheduled, and manual time modes.
Project description
GA Clock
GA Clock provides a controllable datetime source and an internal-time scheduler for applications, simulations, and deterministic tests. A clock can follow wall time, accelerate it, advance in fixed or manual steps, or jump directly between scheduled events. The scheduler always uses the selected clock's internal time.
Installation
The PyPI distribution is named ga-clock; the import package is named ga_clock.
python -m pip install ga-clock
GA Clock has no runtime dependencies. Development and test tools are available as extras:
python -m pip install -e ".[test]"
python -m pip install -e ".[dev]"
Quick Start
from datetime import datetime
from ga_clock import Clock
clock = Clock.manual(start_at=datetime(2026, 1, 1, 9, 0))
clock.step(hours=2)
assert clock.now() == datetime(2026, 1, 1, 11, 0)
assert clock.elapsed_hours() == 2
When start_at is omitted, the clock starts at the current datetime. It also accepts
an ISO datetime string and an optional tzinfo object.
Clock Modes
| Mode | Internal-time behavior | step() behavior |
|---|---|---|
realtime |
Advances at wall-clock speed | Runs due jobs without moving time directly |
wrap |
Advances at factor * wall time |
Runs due jobs without moving time directly |
fixed |
Changes only when stepped | Advances by the configured fixed duration |
scheduled |
Changes only when stepped | Jumps to the next scheduled event |
manual |
Changes only when stepped | Advances by the supplied duration |
Realtime and Wrap
from ga_clock import Clock
realtime = Clock.realtime()
accelerated = Clock.wrap(factor=60)
A wrap factor of 60 advances internal time by one minute for every elapsed wall-time
second. Factors must be greater than zero.
Fixed and Manual
from datetime import timedelta
from ga_clock import Clock
fixed = Clock.fixed(step=timedelta(minutes=15))
fixed.step().step()
assert fixed.elapsed_minutes() == 30
manual = Clock.manual()
manual.step(minutes=5).step(seconds=30)
assert manual.elapsed_seconds() == 330
Scheduled
from datetime import datetime
from ga_clock import Clock
events: list[datetime] = []
clock = Clock.scheduled(start_at=datetime(2026, 1, 1, 9, 0))
clock.every().hour.do(lambda: events.append(clock.now()))
clock.step()
assert events == [datetime(2026, 1, 1, 10, 0)]
Calling step() with no jobs scheduled is a no-op.
Scheduling
The fluent API is inspired by schedule, but all configuration operations return new
objects instead of mutating earlier builder values.
from datetime import datetime
from ga_clock import Clock
calls: list[str] = []
clock = Clock.manual(start_at=datetime(2026, 1, 1, 8, 0))
heartbeat = clock.every(10).seconds.do(lambda: calls.append("heartbeat"))
report = clock.every().monday.at("10:00").do(
lambda: calls.append("report")
).tag("reports")
clock.step(seconds=10)
assert calls == ["heartbeat"]
clock.cancel(heartbeat)
clock.clear("reports")
assert clock.jobs() == []
Supported units are seconds, minutes, hours, days, weeks, months, and years. Weekday
properties from monday through sunday are also available. at() accepts:
:SSfor minute jobs;:MMor:MM:SSfor hourly jobs;HH:MMorHH:MM:SSfor daily and larger jobs.
Returning CancelJob removes a job immediately after it runs:
from ga_clock import CancelJob
def run_once() -> object:
return CancelJob
Elapsed Time
clock.elapsed() # datetime.timedelta
clock.elapsed_seconds() # float
clock.elapsed_minutes() # float
clock.elapsed_hours() # float
clock.elapsed_days() # float
clock.elapsed_months() # float
clock.elapsed_years() # float
clock.elapsed_values() # immutable Elapsed dataclass
Months and years are duration approximations based on the average Gregorian year of 365.2425 days; they are not calendar-boundary counts.
Errors
Invalid modes, factors, durations, schedule formats, and mode-specific operations raise
ClockError. It derives from both GAClockError and ValueError. Non-fatal package
warnings derive from GAClockWarning.
Scheduled job callbacks propagate their exceptions to the caller of step(),
run_pending(), or run_all(); GA Clock does not silently suppress callback failures.
Security
GA Clock does not deserialize data or load executable content. Scheduled callbacks are ordinary Python callables and execute with the permissions of the current process. Only schedule callbacks from trusted application code.
Development
GA Clock supports Python 3.10 and newer.
python -m pip install -e ".[dev]"
python -m compileall -q src
python -m pytest --cov=ga_clock --cov-report=term-missing
ruff check .
mypy
python -m pip check
python -m build
python -m twine check dist/*
Releases
src/ga_clock/_version.py is the only version source. To publish a release:
- Update
__version__in_version.pyand commit the release changes. - Push
mainand wait for CI to pass. - Configure the PyPI Trusted Publisher with project
ga-clock, ownerandreagemma, repositoryga-clock, workflowrelease.yml, and environmentpypi. - Run the Create release GitHub Actions workflow. With no override it creates the
v<version>tag, creates release notes, and explicitly dispatches the build and PyPI publication workflow.
PyPI versions are immutable. Increment _version.py before publishing different
content.
License
GA Clock is distributed under the MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ga_clock-0.2.0.tar.gz.
File metadata
- Download URL: ga_clock-0.2.0.tar.gz
- Upload date:
- Size: 15.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb398d331975e46ad9c526d6c612a47f88593657abb1cb43a2bae8a69202274c
|
|
| MD5 |
f908d4b9cb01f48b8d53b3b542dab9d2
|
|
| BLAKE2b-256 |
fb34bcc46857cd181e4afe0534461b1ef8cbf0e261ba22b3f787af537b27d8d4
|
Provenance
The following attestation bundles were made for ga_clock-0.2.0.tar.gz:
Publisher:
release.yml on andreagemma/clock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ga_clock-0.2.0.tar.gz -
Subject digest:
cb398d331975e46ad9c526d6c612a47f88593657abb1cb43a2bae8a69202274c - Sigstore transparency entry: 1935547569
- Sigstore integration time:
-
Permalink:
andreagemma/clock@caf32f8f7d1e490f0502489f1ceedc068bf6ed8c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/andreagemma
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@caf32f8f7d1e490f0502489f1ceedc068bf6ed8c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file ga_clock-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ga_clock-0.2.0-py3-none-any.whl
- Upload date:
- Size: 11.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
903c57aace34e6873e5b58933720b4359cbe514ab56d65f636841a8c752ab970
|
|
| MD5 |
2e92af61b0e433a3891f0b42dd43c18f
|
|
| BLAKE2b-256 |
4613495a8b5fea556b692ba9cfac9240665694f6667f5a7a7375e1e301044ef0
|
Provenance
The following attestation bundles were made for ga_clock-0.2.0-py3-none-any.whl:
Publisher:
release.yml on andreagemma/clock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ga_clock-0.2.0-py3-none-any.whl -
Subject digest:
903c57aace34e6873e5b58933720b4359cbe514ab56d65f636841a8c752ab970 - Sigstore transparency entry: 1935547622
- Sigstore integration time:
-
Permalink:
andreagemma/clock@caf32f8f7d1e490f0502489f1ceedc068bf6ed8c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/andreagemma
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@caf32f8f7d1e490f0502489f1ceedc068bf6ed8c -
Trigger Event:
workflow_dispatch
-
Statement type: