
- Strictly validate deadlines: future only, no weekends, no public holidays — identically when editing.
- Implementation with ScriptRunner: workflow validator plus Behaviour for live validation in the form.
- Technique: holidays calculated dynamically via Java Time and the Easter formula, without external services.
- Result: consistent date logic, fewer follow-ups, higher quality and compliance.
Working with the customer produces requests that challenge us constructively.
In this case, the requirement was roughly as follows:
“We need rules for the due date. The deadline must not be in the past, not fall on a weekend and not fall on a public holiday — not even if someone changes it later.
Sounds simple, right? 😅
Of course it isn’t — at least not with what Jira Service Management (JSM DC) gives us out of the box.
But with ScriptRunner and a bit of Groovy magic, you can do it elegantly.
In this blog post, I’ll show you exactly how I implemented this — including the calculation of movable public holidays (yes, Easter and Pentecost included!) and the right logic for validation and editing.
Objective
The requirements at a glance:
- The date must be in the future only.
- It must not fall on a weekend or a public holiday.
- When editing, the same rules apply — no backdating, no weekend experiments.
If you’re now saying “Challenge accepted!” — welcome to the club!

The Validator: The first check
First, I added a Script Validator to the workflow.
Particularly helpful here is the calculation of movable public holidays. For independence, I use a formula that performs the calculations directly in the script. I consider “external” to mean additional sources/services; strictly speaking ScriptRunner is one of them — my point refers solely to the code level. I’ll explain how that works separately later on.
The code looks like this:
import com.opensymphony.workflow.InvalidInputException
import java.time.*
// =========================
// Configuration
// =========================
final ZoneId ZONE = ZoneId.of("Europe/Berlin")
// Fixed nationwide public holidays (year-independent)
final Set FIXED_NATIONAL_HOLIDAYS = [
MonthDay.of(1, 1), // New Year’s Day
MonthDay.of(5, 1), // Labour Day
MonthDay.of(10, 3), // German Unity Day
MonthDay.of(12, 25), // Christmas Day
MonthDay.of(12, 26) // Second Christmas Day
] as Set
// =========================
// Helper functions
// =========================
/**
* Easter Sunday by Meeus/Jones/Butcher (Gregorian calendar).
*/
LocalDate easterSunday(int year) {
int a = year % 19
int b = Math.floorDiv(year, 100)
int c = year % 100
int d = Math.floorDiv(b, 4) int e = b % 4
int f = Math.floorDiv(b + 8, 25)
int g = Math.floorDiv(b - f + 1, 3)
int h = (19 * a + b - d - g + 15) % 30
int i = Math.floorDiv(c, 4)
int k = c % 4
int l = (32 + 2 * e + 2 * i - h - k) % 7
int m = Math.floorDiv(a + 11 * h + 22 * l, 451)
int month = Math.floorDiv(h + l - 7 * m + 114, 31) // 3 = March, 4 = April
int day = ((h + l - 7 * m + 114) % 31) + 1
return LocalDate.of(year, month, day)
}
/**
* Movable nationwide public holidays for a given year.
* (Good Friday, Easter Monday, Ascension Day, Whit Monday)
*/ Set movableNationalHolidays(int year) {
LocalDate easter = easterSunday(year)
return [
easter.minusDays(2), // Good Friday
easter.plusDays(1), // Easter Monday
easter.plusDays(39), // Ascension Day
easter.plusDays(50) // Whit Monday ] as Set
}
// =========================
// Validator logic
// =========================
def dueTs = issue.getDueDate()
if (!dueTs) {
return false
}
LocalDate dueDate = dueTs.toInstant().atZone(ZONE).toLocalDate()
LocalDate today = LocalDate.now(ZONE)
// 1) At least 1 day in the future (today + 1 is allowed)
if (dueDate.isBefore(today.plusDays(1))) {
return false }
// You can simply add the rest to suit your requirements — this is just an example :)
So far, so clear. But what happens if someone opens the issue again later and wants to change the date?
Behaviours: So everything stays correct when editing as well
This is where ScriptRunner Behaviours comes into play.
Instead of making the field completely read-only, we simply re-validate the same rules when editing.
That keeps control — without completely blocking the user.
The Behaviour script therefore checks in real time whether the date is allowed while the user is typing or selecting.
This applies the same rules both when creating and when editing — consistent and user-friendly.
And the user sees immediately if something doesn’t fit, rather than only after clicking “Save”.
Calculate holidays dynamically — no crystal ball needed
So that the check also works reliably in the future (and we don’t have to enter Easter by hand every year), I calculate the holidays dynamically.
Here comes the function getHolidays(year) — including the incredibly helpful Gaussian Easter formula:
import java.time.LocalDate
List<LocalDate> getHolidays(int year) {
def holidays = []
// Fixed public holidays
holidays += LocalDate.of(year, 1, 1) // New Year’s Day holidays += LocalDate.of(year, 5, 1) // Labour Day
holidays += LocalDate.of(year, 10, 3) // German Unity Day
holidays += LocalDate.of(year, 12, 25) // Christmas Day
holidays += LocalDate.of(year, 12, 26) // Second Christmas Day
// Movable holidays based on Easter Sunday def easterSunday = getEasterSunday(year)
holidays += easterSunday.minusDays(2) // Good Friday
holidays += easterSunday.plusDays(1) // Easter Monday
holidays += easterSunday.plusDays(39) // Ascension Day
holidays += easterSunday.plusDays(49) // Whit Sunday
holidays += easterSunday.plusDays(50) // Whit Monday
holidays += easterSunday.plusDays(60) // Corpus Christi
return holidays
}
// Gauss Easter formula (has worked for over 200 years — more stable than some Jira releases 😅)
LocalDate getEasterSunday(int year) {
int a = year % 19
int b = year / 100 int c = year % 100
int d = b / 4 int e = b % 4
int f = (b + 8) / 25
int g = (b - f + 1) / 3
int h = (19 * a + b - d - g + 15) % 30
int i = c / 4 int k = c % 4
int l = (32 + 2 * e + 2 * i - h - k) % 7
int m = (a + 11 * h + 22 * l) / 451
int month = (h + l - 7 * m + 114) / 31
int day = ((h + l - 7 * m + 114) % 31) + 1
return LocalDate.of(year, month, day)
}This part of the script takes care of creating all holidays of a given year as date objects so they can later be excluded when checking the due date.
- The
getHolidays(int year)method creates a list ofLocalDateobjects.
First, the fixed public holidays (such as New Year’s or Christmas) are added directly viaLocalDate.of(...).
These never change on the calendar. - Then the script calls
getEasterSunday(year)to calculate the year’s Easter Sunday.
From this date, further movable holidays (such as Good Friday, Pentecost, Corpus Christi, etc.) are calculated relative to Easter Sunday — e.g., “2 days before” or “50 days after”. - The result is a complete list of all relevant holidays in the year that you can later check with
.contains(date)to block invalid days.

Conclusion: Jira is allowed to be strict sometimes
With ScriptRunner, even complex requirements can be cleanly implemented in Jira Service Management — nothing else is needed.
Thanks to the validator and the Behaviour, we check the same rules when creating and when editing.
The result:
- No date in the past.
- No weekend, no public holiday.
- No hassle when editing.
And all of that with a few self-defined rules and a few lines of Groovy code.
If you ever need a more specialized solution — feel free to get in touch with us!