Solutions4sf I build CRMs, and fix the ones that broke

Service Cloud · time in business hours

Your SLA clock counts the weekend.
Salesforce skips it only where you tell it to.

A Salesforce SLA clock counts nights and weekends because business hours start at 24 hours a day, seven days a week, and every place that measures time reads its own setting. Set working hours once, then make sure the case, the milestone and the report each actually use them. This is where each one looks, what the documented methods do, and the four clocks built for an insurance agency in the US.

Salesforce Help, Trailhead and the Apex Reference read on 31 October 2026. Reviewed 1 November 2026.

The cause

Why does the Salesforce SLA clock count nights and weekends?

Because every org starts with business hours of 24 hours, seven days a week, and each place that counts time reads its own setting. A milestone takes its own business hours, then the entitlement process, then the case. A report column reads the default. A formula on two dates reads none at all.

The default is in Salesforce Help, Set Business Hours; the order a milestone follows is in How Business Hours Work in Entitlement Management. The table is the whole problem on one screen: five places, five answers.

The five places that count time on a Salesforce case and the business hours each reads: a milestone (its own, else the entitlement process, else the case), an escalation rule (the rule entry), Business Hours Age (the default), a formula on two dates (none, so calendar time) and a number stored by Apex with BusinessHours.diff (the record passed in); the acceptance test, Friday 16:00 to Monday 10:00 with 9:00 to 17:00 hours, must read exactly 2 hours.
Five places, five answers. Only the stored number from BusinessHours.diff can be filtered and averaged in any report.

Check your own org

How do you count case age in business hours only?

Two documented ways. Business Hours Age in a standard case report is quick, but it cannot be filtered or bucketed, is missing from custom report types and ignores holidays. A number written by Apex with BusinessHours.diff at each status change can be filtered, averaged and reported anywhere, because it is stored on the case.

The limits of the report column are in Enabling Business Hours Age in Case Report, and the holiday rule in Guidelines for Creating Support Holidays. Before choosing, run these two in the Developer Console query editor. The first shows which business hours exist and which is the default; the second counts open cases that no milestone can be timing.

SELECT Name, IsDefault, TimeZoneSidKey FROM BusinessHours WHERE IsActive = true SELECT COUNT() FROM Case WHERE IsClosed = false AND EntitlementId = null

If the default record says nothing about your time zone, or the second number is close to your open cases, the clock on the screen is calendar time with a better label.

What the platform documents

Four methods do all the counting.

All four are static methods of the BusinessHours class in the Apex Reference Guide. Each takes the Id of a business hours record, so the same code can run on different hours for different teams.

  1. 01

    diff: how much working time passed

    Takes a business hours Id and two Datetimes and returns the working time between them in milliseconds. This is the call behind every clock below: divide by 1,000 for seconds and by 3,600 for hours, and store the result.

  2. 02

    add: when the deadline falls

    Adds an interval in milliseconds to a start Datetime, traversing business hours only, and returns the result in the local time zone. Salesforce documents that precision under one minute is ignored, so a target of 90 seconds behaves as one minute.

  3. 03

    isWithin: is the team open right now

    Returns true when a Datetime falls inside business hours. The documentation states that holidays are included in this calculation, which makes it the right test for an after hours auto reply or for routing a case to an on call queue.

  4. 04

    nextStartDate: when the team opens again

    Returns the next moment business hours open, or the same moment if they are already open. Useful for the promise in an auto reply: we will start on this at Monday 09:00.

The conversion that every clock below uses, with the four decimals the agency asked for:

Long ms = BusinessHours.diff(bh.Id, startedAt, endedAt); Decimal hours = ((Decimal) ms / 1000 / 3600).setScale(4, RoundingMode.HALF_UP);

One build, from the inside

Four clocks for an insurance agency.

An insurance agency in the US, policies kept in Applied Epic, every service email a case. The owner wanted to judge staffing from measured time rather than impressions. This is how the time was counted, in the order it was built; the rest of the build is in the insurance agency case study.

  1. 01

    One business hours record, named for the agency

    Monday to Friday, 9:00 to 17:00 in the agency’s own time zone, with its holidays added, set as the default so every new case gets it in BusinessHoursId. Named for the place, not Default, so whoever reads it on a case knows which clock it is.

  2. 02

    History tracking on Status before any code

    The time waiting for the client is not a field anywhere. It is the gaps between status changes, and those exist only in case history. Turn tracking on first, or the first months of cases cannot be measured later.

  3. 03

    Four clocks, written when the status changes

    New to In Progress, Received to Closed, On request from the moment a person took the case, and On request excluding hold. The case trigger calls one Apex service when the status moves to In Progress or Closed, and measures to that moment.

  4. 04

    Waiting subtracted interval by interval

    Every stretch in On Hold or Waiting for Customer is measured with diff against the same business hours and subtracted, so a weekend inside a hold is not removed twice. A hold that is still open counts until now. Reading case history runs in an inner without sharing class, so an agent who cannot see history can still close a case.

  5. 05

    The person clock restarts when the case changes hands

    Assigned at is stamped when the owner becomes a person and cleared when the case goes back to a queue, so On request times whoever holds the case now, not whoever touched it first.

  6. 06

    Hours with four decimals, not hours and minutes

    The agency asked for hours. An early version, still visible commented out in the class, turned 90 minutes into 1.30, which reads like an hour and a third and averages wrong. The live one divides seconds by 3,600 and keeps four decimals.

