No Deadline at Easter – Validation Logic for Date Fields in Jira Service Management

Diagram "No Deadline at Easter" with an Easter formula that leads to getHoliday(year) and then to holidays relative to Easter Sunday.
ℹ️ tl;dr
  • 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:

  1. The date must be in the future only.
  2. It must not fall on a weekend or a public holiday.
  3. When editing, the same rules apply — no backdating, no weekend experiments.

If you’re now saying “Challenge accepted!” — welcome to the club!

Laptop with deadline calendar and Groovy code, from there a validator and a Behaviour node leading to Jira Service Management

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 of LocalDate objects.
    First, the fixed public holidays (such as New Year’s or Christmas) are added directly via LocalDate.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.
Artboard 6 1 Easter

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!