Keine Deadline an Ostern – Validierungslogik für Datumsfelder in Jira Service Management

Schaubild "Keine Deadline an Ostern" mit Osterformel die zu getHoliday(year) und dann zu relativen Feiertagen um Ostersonntag herum führt.
ℹ️ tl;dr
  • Deadlines strikt validieren: nur Zukunft, kein Wochenende, kein Feiertag – identisch auch beim Editieren.
  • Umsetzung mit ScriptRunner: Workflow-Validator plus Behaviour für Live-Prüfung im Formular.
  • Technik: Feiertage dynamisch per Java-Time und Osterformel, ohne externe Dienste.
  • Ergebnis: konsistente Datumslogik, weniger Rückfragen, höhere Qualität und Compliance.

Aus der Zusammenarbeit mit dem Kunden entstehen Wünsche, die uns konstruktiv herausfordern.
In diesem Fall lautete die Anforderung ungefähr so:

„Wir brauchen Regeln für das Fälligkeitsdatum. Die Deadline soll nicht in der Vergangenheit liegen, nicht auf ein Wochenende und nicht auf einen Feiertag fallen – auch nicht, wenn jemand es später ändert.

Klingt einfach, oder? 😅
Ist es natürlich nicht – zumindest nicht mit dem was uns Jira Service Management (JSM DC) von Haus aus an die Hand gibt.
Aber mit ScriptRunner und ein bisschen Groovy-Magie bekommt man das wunderbar elegant hin.

In diesem Blogbeitrag zeige ich dir, wie ich genau das umgesetzt habe – inklusive Berechnung beweglicher Feiertage (ja, Ostern und Pfingsten sind dabei!) und der passenden Logik für Validierung und Bearbeitung.

Zielsetzung

Die Anforderungen im Überblick:

  1. Das Datum darf nur in der Zukunft liegen.
  2. Es darf nicht auf ein Wochenende oder einen Feiertag fallen.
  3. Beim Editieren gelten dieselben Regeln – kein Zurückdatieren, kein Wochenend-Experiment.

Wenn du jetzt innerlich „Challenge accepted!“ sagst – willkommen im Club!

Laptop mit Deadline Kalender und Groovy Code, davon ausgehend ein Validator und ein Behavior Node die zu Jira Service Management führen

Der Validator: Die erste Kontrolle

Zuerst habe ich einen Script Validator im Workflow eingebaut.
Besonders hilfreich ist hier die Berechnung der beweglichen Feiertage. Aus Gründen der Unabhängigkeit setze ich auf eine Formel, die die Berechnungen direkt im Skript vornimmt. “Extern” verstehe ich hier als zusätzliche Quellen/Services; ScriptRunner zählt streng genommen dazu – mein Punkt bezieht sich ausschließlich auf die Code-Ebene. Wie das funktioniert, erkläre ich später noch einmal gesondert.

Der Code sieht so aus:

import com.opensymphony.workflow.InvalidInputException import java.time.* // ========================= // Konfiguration // ========================= final ZoneId ZONE = ZoneId.of("Europe/Berlin") // Feste bundesweite Feiertage (jahresunabhängig) final Set FIXED_NATIONAL_HOLIDAYS = [ MonthDay.of(1, 1), // Neujahr MonthDay.of(5, 1), // Tag der Arbeit MonthDay.of(10, 3), // Tag der Deutschen Einheit MonthDay.of(12, 25), // 1. Weihnachtstag MonthDay.of(12, 26) // 2. Weihnachtstag ] as Set // ========================= // Hilfsfunktionen // ========================= /** * Ostersonntag nach Meeus/Jones/Butcher (gregorianischer Kalender). */ 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 = März, 4 = April int day = ((h + l - 7 * m + 114) % 31) + 1 return LocalDate.of(year, month, day) } /** * Bewegliche bundesweite Feiertage für ein Jahr. * (Karfreitag, Ostermontag, Christi Himmelfahrt, Pfingstmontag) */ Set movableNationalHolidays(int year) { LocalDate easter = easterSunday(year) return [ easter.minusDays(2), // Karfreitag easter.plusDays(1), // Ostermontag easter.plusDays(39), // Christi Himmelfahrt easter.plusDays(50) // Pfingstmontag ] as Set } // ========================= // Validator-Logik // ========================= def dueTs = issue.getDueDate() if (!dueTs) { return false } LocalDate dueDate = dueTs.toInstant().atZone(ZONE).toLocalDate() LocalDate today = LocalDate.now(ZONE) // 1) Mindestens 1 Tag in der Zukunft (heute + 1 ist erlaubt) if (dueDate.isBefore(today.plusDays(1))) { return false } // Den Rest könnt ihr einfach ergänzen, wie es zu euren Anforderungen passt - es ist nur ein Beispiel :)


