# Work Calendar A work calendar defines which days and hours count as working time, so that [timers](https://docs.workflowengine.io/get-started/concepts/timer/) 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](https://docs.workflowengine.io/get-started/concepts/workflow-runtime/) supports any number of named calendars. A [process instance](https://docs.workflowengine.io/get-started/concepts/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: ```csharp using OptimaJet.Workflow.Core.Runtime.Calendars; // Define a single holiday: December 31, 2023 var holiday = MonthDay.Create(2023, Months.December, 31); // Define a custom workday: December 30, 2023, from 10 AM for 4 hours var customWorkday = Workday.Create( MonthDay.Create(2023, Months.December, 30), TimeSpan.FromHours(10), TimeSpan.FromHours(4)); // Build the year definition with holidays and custom workdays var year2023 = Year.Create(2023) .SetHolidays(holiday) .SetWorkdays(customWorkday); // Shorthand notation for another year var year2024 = Year.Create(2024) .SetHoliday(Months.January, 1) .SetHoliday(Months.December, 31) .SetWorkday(Months.December, 30, TimeSpan.FromHours(10), TimeSpan.FromHours(4)); // Assemble the calendar var 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: ```csharp 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. Table: Calendar-aware timer value examples | 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: ```csharp 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: ```csharp 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](https://docs.workflowengine.io/get-started/concepts/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 `4h` fires 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 whours` at 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](https://docs.workflowengine.io/get-started/concepts/timer/): Timers reference work calendars through the 'w' notation. - [Workflow Runtime](https://docs.workflowengine.io/get-started/concepts/workflow-runtime/): WorkflowRuntime manages calendar registration and selection. - [Process Instance](https://docs.workflowengine.io/get-started/concepts/process-instance/): A process instance can be assigned a specific calendar at creation. - [Persistence](https://docs.workflowengine.io/get-started/concepts/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.