Summary#
ServiceHours is the window an SLA clock accrues within. The platform's tick-accrual engine walks a schedule's
weekly Windows (and any holiday Exceptions) to advance SLA time and compute deadlines — so a ticket on a
business-hours schedule simply doesn't burn its clock at night, while one on a 24/7 schedule accrues around the clock.
The shape is platform (the engine must walk it), the rows are the app's (it seeds BusinessHours /
AroundTheClock, assigns which one governs an instance, and owns everything about the SLA's meaning).
Signature#
public entity ServiceHours {
string Name;
Zone Zone; // REQUIRED — the zone the windows' local times are read in (DST-correct)
[ForeignKey(ServiceHours)] ServiceWindow[] Windows; // recurring weekly open windows — Mon 09:00–17:00, …
[ForeignKey(ServiceHours)] ServiceException[] Exceptions; // holiday / special-hours date ranges
}
public entity ServiceWindow {
ServiceHours ServiceHours;
DayOfWeek Day; // the C# built-in enum (Sunday = 0 … Saturday = 6)
TimeSpan Start; TimeSpan End; // local time-of-day, as a TimeSpan from midnight
}
public entity ServiceException {
ServiceHours ServiceHours;
DateTime From; DateTime To;
bool IsClosed; // the clock does not accrue across From..To
string Reason;
}Description#
ServiceHours is ordinary app data (a data-DB entity), editable through the app's own UI — not a declaration
block. An app seeds the schedules it needs and points an instance at one (typically snapshotted in the workflow's
Start { } from a contract/severity matrix). The platform reads the rows and walks them; it never defines them.
Windowsare recurring weekly intervals in local time. The parent'sZonegives them a DST-correct instant (a09:00Stockholm window is a different UTC instant in summer and winter). A schedule with no windows is the identity schedule — ticks equal wall-clock — which is the 24/7 / "AroundTheClock" case.Zoneis required. An SLA schedule must always state the zone its hours are measured in, so a row can never be saved ambiguous — the platform rejects it at commit (UI edit or seed), not just at compile. A 24/7 schedule names a zone too (Zone = "UTC"); it's inert there (with no windows the walk never reads it) but keeps every schedule self-documenting.Exceptionsare date-range overrides applied on top of the windows. AnIsClosedexception (a public holiday) removes accrual across itsFrom..To; theReasonis for humans.DayOfWeekis the C# built-in enum, sow.Day == DayOfWeek.Mondayreads and stores exactly as in C#.
Binding it to a workflow. A workflow names the schedule its clocks accrue within with a ServiceHours = <expr>;
setting (a value over this.Item resolving to a ServiceHours ref, typically snapshotted onto the entity in Start).
Every SLA clock on the run — a state's Expire, a milestone's Within, a reminder — then advances only inside that
schedule's windows, so a deadline computed from a 4-hour budget lands after the intervening nights and weekends, not 4
wall-clock hours later. With no ServiceHours binding a run uses the identity schedule (24/7).
workflow SupportTicket {
ServiceHours = this.Item.ServiceHours; // the schedule this run's clocks walk
Accrues = [Open, Working]; // and the states in which they run at all
...
}Two schedule-shaped things, deliberately split. ServiceHours is when the SLA clock accrues — the customer's
promise window. It is not availability (who is on shift): rosters, leave, and follow-the-sun live entirely in the
app and the platform never consults them. The promise (ServiceHours) and the people (availability) are different
concerns with different owners.
Examples#
enum OrderState { Open, Done }
[Principal]
entity Person {
[Required, MaxLength(200)] string Email;
security { allow read, create when IsAuthenticated; }
}
entity Order {
[Required, MaxLength(60)] string Reference;
OrderState Status; // no default: the workflow owns this field
security { allow read, create, update when IsAuthenticated; }
}
// The schedule entities ship with the platform; an app that reaches them says who may.
partial entity ServiceHours { security { allow read, create when IsAuthenticated; } }
partial entity ServiceWindow { security { allow read, create when IsAuthenticated; } }
// Two schedules: one that accrues around the clock, one that only counts business hours.
void SeedSchedules() {
new ServiceHours { Name = "AroundTheClock", Zone = Zone.Of("UTC") }; // no windows ⇒ 24/7
var biz = new ServiceHours { Name = "BusinessHours", Zone = Zone.Of("Europe/Stockholm") };
new ServiceWindow { ServiceHours = biz, Day = DayOfWeek.Monday,
Start = TimeSpan.FromHours(9), End = TimeSpan.FromHours(17) };
}Seed a 24/7 schedule and a Mon–Fri business-hours one with a holiday:
var aroundTheClock = new ServiceHours { Name = "AroundTheClock", Zone = "UTC" }; // no windows ⇒ accrues 24/7
var bizHours = new ServiceHours { Name = "BusinessHours", Zone = "Europe/Stockholm" };
foreach (var d in [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday]) {
new ServiceWindow { ServiceHours = bizHours, Day = d, Start = TimeSpan.FromHours(9), End = TimeSpan.FromHours(17) };
}
new ServiceException { ServiceHours = bizHours, From = new DateTime(2026, 6, 19), To = new DateTime(2026, 6, 19),
IsClosed = true, Reason = "Midsommarafton" };See also#
- Assigned / Finished (milestones) — the SLA clock whose ticks accrue within these windows
- <span class="planned" title="this page is planned and not written yet">workflow-state</span> —
Accruesnames the states in which the clock runs - subscribe — the slot whose promise the clock measures