Work Calendar
A work calendar defines which days and hours count as working time, so that timers configured with calendar-aware notation fire only during business hours. Use a work calendar when a process must respect your organisation's working schedule - skip weekends, observe holidays, and count delays in business time rather than wall-clock time. This page explains how to create, register, and assign calendars to process instances.
WorkflowRuntime supports any number of named calendars. A process instance can be assigned a specific calendar; otherwise, its scheme calendar or the runtime default is used. When an interval timer value uses a w working-time unit, WorkflowRuntime calculates the delay according to the selected calendar instead of wall-clock time.
What a Work Calendar is
A work calendar is a named configuration that specifies three things. Weekend days determine which days of the week are non-working, for example Saturday and Sunday. Working hours define the start time and duration of the working day, for example 10:00 to 18:00 with an 8-hour day. Holidays and exceptions are specific dates that override the default schedule, such as public holidays, and custom workday settings for specific dates.
WorkflowRuntime stores all registered calendars in memory. When it registers an interval timer with a calendar-aware value, the runtime calculates the firing time by advancing through only the working hours defined in the calendar, skipping weekends, holidays, and non-working hours.
A process instance inherits its calendar through a priority chain: the process-level calendar takes precedence, followed by the scheme-level calendar, then the runtime default calendar.
How a Work Calendar works
Create a calendar with the Calendar.Create method. Configure its weekly schedule, then override specific dates for holidays and custom workdays:
using OptimaJet.Workflow.Core.Runtime.Calendars;// Define a single holiday: December 31, 2023var holiday = MonthDay.Create(2023, Months.December, 31);// Define a custom workday: December 30, 2023, from 10 AM for 4 hoursvar customWorkday = Workday.Create( MonthDay.Create(2023, Months.December, 30), TimeSpan.FromHours(10), TimeSpan.FromHours(4));// Build the year definition with holidays and custom workdaysvar year2023 = Year.Create(2023) .SetHolidays(holiday) .SetWorkdays(customWorkday);// Shorthand notation for another yearvar year2024 = Year.Create(2024) .SetHoliday(Months.January, 1) .SetHoliday(Months.December, 31) .SetWorkday(Months.December, 30, TimeSpan.FromHours(10), TimeSpan.FromHours(4));// Assemble the calendarvar calendar = Calendar.Create("MyCalendar") .SetWeekends(DayOfWeek.Saturday, DayOfWeek.Sunday) .SetWorkingHours(TimeSpan.FromHours(8)) .SetStartOfWork(TimeSpan.FromHours(10)) .SetYears(year2023, year2024);Register the calendar with WorkflowRuntime and set it as the default:
runtime.WithCalendars(calendar);runtime.SetDefaultCalendar(calendar.Name);SetDefaultCalendar accepts only the name of a calendar already registered with WithCalendars.
Using calendars with timers
Once a calendar is registered and selected, use w working-time units in an Interval timer value to activate calendar-aware calculation. Do not mix working-time units with ordinary interval units in the same value.
| Timer value | Meaning |
|---|---|
2wd | 2 working days, skipping weekends and holidays |
8 whours | 8 working hours - the timer fires after 8 hours of working time |
1 wd | 1 working day - the timer fires after one full working day |
Without a w working-time unit, an interval timer uses wall-clock time regardless of any calendar. A working-time value requires the selected calendar name to resolve to a registered calendar.
Assigning a calendar to a specific process
Pass the calendar name when creating a process instance to override the default:
var createParams = new CreateInstanceParams("SchemeCode", processId){ CalendarName = "MyCalendar"};await runtime.CreateInstanceAsync(createParams);Multiple calendars for different departments
Register multiple calendars and assign them per scheme or per process:
var itCalendar = Calendar.Create("IT department calendar") .SetWeekends(DayOfWeek.Saturday, DayOfWeek.Sunday) .SetWorkingHours(TimeSpan.FromHours(8)) .SetStartOfWork(TimeSpan.FromHours(10)) .SetYears(Year.Create(2023) .SetHolidays( MonthDay.Create(2023, Months.January, 1), MonthDay.Create(2023, Months.January, 2)) .SetWorkday(Months.December, 31, TimeSpan.FromHours(10), TimeSpan.FromHours(4)));var salesCalendar = Calendar.Create("Sales department calendar") .SetWeekends(DayOfWeek.Saturday, DayOfWeek.Sunday) .SetWorkingHours(TimeSpan.FromHours(8)) .SetStartOfWork(TimeSpan.FromHours(7)) .SetYears(Year.Create(2023) .SetHoliday(Months.January, 1));runtime.WithCalendars(itCalendar, salesCalendar);runtime.SetDefaultCalendar("IT department calendar");Calendar selection priority
When WorkflowRuntime calculates an interval timer's firing time, it selects the first configured calendar name in this order: the process-level calendar assigned through CreateInstanceParams.CalendarName, the scheme-level calendar, then the runtime default calendar set through runtime.SetDefaultCalendar(). The selected name must be registered in runtime.Calendars; an unregistered higher-priority name does not fall back to a lower-priority calendar. Without a selected calendar, ordinary interval values use wall-clock time, while w working-time values cannot be calculated.
Work Calendar and Timer
A timer that uses a work calendar behaves differently from a wall-clock timer:
- A wall-clock timer counts real seconds, minutes, and hours. A timer set to
4hfires exactly 4 hours after it is registered, regardless of whether that time falls outside business hours. - A calendar-aware timer counts only working time. A timer set to
4 whoursat 3 PM, with the workday ending at 6 PM, is scheduled for 11 AM on the next working day: three working hours elapse on the first day and one on the next.
Use a wall-clock timer for absolute deadlines and technical timeouts. Use a calendar-aware timer for business deadlines that should only count during operational hours.
Calendar calculation happens when an interval timer is registered. Timer execution still requires WorkflowRuntime to be configured for single-server or multi-server mode and started with StartAsync().
See also
Timer
Timers reference work calendars through the 'w' notation.
Workflow Runtime
WorkflowRuntime manages calendar registration and selection.
Process Instance
A process instance can be assigned a specific calendar at creation.
Persistence
Timer states are persisted through the persistence provider.
Frequently asked questions
What is a work calendar in Workflow Engine?
A work calendar defines which days and hours count as working time. When a timer uses the w notation, WorkflowRuntime uses the calendar to skip weekends, holidays, and non-working hours. A calendar is configured with weekend days, working hours, and per-date exceptions.
How do I make a timer use a work calendar?
Use a w working-time unit in an Interval timer value. For example, 2wd means 2 working days and 8 whours means 8 working hours. The selected work calendar must be registered with WorkflowRuntime.
How does a work calendar handle an interval that crosses non-working time?
The work calendar includes only configured working time in the interval. If a 4-working-hour timer is set at 3 PM on Friday and the workday ends at 6 PM, its firing time is Monday at 11 AM when Monday is the next working day.
Can I have different calendars for different processes?
Yes. Register multiple calendars with runtime.WithCalendars() and assign each process instance a specific calendar through CreateInstanceParams.CalendarName. When that property is null, the runtime checks the scheme calendar and then the default runtime calendar.
How do I define public holidays in a calendar?
Use Year.Create(year).SetHoliday(month, day) for individual holidays or SetHolidays() for multiple. Use SetWorkday() to override a date that would normally be a weekend. Each year's configuration is independent.