# ServiceHours.IsOpen, NextOpen, BusinessTime, After

> Ask a schedule the questions people and queues are asked — is it open now ("on shift"), when does it next open, how much working time lies between two instants, and when a span of working time from now runs out — answered by the same calendar walk the workflow engine accrues SLA time with, so a roster and a due date can never disagree.

<!-- id: workflow-service-hours-open · area: workflow · stability: preview · html: https://osysharp.com/reference/workflow/service-hours-open/ -->

## Summary        {#summary}
A [`ServiceHours`](https://osysharp.com/reference/workflow/service-hours/) row says when a team works: weekly windows in a zone, and closed days. The
workflow engine already walks it to decide when an SLA clock is due. These four calls ask the same walk directly:

- **`ServiceHours.IsOpen(hours, at)`** — is the schedule open at that instant? The question "is she on shift?".
- **`ServiceHours.NextOpen(hours, at)`** — the first instant at or after `at` when it is open: `at` itself when it
  already is, the next window's opening when not, and `null` when nothing opens within forty years.
- **`ServiceHours.BusinessTime(hours, from, to)`** — how much open time lies between two instants.
- **`ServiceHours.After(hours, at, span)`** — the instant `span` of open time after `at`: BusinessTime's inverse, and
  exactly the walk the engine sets a deadline with. `null` when the schedule does not open enough within the walk.

## Signature      {#signature}
```osy syntax
bool      ServiceHours.IsOpen(ServiceHours? hours, DateTime at);
DateTime? ServiceHours.NextOpen(ServiceHours? hours, DateTime at);
TimeSpan  ServiceHours.BusinessTime(ServiceHours? hours, DateTime from, DateTime to);
DateTime? ServiceHours.After(ServiceHours? hours, DateTime at, TimeSpan span);
```

## Description    {#description}

### One walk, two readers          {#one-walk}
The engine computes a milestone's due time by walking the run's schedule; these calls load the same rows into the same
calendar and ask it. So "Rahul is off hours" and "this reply is due Monday 10:00" are two readings of one answer, and an
app that shows both cannot contradict itself. A window's local time is read in the schedule's `Zone`, daylight saving
included; a `ServiceException` with `IsClosed` closes its dates.

### No schedule is around the clock {#null}
`hours` may be null. That is the 24/7 schedule — the same meaning a workflow with no `ServiceHours` has — so
`IsOpen(null, at)` is true, `BusinessTime(null, from, to)` is `to - from`, and `After(null, at, span)` is `at + span`. A person who has not set their hours is
always available, rather than never.

### A read, and a server one       {#side}
Each call reads the schedule's rows, so it runs on the server and is refused inside a query predicate (it is not a
column). In a workflow body it is remembered like any other read, so a replay sees the answer the first pass saw.

## Examples       {#examples}

### On shift, and when they start   {#example-shift}
```osy title="who is on shift now, and when the rest start" test app=workflow-service-hours-open
using Osysharp.ServiceHours;

partial entity ServiceHours  { security { allow read, create when IsAuthenticated; } }
partial entity ServiceWindow { security { allow read, create when IsAuthenticated; } }

[Principal]
entity Agent {
  [Required, MaxLength(100)] string Name;
  ServiceHours? Hours;                       // their working hours, in their zone; none: around the clock
  security { allow read, create, update when IsAuthenticated; }
}

bool OnShift(Agent a) { return ServiceHours.IsOpen(a.Hours, DateTime.UtcNow); }

string Starts(Agent a) {
  var next = ServiceHours.NextOpen(a.Hours, DateTime.UtcNow);
  return next == null ? "not scheduled" : $"{a.Name} starts at {next:HH:mm} UTC";
}

TimeSpan WorkedSince(Agent a, DateTime since) { return ServiceHours.BusinessTime(a.Hours, since, DateTime.UtcNow); }

// "Offered to her first — it falls through at…": fifteen minutes of HER working time from now, which on a Friday evening
// is Monday morning.
DateTime? FallsThrough(Agent a) { return ServiceHours.After(a.Hours, DateTime.UtcNow, TimeSpan.FromMinutes(15)); }
```

## See also       {#see-also}
- [ServiceHours (SLA-accrual windows)](https://osysharp.com/reference/workflow/service-hours/) — the schedule itself, and binding it to a workflow's clocks
- [Assigned / Finished (milestones)](https://osysharp.com/reference/workflow/milestone/) — the promises that walk it
- [Workflow.WorkByItem&lt;T&gt; (one row per item — the board read)](https://osysharp.com/reference/workflow/work-by-item/) — the due time a board shows, computed by the same walk
