Add 8 working hours.
Land on Wednesday at 09:30.
Fast, correct business-time arithmetic for Rust, Python and JavaScript.
cargo add workclock jiffpip install workclocknpm i workclock
- Thu 24
- Fri 25
- Sat–Sun
- Mon 28
- Tue 29
- Wed 30
Working hours 09:00–17:00, break 12:00–13:00. Times in Europe/London.
Due 09:30 on Wednesday 30 December 2026 (Europe/London)
support.add("2026-12-24T16:30:00Z", 8 * 3_600_000)Loading workclock…
Same answer in Rust, Python and JavaScript
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(())
}These three samples run in CI as written: the README doctest in crates/workclock-wasm/src/lib.rs, tests/python/test_readme.py and js/test/readme.test.ts.
How you can check it
Every rule and edge case is specified with an example and an ID.
The Rust, Python and JavaScript test suites all run the same file of examples, so the bindings cannot disagree on them.
Each binding is compared with a brute-force oracle on random schedules: proptest (Rust), Hypothesis (Python), fast-check (JavaScript).
oracle_properties.rs,test_properties.py,properties.test.tsworkclock.compatis tested against businesstimedelta 1.0.1 where 1.0.1 is correct, and against the oracle everywhere.businesstimedelta’s own test suite runs against
workclock.compatin CI.Each upstream bug it fixes has a test: issues 8, 9, 10 and 12, and overnight shifts.
CI fails if line coverage of the Rust crate drops below 90%.
ci.yml, jobcoverageTested on Linux, macOS and Windows; Python 3.9 and 3.15; Node.js 22, 24 and 26; Rust 1.85 checked separately.
ci.yml, jobstest,python-test,wasm,msrvBenchmarks compare every binding with businesstimedelta 1.0.1, with medians, p95s, date and machine.
The npm package is published with provenance, so npm shows which workflow run built it.
release.yml, jobnpmPyPI packages are published from GitHub Actions with trusted publishing, without a stored token.
release.yml, jobpypi; Release ProcessEvery GitHub Action is pinned to a commit SHA.
MIT licence. Rust 1.85+, CPython 3.9+, Node.js 22+.
Benchmarks
| Implementation | Median |
|---|---|
| Rust | 1.19 µs |
| JavaScript | 1.77 µs |
| Python | 3.96 µs |
| JavaScript, standard library (Temporal) | 9.06 µs |
| Python, standard library (zoneinfo) | 4.01 µs |
| Python, businesstimedelta 1.0.1 | 169 µs |
Measured on in a GitHub Actions run, on AMD EPYC 7763 64-Core Processor, 4 logical cores. The standard-library rows are a loop written by hand for this one schedule. benchmarks/RESULTS.md has every case with p95s.
What it is for
Support-ticket SLAs
When is a ticket due, and how much of its SLA has elapsed?
add,betweenPayroll and overtime
How many hours of a shift fell inside contracted time?
betweenStaff rosters, including overnight shifts
When does the next shift start and end?
next_period/nextPeriodDelivery ETAs that only count operating hours
When does a parcel arrive?
add
Note: Coming from businesstimedelta?
Most code migrates with one line:
import workclock.compat as businesstimedeltaIt fixes overnight shifts, the crash past the last holiday (#8, #10) and the infinite loop with holidays in another zone (#9). MIGRATING.md lists every difference.