The acceptance test is one case: created on Friday at 16:00 agency time, taken, put on hold for a while, closed on Monday at 10:00. With 9:00 to 17:00 hours, Received to Closed must read exactly 2 hours, one on Friday and one on Monday, and the clock excluding hold must be lower by the hold, counted in working time. An Apex test sets the created date and inserts the history, so the same numbers are asserted on every deployment.

Waiting for the client

How do you stop the SLA clock while waiting for the customer?

With entitlements, set the case’s Stopped checkbox when the status moves to Waiting for Customer and clear it when the reply arrives; Salesforce records when the process stopped. Without entitlements, read each waiting interval from case history and subtract it, measured with BusinessHours.diff, as the agency build does.

Both fields, IsStopped and StopStartDate, are described in the Case object reference, and Milestone Stop and Resume Behavior adds the rule that catches people: the timer stops on the first action that stops it, not the latest. The checkbox does not set itself: a small record triggered flow on Status does it, and that flow belongs in the same change as the milestones.

What makes it worse

Four choices that look reasonable and are not.

The first is the most common. The Trailhead unit on milestones shows a 16 business hour target entered as 960 minutes, then adds the trigger that attaches entitlements, because nothing else does. The third is one of the five sources of duplicate cases from Email to Case.

  1. 01

    Milestones before every case has an entitlement

    Email to Case creates cases without one, and Salesforce’s own Trailhead unit says entitlements are not added to new cases automatically without a trigger. The process looks configured and most cases run no clock.

  2. 02

    Trusting the report column across a holiday

    Salesforce documents that report results do not take holidays into account. A milestone and a report can then disagree about the same case, and the report is the one the manager reads.

  3. 03

    Reopening closed cases

    A clock written at closing measures from the first email. Reopen the case and close it again, and every working day it sat closed is counted as handling time. In the agency build a reply opens a new case linked to the old one instead.

  4. 04

    A 24 hour day in the record used for reporting

    Business Hours Age only works when the working hours are under 24 hours. A support desk that is open around the clock needs no business hours at all; one that is not should not pretend.

Where business hours sit in the order of a whole build, after the case model and before the reports, is in the order a Service Cloud implementation goes in.

Asked often enough to answer here

Questions

  • Q

    Does Salesforce skip holidays in business hours?

    Yes, for anything that runs on business hours: holidays suspend them, up to 1,000 holidays per business hours record, and isWithin includes them. Reports are the exception. Salesforce documents that report results do not take holidays into account, so a stored number is the safer source for monthly figures.

  • Q

    Can different customers have different business hours?

    Yes. Business hours on a milestone override those on the entitlement process, which override those on the case. Set them on the process when one process serves teams in different time zones, and on the milestone when a severe case must run on a longer day.

  • Q

    Do I need entitlements just to measure time?

    No. Entitlements and milestones are for targets and warnings. To measure how long requests take, business hours on the case and a number written with BusinessHours.diff are enough. The agency build measured four clocks this way and left targets as an optional later stage.

  • Q

    Can a flow do this without code?

    Partly. Milestones, escalation rules and the report column need no code. Subtracting the time spent waiting for the client means reading case history and calling BusinessHours.diff, which in this build is one Apex service. A flow can call the same logic as an invocable action.

The base package covers the case model, email to case, queues, escalation and the reports a manager opens, with business hours inside them. Response targets with entitlements and milestones are their own line in the estimator, priced before anything is built.

Serhii Skrypnyk · Senior Salesforce Administrator · 7 Salesforce certifications · on the platform since 2018. Reviewed 1 November 2026.

Read next

  1. 01

    COI Requests as Their Own Salesforce Case

    How an agency makes certificate of insurance requests their own case type in Service Cloud: keywords kept as records, routing, turnaround and what to count.

  2. 02

    Email to Case Duplicate Cases and Loops

    Why Salesforce Email to Case is creating duplicate cases or an auto response loop: five causes, two queries that tell them apart, and the closed case decision.

  3. 03

    Service Cloud Implementation Roadmap

    Seven phases of a Service Cloud build in the order that keeps the cost down, and what each phase has to produce before the next one starts.