Benchmarks
How fast each binding is, measured against businesstimedelta 1.0.1 and a hand-written standard-library loop.
The Rust, Python and JavaScript bindings are timed on the same three cases, against businesstimedelta 1.0.1 and against the loop you would write with each language's standard library. The Python suite also times workclock.compat. The cases and the commands come from benchmarks/README.md. The numbers in benchmarks/RESULTS.md come from the Benchmarks workflow on a GitHub-hosted Ubuntu runner, and RESULTS.md links the run, so anyone can open it.
Runner CPUs vary from run to run. On 2026-10-26, three runs on AMD EPYC 7763 and Intel Xeon 8573C runners moved the times by up to about 20%, while the speed-ups over businesstimedelta, measured in the same run, stayed within about 6%. Compare the "vs businesstimedelta" and "vs stdlib" columns rather than raw times.
CI also counts instructions under Valgrind for Rust and Python, and fails a pull request whose single or year case executes more than 3% more instructions. Instruction counts has the details.
Cases
The Rust, Python and JavaScript suites measure the same three cases, all with
one schedule: Europe/London, Monday to Friday 09:00–17:00, a 12:00–13:00
break, and the bank holidays of England in 2026 and 2027. The 2027 ones are
there because businesstimedelta 1.0.1 raises TypeError for instants after
its last holiday (upstream issue 8).
| Case | Call |
|---|---|
single | between Tuesday 2026-03-10 10:15 and 15:45 UTC |
year | between 2026-01-01 and 2027-01-01 UTC |
batch | between for 1,000,000 pairs: pair i starts i * 7919 mod 525600 minutes into 2026 and lasts i * 104729 mod 10080 minutes |
Building the schedule and the pairs is not timed.
Standard-library baselines
Neither Python nor JavaScript can compute working time out of the box, so each
suite also times stdlib: the loop a developer would write for this one
schedule with the standard library alone. It walks the local dates from the
first instant to the second, skips weekends and holidays, and adds the
overlap with 09:00–12:00 and 13:00–17:00 London time.
| Language | Baseline | Built on |
|---|---|---|
| Python | benchmarks/python/stdlib.py | datetime and zoneinfo |
| JavaScript | js/bench/stdlib.ts | Temporal, on by default from Node.js 26 |
Tests check both against workclock on the single and year cases, both
daylight saving changes, Easter, a reversed pair and 10,000 batch pairs, so
every speed-up compares code that gives the same answers. RESULTS.md divides
each language's stdlib median by every row's median in its "vs stdlib"
column. Rust's standard library has no time zones, so Rust has no baseline.
Running
cargo bench -p workclock --bench workclock
uv run pytest benchmarks/python
cd js && npm run build:node && npm run benchThe JavaScript suite needs Node.js 26, for Temporal.
The Python suite also measures workclock's compat API, between_many on
lists and on NumPy arrays, and businesstimedelta 1.0.1. It runs the single case
on compat and businesstimedelta a second time with a python-holidays object
for England in place of the list; compat fills it in for 1970 to 2100. One
round of the batch case takes businesstimedelta about half an hour; add
-k "not (test_batch and businesstimedelta)" to skip it.
The JavaScript suite runs the batch case three ways: between in a loop, and
betweenMany on arrays of Dates and on Float64Arrays.
Results
Cases and commands are in README.md.
| Machine | |
|---|---|
| Date | 2026-10-26 |
| Run | 40285792116 |
| Runner | GitHub-hosted ubuntu24, image 20261023.334.3 |
| CPU | AMD EPYC 7763 64-Core Processor, 4 logical cores |
| Memory | 16 GiB |
| OS | Linux-6.17.0-1022-azure-x86_64-with-glibc2.39 |
| Rust | rustc 1.99.1 (58cf8f9c3 2026-10-13) |
| Python | 3.15.1 |
| Node.js | v26.12.1 |
single
| Implementation | Median | p95 | Samples | vs businesstimedelta | vs stdlib |
|---|---|---|---|---|---|
| Rust | 1.19 µs | 1.2 µs | 100 | 140× | |
| JavaScript | 1.77 µs | 1.81 µs | 1109598 | 95× | 5.1× |
| Python, workclock | 3.96 µs | 4.08 µs | 50564 | 43× | 1× |
| Python, stdlib | 4.01 µs | 4.12 µs | 41903 | 42× | 1× |
| Python, workclock.compat, python-holidays | 6.93 µs | 7.33 µs | 1829 | 24× | 0.58× |
| Python, workclock.compat | 7.24 µs | 14 µs | 9427 | 23× | 0.55× |
| JavaScript, stdlib | 9.06 µs | 9.6 µs | 173166 | 19× | 1× |
| Python, businesstimedelta | 169 µs | 182 µs | 3879 | 1× | 0.024× |
| Python, businesstimedelta, python-holidays | 196 µs | 210 µs | 2790 | 0.86× | 0.02× |
year
| Implementation | Median | p95 | Samples | vs businesstimedelta | vs stdlib |
|---|---|---|---|---|---|
| Rust | 84.5 µs | 86.9 µs | 100 | 630× | |
| Python, workclock | 85.4 µs | 96.1 µs | 8093 | 620× | 8.4× |
| Python, workclock.compat | 88.7 µs | 100 µs | 5481 | 600× | 8.1× |
| JavaScript | 106 µs | 114 µs | 18747 | 500× | 16× |
| Python, stdlib | 719 µs | 747 µs | 1256 | 74× | 1× |
| JavaScript, stdlib | 1.71 ms | 1.74 ms | 891 | 31× | 1× |
| Python, businesstimedelta | 53 ms | 54.7 ms | 19 | 1× | 0.014× |
batch
| Implementation | Median | p95 | Samples | vs businesstimedelta | vs stdlib |
|---|---|---|---|---|---|
| Rust | 1.96 s | 1.97 s | 10 | 300× | |
| Python, workclock between_many, NumPy | 2.13 s | 2.15 s | 5 | 280× | 4.7× |
| JavaScript, betweenMany, Float64Array | 2.54 s | 2.55 s | 5 | 230× | 15× |
| JavaScript, between in a loop | 2.68 s | 2.68 s | 5 | 220× | 14× |
| JavaScript, betweenMany, Dates | 2.68 s | 2.71 s | 5 | 220× | 14× |
| Python, workclock between_many | 3.42 s | 3.45 s | 5 | 170× | 2.9× |
| Python, workclock | 4.6 s | 4.62 s | 3 | 130× | 2.2× |
| Python, workclock.compat | 7.99 s | 8.01 s | 3 | 74× | 1.2× |
| Python, stdlib | 9.95 s | 9.95 s | 3 | 60× | 1× |
| JavaScript, stdlib | 38.3 s | 39 s | 5 | 16× | 1× |
| Python, businesstimedelta | 9.9 min | 9.9 min | 1 | 1× | 0.017× |
Report a problem with this pageAsk a question in Discussions