SpEL – Methods for Working Days and Public Holidays
This document describes the SpEL methods that extend date objects with working-day calculations, including support for public holidays.
Contents
- Overview
- Methods
- The
countryParameter - Date Conversion Behaviour
- Holidays – System.Holidays Register
- Practical Examples
- Combining with Existing Methods
- Caching Notes
Overview
All three methods are available as implicit methods on the Date object — they can therefore be called directly on the result of #now() or on any other data expression returning a Date.
| Method | Description |
|---|---|
.plusWorkdaysWithHolidays(n, country) | Adds n working days, skipping weekends and public holidays |
.plusWorkdays(n) | Adds n working days, skipping weekends only |
.isWorkingDayWithHolidays(country) | Returns true if the date falls on a working day (Mon–Fri and not a holiday) |
Holidays are loaded from the System.Holidays register (see the section below).
Methods
plusWorkdaysWithHolidays
<Date>.plusWorkdaysWithHolidays(n: Int, country: String?): Date
Advances the date by n working days (Monday–Friday), skipping public holidays recorded in the System.Holidays register for the given country.
Parameters
| Parameter | Type | Description |
|---|---|---|
n | Int | Number of working days to add (≥ 0) |
country | String? | Country code ('CZ', 'SK'), or null for holidays of all countries |
Return value: Date – the resulting working day at midnight UTC.
Examples
// 7 working days from today, using CZ holidays
#now().plusWorkdaysWithHolidays(7, 'CZ')
// 3 working days from today, using SK holidays
#now().plusWorkdaysWithHolidays(3, 'SK')
// 5 working days without country distinction (skips a holiday of any country)
#now().plusWorkdaysWithHolidays(5, null)
// Working day 10 days from a specific date stored in a variable
#myDate.plusWorkdaysWithHolidays(10, 'CZ')
plusWorkdays
<Date>.plusWorkdays(n: Int): Date
Advances the date by n working days (Monday–Friday). Public holidays are not taken into account — if you also need to skip holidays, use plusWorkdaysWithHolidays.
Parameters
| Parameter | Type | Description |
|---|---|---|
n | Int | Number of working days to add (≥ 0) |
Return value: Date – the resulting working day at midnight UTC.
Examples
// The next working day (Monday–Friday), regardless of holidays
#now().plusWorkdays(1)
// Five working days from now
#now().plusWorkdays(5)
// Zero working days – returns the same date (weekends are not advanced)
#now().plusWorkdays(0)
Note:
plusWorkdays(0)returns the input date unchanged, even if it falls on a weekend. To get the "nearest working day from today", useplusWorkdaysWithHolidays(0, 'CZ')—getWorkdayswithtotalDays=0advances to the first working day.
isWorkingDayWithHolidays
<Date>.isWorkingDayWithHolidays(country: String?): Boolean
Returns true if the date falls on a working day (Monday–Friday) and is not a public holiday recorded in the System.Holidays register for the given country.
Parameters
| Parameter | Type | Description |
|---|---|---|
country | String? | Country code ('CZ', 'SK'), or null for holidays of all countries |
Return value: Boolean
Examples
// Is today a working day in the Czech Republic?
#now().isWorkingDayWithHolidays('CZ')
// Is a specific date a working day in Slovakia?
#myDate.isWorkingDayWithHolidays('SK')
// Is the date a working day without country distinction?
#now().isWorkingDayWithHolidays(null)
The country Parameter
| Value | Description |
|---|---|
'CZ' | Filters only Czech Republic holidays |
'SK' | Filters only Slovak Republic holidays |
null | Takes into account holidays of all registered countries — a day is excluded if it is a holiday in any country |
Date Conversion Behaviour
- The input
Dateis converted to aLocalDatein UTC (consistent with#now(), which returns time in UTC). - The resulting
Dateis always midnight UTC of the resulting day (2025-03-14T00:00:00.000Z). - If you are working with a date in a different time zone, perform the conversion before passing it to the method, e.g. using
.setTime(h, m, s, 'CET').
Holidays – System.Holidays Register
Holidays are loaded from the register with code System.Holidays. A register entry (RegisterValue) contains the following values:
code: arbitrary identifier (recommended pattern:{country}.{name}, e.g.cz.novy.rok).name: human-readable holiday name (e.g.New Year's Day).chars.detail: map with the fields described below — most of the holiday metadata lives inside this nested map.validFor: aTimePeriodthat marks the entry's validity window (validityFrom/validityTo). Entries outside their valid range are ignored automatically, so historical or future-only holidays can be modeled without branching logic.- The helper
RegisterPublicService.getValidRegisterValuesByRegister(REGISTER_CODE)already filters by validity, so services reading the register only receive currently applicable values.
chars.detail fields
| Key | Required | Description |
|---|---|---|
country | yes | Country code ("CZ" or "SK"). Comparisons are case-insensitive, allowing CZ, cz, etc. |
recurrenceType | yes | Either "YEARLY" (fixed-date, same day every year) or "ONE_TIME" (moveable or single-occurrence holiday). |
monthDay | yes for YEARLY | Month and day in MM-DD format ("01-01" = 1 January). Only required for YEARLY. |
date | yes for ONE_TIME | Holiday date expressed either as ISO instant (YYYY-MM-DDTHH:mm:ss.sssZ) or plain date (YYYY-MM-DD). ISO instants are interpreted in the Europe/Prague zone, so the resolved LocalDate always reflects Prague local time. |
yearFrom | no (YEARLY only) | First year (inclusive) from which the holiday applies. Omit to make the holiday retroactive. |
yearTo | no (YEARLY only) | Last year (inclusive) the holiday applies. Omit to keep it valid indefinitely. |
Recurrence Types
YEARLY – fixed-date holiday that repeats every year (New Year, Christmas, …).
{ "detail": { "recurrenceType": "YEARLY", "country": "CZ", "monthDay": "12-25", "yearFrom": "2000", "yearTo": "2030" } }
Optional yearFrom / yearTo restrict the holiday to a range of years without duplicating entries.
ONE_TIME – one-off holiday or moveable date (Easter, days of mourning, …). The date field can be a simple calendar date or a full ISO instant.
{ "detail": { "recurrenceType": "ONE_TIME", "country": "CZ", "date": "2026-03-10T23:00:00.000Z" } }
Multiple countries can share the register; to treat a day as holiday when any country marks it, pass country = null to the SpEL methods.
Cache Invalidation
HolidayService.getHolidaysForYear(year, country) is cached under year_country (e.g. 2025_CZ). After modifying register entries, call HolidayService.evictCache() (cache name system-holidays) so the new data is used immediately.
Practical Examples
Calculating a Due Date
// Due 14 working days from today (CZ holidays)
#now().plusWorkdaysWithHolidays(14, 'CZ')
Conditional Date Assignment
// If today is a working day, assign today; otherwise the next working day (+1)
#if(#now().isWorkingDayWithHolidays('CZ'))
.then(#now())
.else(#now().plusWorkdaysWithHolidays(1, 'CZ'))
Calculation Inside a #do Block
#do(
#start = #now(),
#due = #start.plusWorkdaysWithHolidays(10, 'CZ'),
#isBizDay = #start.isWorkingDayWithHolidays('CZ'),
#due.iso()
)
Due Date Output in ISO Format
#now().plusWorkdaysWithHolidays(5, 'SK').iso()
Escalation – If Deadline Is Today and Not a Working Day
#if(#deadline.isWorkingDayWithHolidays('CZ').not())
.then(#deadline.plusWorkdaysWithHolidays(1, 'CZ'))
.else(#deadline)
Comparing with Another Date
// Is the deadline earlier than 3 working days from now?
#deadline.isBefore(#now().plusWorkdaysWithHolidays(3, 'CZ'))
Slovak Due Date Formatted as dd.MM.yyyy
#now().plusWorkdaysWithHolidays(7, 'SK').formatted('dd.MM.yyyy', 'Europe/Bratislava')
Combining with Existing Methods
The new methods work seamlessly with existing methods on Date:
| Method | Description |
|---|---|
.iso() | ISO 8601 format (2025-03-14T00:00:00.000Z) |
.formatted(format, zone?) | Custom format and time zone |
.plusDays(n) | Add calendar days |
.plusMonths(n) | Add months |
.isBefore(date) | Comparison – is earlier? |
.isAfter(date) | Comparison – is later? |
.setMidnight() | Sets time to midnight (CET) |
Chaining example:
// 5 working days from today, output as dd.MM.yyyy in Prague time
#now().plusWorkdaysWithHolidays(5, 'CZ').formatted('dd.MM.yyyy', 'Europe/Prague')
// Due date in 3 working days – comparison with a date from an object
#now().plusWorkdaysWithHolidays(3, 'CZ').isBefore(ticket.dueDate)
Caching Notes
- Results of
HolidayService.getHolidaysForYear(year, country)are cached under the keyyear_country(e.g.2025_CZ). - The cache is shared across the whole application — the first call for a given year loads holidays from the DB; subsequent calls return the cached result.
- For calculations spanning the end of the year (e.g. in December), holidays for
year + 1are automatically loaded as well. - The cache can be invalidated via
HolidayService.evictCache()or by restarting the application.