Skip to main content
Version: 2.4

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

  1. Overview
  2. Methods
  3. The country Parameter
  4. Date Conversion Behaviour
  5. Holidays – System.Holidays Register
  6. Practical Examples
  7. Combining with Existing Methods
  8. 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.

MethodDescription
.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

ParameterTypeDescription
nIntNumber of working days to add (≥ 0)
countryString?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

ParameterTypeDescription
nIntNumber 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", use plusWorkdaysWithHolidays(0, 'CZ')getWorkdays with totalDays=0 advances 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

ParameterTypeDescription
countryString?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

ValueDescription
'CZ'Filters only Czech Republic holidays
'SK'Filters only Slovak Republic holidays
nullTakes into account holidays of all registered countries — a day is excluded if it is a holiday in any country

Date Conversion Behaviour

  • The input Date is converted to a LocalDate in UTC (consistent with #now(), which returns time in UTC).
  • The resulting Date is 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: a TimePeriod that 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

KeyRequiredDescription
countryyesCountry code ("CZ" or "SK"). Comparisons are case-insensitive, allowing CZ, cz, etc.
recurrenceTypeyesEither "YEARLY" (fixed-date, same day every year) or "ONE_TIME" (moveable or single-occurrence holiday).
monthDayyes for YEARLYMonth and day in MM-DD format ("01-01" = 1 January). Only required for YEARLY.
dateyes for ONE_TIMEHoliday 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.
yearFromno (YEARLY only)First year (inclusive) from which the holiday applies. Omit to make the holiday retroactive.
yearTono (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:

MethodDescription
.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 key year_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 + 1 are automatically loaded as well.
  • The cache can be invalidated via HolidayService.evictCache() or by restarting the application.