Skip to content

Add 8 working hours.
Land on Wednesday at 09:30.

Fast, correct business-time arithmetic for Rust, Python and JavaScript.

  • cargo add workclock jiff
  • pip install workclock
  • npm i workclock
Inputs
Working hours
to

Time zoneEurope/London

  • 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.

The full programs are sla.rs, sla.py and sla.mjs.

How you can check it

  • Every rule and edge case is specified with an example and an ID.

    DESIGN.md

  • The Rust, Python and JavaScript test suites all run the same file of examples, so the bindings cannot disagree on them.

    tests/fixtures/design_examples.json

  • 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.ts

  • workclock.compat is tested against businesstimedelta 1.0.1 where 1.0.1 is correct, and against the oracle everywhere.

    tests/python/test_differential.py

  • businesstimedelta’s own test suite runs against workclock.compat in CI.

    tests/python/upstream

  • Each upstream bug it fixes has a test: issues 8, 9, 10 and 12, and overnight shifts.

    tests/python/upstream_issues

  • CI fails if line coverage of the Rust crate drops below 90%.

    ci.yml, job coverage

  • Tested on Linux, macOS and Windows; Python 3.9 and 3.15; Node.js 22, 24 and 26; Rust 1.85 checked separately.

    ci.yml, jobs test, python-test, wasm, msrv

  • Benchmarks compare every binding with businesstimedelta 1.0.1, with medians, p95s, date and machine.

    RESULTS.md

  • The npm package is published with provenance, so npm shows which workflow run built it.

    release.yml, job npm

  • PyPI packages are published from GitHub Actions with trusted publishing, without a stored token.

    release.yml, job pypi; Release Process

  • Every GitHub Action is pinned to a commit SHA.

    ADR 0002

  • MIT licence. Rust 1.85+, CPython 3.9+, Node.js 22+.

    LICENSE, README requirements

Benchmarks

Median time of one between call over a 5.5-hour span.
ImplementationMedian
Rust1.19 µs
JavaScript1.77 µs
Python3.96 µs
JavaScript, standard library (Temporal)9.06 µs
Python, standard library (zoneinfo)4.01 µs
Python, businesstimedelta 1.0.1169 µ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

Note: Coming from businesstimedelta?

Most code migrates with one line:

import workclock.compat as businesstimedelta

It 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.