Osy#betaRun it on Osyrin: osyrin.com

Reference / Workflow

ServiceHours.IsOpen, NextOpen, BusinessTime, After

ServiceHours.IsOpen(hours, at) → bool · ServiceHours.NextOpen(hours, at) → DateTime? · ServiceHours.BusinessTime(hours, from, to) → TimeSpan · ServiceHours.After(hours, at, span) → DateTime?

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.

preview1 example compiled by CIworkflowslaservice-hours

Summary#

A ServiceHours 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#

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#

One walk, two readers#

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#

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#

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#

On shift, and when they start#

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#

Related

ServiceHours (SLA-accrual windows)

A schedule the SLA clock accrues within — the platform WALKS its weekly windows (and holiday exceptions) to advance…

Assigned / Finished (milestones)

A milestone puts an SLA on a slot's progress — Assigned (someone must PICK IT UP within Within) and Finished (it must…

Workflow.WorkByItem<T> (one row per item — the board read)

One row per tracked ENTITY, where `Workflow.Work<T>()` is one per SLOT. A board, a queue and a "my work" screen are all…