So weit, so klar. Aber was passiert, wenn jemand den Vorgang später nochmal öffnet und das Datum ändern will?

Behaviours: Damit auch beim Editieren alles korrekt bleibt

Hier kommt ScriptRunner Behaviours ins Spiel.
Anstatt das Feld komplett schreibgeschützt zu machen, prüfen wir beim Bearbeiten einfach erneut dieselben Regeln.
So bleibt die Kontrolle – aber ohne den User völlig zu blockieren.

Das Behaviour-Skript prüft also in Echtzeit, ob das Datum erlaubt ist, während der Benutzer tippt oder auswählt.

Damit werden dieselben Regeln sowohl bei der Erstellung als auch bei der Bearbeitung angewendet – konsistent und benutzerfreundlich.
Und der User sieht sofort, wenn etwas nicht passt, statt erst nach dem Klick auf „Speichern“.

Feiertage dynamisch berechnen – ganz ohne Glaskugel

Damit die Prüfung auch in Zukunft zuverlässig funktioniert (und wir nicht jedes Jahr Ostern von Hand nachtragen müssen), berechne ich die Feiertage dynamisch.

Hier kommt die Funktion getHolidays(year) – inklusive der wahnsinnig hilfreichen Gaußschen Osterformel:

import java.time.LocalDate List<LocalDate> getHolidays(int year) { def holidays = [] // Feste Feiertage holidays += LocalDate.of(year, 1, 1) // Neujahr holidays += LocalDate.of(year, 5, 1) // Tag der Arbeit holidays += LocalDate.of(year, 10, 3) // Tag der Deutschen Einheit holidays += LocalDate.of(year, 12, 25) // 1. Weihnachtstag holidays += LocalDate.of(year, 12, 26) // 2. Weihnachtstag // Bewegliche Feiertage basierend auf Ostersonntag def easterSunday = getEasterSunday(year) holidays += easterSunday.minusDays(2) // Karfreitag holidays += easterSunday.plusDays(1) // Ostermontag holidays += easterSunday.plusDays(39) // Christi Himmelfahrt holidays += easterSunday.plusDays(49) // Pfingstsonntag holidays += easterSunday.plusDays(50) // Pfingstmontag holidays += easterSunday.plusDays(60) // Fronleichnam return holidays } // Gaußsche Osterformel (funktioniert seit über 200 Jahren – stabiler als mancher Jira-Release 😅) 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) }

Dieser Teil des Scripts kümmert sich darum, alle Feiertage eines bestimmten Jahres als Datumsobjekte zu erzeugen, damit sie später bei der Prüfung des Fälligkeitsdatums ausgeschlossen werden können.

  • Die Methode getHolidays(int year) erstellt dafür eine Liste von LocalDate-Objekten.
    Zuerst werden die festen Feiertage (wie Neujahr oder Weihnachten) direkt per LocalDate.of(...) hinzugefügt.
    Diese ändern sich ja nie im Kalender.
  • Danach ruft das Script getEasterSunday(year) auf, um den Ostersonntag des betreffenden Jahres zu berechnen.
    Von diesem Datum aus werden dann weitere bewegliche Feiertage (wie Karfreitag, Pfingsten, Fronleichnam usw.) relativ zum Ostersonntag berechnet – z. B. „2 Tage vorher“ oder „50 Tage danach“.
  • Das Ergebnis ist eine vollständige Liste aller relevanten Feiertage des Jahres, die man später einfach per .contains(datum) prüfen kann, um ungültige Tage zu blockieren.
Zeichenfläche 6 1Ostern

Fazit: Jira darf auch mal streng sein

Mit ScriptRunner lassen sich in Jira Service Management auch komplexe Anforderungen sauber umsetzen – mehr braucht es dazu nicht.
Durch den Validator und das Behaviour prüfen wir dieselben Regeln beim Erstellen und beim Bearbeiten.

Das Ergebnis:

  • Kein Datum in der Vergangenheit.
  • Kein Wochenende, kein Feiertag.
  • Kein Ärger beim Editieren.

Und das alles mit ein paar selbst definierten Regeln und ein paar Zeilen Groovy-Code.
Wenn du auch mal eine speziellere Lösung brauchst – Meld dich gerne bei uns!

Autor: Helge Sylvester

Helge Sylvester
Mit Wurzeln im Maschinenbau und weltweitem Einsatz bringt Helge ein tiefes Verständnis für industrielle Abläufe und technische Präzision in die Prozessberatung ein. Heute hilft er Teams dabei, Strukturen zu hinterfragen, Abläufe zu verschlanken und prozessuale Lösungen zu finden, die wirklich funktionieren. Nach Feierabend geht es meist sportlich mit Freunden und Familie weiter.