workclock
Fast, correct business-time arithmetic (working hours, SLAs, shifts) for Rust, Python and JavaScript — a maintained replacement for businesstimedelta, with a compatibility layer for existing code.
Status: pre-1.0; the API may still change in a minor release.
Install
cargo add workclock jiff
pip install workclock
uv add workclock
npm i workclock
The Rust API takes and returns jiff 0.2 types, so Rust code depends on jiff too.
Requirements
- Rust: 1.85 or later.
- Python: CPython 3.9 or later. The wheels use the stable ABI (abi3), one per platform: Linux x86_64 and aarch64 (manylinux and musllinux), macOS x86_64 and arm64, and Windows x64. Free-threaded CPython cannot load abi3 wheels. Other platforms build from the sdist, which needs a Rust toolchain.
- JavaScript: Node.js 22 or later. Browsers load the package through a
bundler, or without one from
workclock/web.
Time zone data
The Python wheels and the npm package bundle the IANA time zone database, so they need no system tzdata. The npm package always uses its bundled copy, whose zone rules are as current as the workclock release. The Python wheels use the system database when there is one, and the bundled copy otherwise.
The Rust crate uses jiff's defaults: the system database
(/usr/share/zoneinfo, or the directory in TZDIR) on Linux and macOS, and
a bundled copy on Windows. On a Linux image without tzdata, as some slim and
distroless images are, every named zone, even "UTC", is an unknown-zone
error. Install the tzdata package, point TZDIR at a zoneinfo directory,
or enable jiff's tzdb-bundle-always feature to bundle the database.
Use cases
- Support-ticket SLAs: when is a ticket due, and how much of its SLA has elapsed?
- Payroll and overtime: how many hours of a shift fell inside contracted time?
- Staff rosters, including overnight shifts.
- Delivery ETAs that only count operating hours.
Quickstart
A support desk in London works 09:00–17:00 on weekdays with a lunch hour, and is closed on 25 and 28 December 2026. A ticket opened at 16:30 on Christmas Eve with an 8-hour SLA is due at 09:30 on 30 December.
Rust
use jiff::civil::{date, time};
use jiff::{SignedDuration, Zoned};
use workclock::{Break, Holidays, Schedule, Weekdays, WorkDay};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let support = Schedule::builder()
.time_zone("Europe/London")
.rule(WorkDay::new(time(9, 0, 0, 0), time(17, 0, 0, 0), Weekdays::MON_TO_FRI))
.rule(Break::new(time(12, 0, 0, 0), time(13, 0, 0, 0), Weekdays::MON_TO_FRI))
.rule(Holidays::new([date(2026, 12, 25), date(2026, 12, 28)]))
.build()?;
let opened: Zoned = "2026-12-24T16:30[Europe/London]".parse()?;
let due = support.add(&opened, SignedDuration::from_hours(8))?;
assert_eq!(due.to_string(), "2026-12-30T09:30:00Z");
let now: Zoned = "2026-12-29T11:00[Europe/London]".parse()?;
assert_eq!(support.between(&opened, &now), SignedDuration::from_mins(150));
Ok(())
}
API documentation is on docs.rs.
Python
from datetime import date, datetime, time
from zoneinfo import ZoneInfo
from workclock import Break, Holidays, Schedule, WorkDay
london = ZoneInfo("Europe/London")
support = Schedule(
[
WorkDay(time(9), time(17)), # Monday to Friday by default
Break(time(12), time(13)),
Holidays([date(2026, 12, 25), date(2026, 12, 28)]),
],
time_zone=london,
)
opened = datetime(2026, 12, 24, 16, 30, tzinfo=london)
support.add(opened, hours=8) # 2026-12-30 09:30, London time
support.between(opened, datetime(2026, 12, 29, 11, tzinfo=london)) # 2:30:00
between_many, add_many and sub_many take lists or NumPy arrays and
process them in one call. Most code written for businesstimedelta runs
unchanged with import workclock.compat as businesstimedelta;
MIGRATING.md
lists where it differs.
JavaScript
import { Schedule } from "workclock";
const support = new Schedule({
timeZone: "Europe/London",
rules: [
{ type: "workday", start: "09:00", end: "17:00" },
{ type: "break", start: "12:00", end: "13:00" },
{ type: "holidays", dates: ["2026-12-25", "2026-12-28"] },
],
});
support.add("2026-12-24T16:30:00Z", 8 * 3_600_000); // Date 2026-12-30T09:30:00.000Z
support.between("2026-12-24T16:30:00Z", new Date("2026-12-29T11:00:00Z")); // 9000000 ms
Instants are Dates or ISO 8601 strings with an offset; durations are
milliseconds. In browsers without a bundler, import the default init from
workclock/web and await init() first.
More complete programs are in
crates/workclock/examples,
examples/python and
js/examples.
The errors each binding raises are listed in
DESIGN.md.
Why workclock
Correct
businesstimedelta 1.0.1 has had no release since 2019. workclock fixes:
- Overnight shifts. A 22:00–06:00 shift counts 0 hours between Monday 22:00 and Tuesday 06:00, and adding 4 hours at Monday 22:00 lands on Wednesday. workclock gives 8 hours and Tuesday 02:00.
- Crashes past the last holiday
(#8,
#10):
TypeErroronce a query passes the last date in a holiday list. - Infinite loops (#9) when holidays and working hours use different time zones. Every workclock search is bounded.
- Signed differences (#12):
Schedule.between(a, b)is negative whenbis beforea. - DST. Working hours are wall-clock times in each rule's IANA zone, and ambiguous or skipped times resolve the way jiff, RFC 5545 and JavaScript Temporal do.
Every rule is specified with examples in DESIGN.md, and each binding is checked against a brute-force oracle with property-based tests.
Fast
Medians on a 2017 laptop (Intel i5-8250U, Windows 11), from Python:
| Case | businesstimedelta 1.0.1 | workclock | Speed-up |
|---|---|---|---|
| One call, a 5½-hour span | 385 µs | 8.5 µs | 45× |
| One call, a year with bank holidays | 134 ms | 163 µs | 820× |
| 1,000,000 pairs | 22.5 min | 2.92 s (between_many on NumPy arrays) |
460× |
The same single call takes 1.7 µs from Rust and 2.2 µs from JavaScript, and
16 µs through workclock.compat, which keeps the schedule it builds from
businesstimedelta's mutable rules until their attributes change.
benchmarks/RESULTS.md
has every case with p95s, and
DESIGN.md
explains where the time goes.
Prior art & credits
workclock ports the test suite and the API of
businesstimedelta by
Sebastiaan Boer, released under the MIT license. Its last release, 1.0.1,
was in September 2019, and issues
#3,
#8,
#9,
#10 and
#12 remain open.
The ported API is workclock.compat, and the ported tests run against it in
CI. The original copyright notice is kept in
NOTICE.
License
Metadata
Release files for workclock 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| workclock-0.1.1.tar.gz | 46.6 kB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| workclock-0.1.1-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| workclock-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ x86-64 | Details |
| workclock-0.1.1-cp39-abi3-musllinux_1_2_aarch64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ ARM64 | Details |
| workclock-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| workclock-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| workclock-0.1.1-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| workclock-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 4.2 MB
Release files / workclock-0.1.1.tar.gz
| Download URL | workclock-0.1.1.tar.gz |
|---|---|
| Size | 46.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
349be14e6fb2c1de55d1310d45379b47bb3e1cae551c55651f7e8b0549b6d547
|
|
BLAKE2b-256 checksum How to use checksums |
9acd43dd47fe53f1695907afe3984fce3b2f73bbb29ee904793da97b58e80dcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-win_amd64.whl
| Download URL | workclock-0.1.1-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 412.3 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7c568b081c9bf6446032bb33f02ef65388a7b33be6d3357224d3e6d327fd7ed7
|
|
BLAKE2b-256 checksum How to use checksums |
cdf072a2fd42a0862ed4209c10f310bd443cb66dea5d3ace532274d2e47c7cf4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | workclock-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 783.1 kB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
038c48f13577ed7fcc7e8ea600333d3aac5f1ac0605fba2885cd5b663cf616b9
|
|
BLAKE2b-256 checksum How to use checksums |
cc386cc93be6b895e50befdad5abd6e066734fdbdba10b676107047791d91ead
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-musllinux_1_2_aarch64.whl
| Download URL | workclock-0.1.1-cp39-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 759.5 kB |
| Tags | CPython 3.9 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
fb88e452855d9ffee933978ce11555ced8c5392e6c45764aebf535f212778733
|
|
BLAKE2b-256 checksum How to use checksums |
de502eeadef40332ff0f03a26efe7f701b5fed94473487d9585848ab2638d514
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | workclock-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 577.6 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c238020b7ea9bae6e6909d083f0c83b9a8b773dbb8fb52ce64b9c4a1c57ce906
|
|
BLAKE2b-256 checksum How to use checksums |
989aa7ec3dc3f6cfb4e6f15b4a370fe03ec90aaabec87b174c95a6e24b4cf25a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | workclock-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 580.8 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
cb3285fb23d2f4ef821934ded335b7501a42540fb6705065ceddc67869f91b0d
|
|
BLAKE2b-256 checksum How to use checksums |
ad5ceb7f4aaeee638e7c338385b9f40e5b8a0a1d14a75d15946bee24bbb0f653
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | workclock-0.1.1-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 529.0 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cf19f310535ae7d326f29dcf40f3b657574a509efb08b60e0c42991fb0c32713
|
|
BLAKE2b-256 checksum How to use checksums |
23c885d91830d99ad5d7b08b97034ac36d1a2073bf8d26d5ed97c8e0516c1f8a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / workclock-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | workclock-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 518.7 kB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
7a5645be24ca4d0a0a84013739d0f93d9e8bb05f4895d4d10eda926002ecb65a
|
|
BLAKE2b-256 checksum How to use checksums |
494931ebf2ff9aa04d5a4ce01409d82b070b82ad36714765bd4409c8d9045611
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logSecurity for workclock 0.1.1
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.
Full provenance
All 8 files in this release have been uploaded with provenance metadata.
Attestation for workclock-0.1.1.tar.gz
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 Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.