Support SLAs
Find when a support ticket is due, and how much of its SLA has been used and how much is left, with add and between.
A ticket is due 8 working hours after it is opened. When is that, and how much of the SLA is left right now? This guide answers both with add and between, in Rust, Python and JavaScript.
The schedule
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. As rules, in the Europe/London time zone:
WorkDay: 09:00–17:00, Monday to Friday.Break: 12:00–13:00, Monday to Friday.Holidays: Friday 25 and Monday 28 December 2026.
A ticket is opened at 16:30 on Thursday 24 December 2026, London time, with an 8-hour SLA. At 11:00 on Tuesday 29 December, an agent wants to know how much of the SLA has been used and how much is left.
Find the due time and the time left
Each tab adds the SLA to the opening time to get the due time, then counts the working time from opening to now (used) and from now to the due time (left).
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 days = Weekdays::MON_TO_FRI;
let support = Schedule::builder()
.time_zone("Europe/London")
.rule(WorkDay::new(time(9, 0, 0, 0), time(17, 0, 0, 0), days))
.rule(Break::new(time(12, 0, 0, 0), time(13, 0, 0, 0), days))
.rule(Holidays::new([date(2026, 12, 25), date(2026, 12, 28)]))
.build()?;
let sla = SignedDuration::from_hours(8);
let opened: Zoned = "2026-12-24T16:30[Europe/London]".parse()?;
let due = support.add(&opened, sla)?;
assert_eq!(due.to_string(), "2026-12-30T09:30:00Z");
let now: Zoned = "2026-12-29T11:00[Europe/London]".parse()?;
let used = support.between(&opened, &now);
let left = support.between(&now, due);
assert_eq!(used, SignedDuration::from_mins(150));
assert_eq!(left, SignedDuration::from_mins(330));
assert_eq!(used.as_secs_f64() / sla.as_secs_f64(), 0.3125);
Ok(())
}London is at UTC+00:00 in December, so the UTC instants in the Rust and JavaScript tabs are also London wall-clock times.
The result
The ticket is due at 09:30 on Wednesday 30 December 2026, London time. By 11:00 on Tuesday 29 December it has used 2 hours 30 minutes of working time, 31.25% of the SLA, and 5 hours 30 minutes are left:
Date, Europe/London | Working time counted |
|---|---|
| Thursday 24 December, from 16:30 | 30 minutes, used |
| Friday 25 December | None: a holiday |
| Saturday 26 and Sunday 27 December | None: not working days |
| Monday 28 December | None: a holiday |
| Tuesday 29 December, 09:00–11:00 | 2 hours, used |
| Tuesday 29 December, 11:00–12:00 and 13:00–17:00 | 5 hours, left |
| Wednesday 30 December, 09:00–09:30 | 30 minutes, left |
- Thu 24
- Fri 25
- Sat–Sun
- Mon 28
- Tue 29
- Wed 30
Used plus left is always the SLA: between(opened, now) + between(now, due) is between(opened, due), and that is the 8 hours add counted (Adding time: Round trips).
Edge cases
- Overdue tickets. After the due time, the time left is negative: at 10:00 on Wednesday 30 December,
between(now, due)is minus 30 minutes, so the ticket is 30 working minutes overdue. Zero means the SLA ends atnow. Measuring time: The sign of the result explains the sign. - Tickets opened outside working hours.
addcounts from the start of the next working time. A ticket opened at 10:00 on Saturday 26 December starts counting at 09:00 on Tuesday 29 December and is due at 10:00 on Wednesday 30 December (Adding time: Where the result lands). - A due time at the end of the day. A ticket that uses up a stretch of working time is due when that stretch ends, such as 17:00, not at 09:00 the next working day (DESIGN.md A2).
- Holidays. Each date in
Holidaysremoves 00:00 to the next 00:00 in the schedule's time zone, not in UTC (Rules: Holidays). - Daylight saving time (DST). Working hours are wall-clock times in
Europe/London, so 09:00 stays 09:00 after the clocks change (Time zones and DST).
What 0.1.1 does not do (DESIGN.md: Scope):
- SLAs in business days, such as "by the end of the next business day". workclock counts working time, not days, and cannot convert a duration into business days.
- Shorter hours on one date, such as closing at 13:00 on Christmas Eve. Working hours repeat by weekday; there are no per-date overrides. A whole closed day is a holiday.
See also
- Adding time: what
addandsubreturn, including at the edges of working time. - Measuring time: what
betweencounts. - Periods: when the current stretch of working time ends.
- Batch operations: due times for many tickets in one call.
- crates/workclock/examples/sla.rs, examples/python/sla.py and js/examples/sla.mjs: the same scenario as complete programs.
Report a problem with this pageAsk a question in Discussions
Inputs and precision
Which instant, duration and time zone types each language accepts, how precise results are, and which inputs are rejected.
Payroll and overtime
Split the time on a timesheet into contracted hours and overtime by counting the working time inside each clock-in and clock-out span with between.