com um clique
write-holiday-tests
Write country-specific holiday tests using CalendarCheckerApi
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Write country-specific holiday tests using CalendarCheckerApi
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Create XML holiday calendar configuration files for a new country or region
Add subdivision holiday configurations based on ISO 3166-2 codes
Add country description properties for localization
Add holiday description properties for localization
Register a new holiday calendar in the HolidayCalendar enum
| name | Write Holiday Tests |
| description | Write country-specific holiday tests using CalendarCheckerApi |
This guide explains how to write tests for new or modified holiday calendars using the CalendarCheckerApi.
Create test file at: jollyday-tests/src/test/java/de/focus_shift/jollyday/tests/[CountryName]Test.java
Example naming:
HolidayDETest.javaHolidayUSTest.javaHolidayFRTest.javaThe CalendarCheckerApi provides fluent assertions for testing holidays:
import static de.focus_shift.jollyday.tests.CalendarCheckerApi.assertFor;
import static de.focus_shift.jollyday.core.HolidayCalendar.GERMANY;
import static java.time.Month.JANUARY;
import static java.time.Month.FEBRUARY;
@Test
void ensuresHolidays() {
assertFor(GERMANY)
.hasFixedHoliday("NEW_YEAR", JANUARY, 1).and()
.hasChristianHoliday("GOOD_FRIDAY").and()
.hasIslamicHoliday("ID_UL_ADHA")
.check();
}
// Basic fixed holiday
.hasFixedHoliday("NEW_YEAR", JANUARY, 1)
// With validFrom year
.hasFixedHoliday("JUNETEENTH", JUNE, 19, 2021)
// With validFrom and validTo
.hasFixedHoliday("REFORMATION_DAY", OCTOBER, 31, 2017, null)
.hasChristianHoliday("GOOD_FRIDAY")
.hasChristianHoliday("EASTER_MONDAY")
.hasChristianHoliday("ASCENSION_DAY")
.hasChristianHoliday("WHIT_MONDAY")
.hasChristianHoliday("CORPUS_CHRISTI")
.hasIslamicHoliday("ID_AL_FITR") // type, year, month, day
.hasIslamicHoliday("ID_UL_ADHA")
.hasIslamicHoliday("NEWYEAR")
// if the XML overrides descriptionPropertiesKey to a plain name instead of the default
// "islamic."-prefixed key (e.g. Singapore's HARI_RAYA_PUASA/HARI_RAYA_HAJI), pass true:
.hasIslamicHoliday("HARI_RAYA_PUASA", true)
The XSD defines three distinct "weekday-based" holiday types. Each has its own assertion method, which independently recomputes the expected date rather than calling into the production calculator — this keeps the check a genuine verification of the XML config, not a tautology against the same code being tested.
// FixedWeekday: Nth/last weekday of a month, e.g. US Thanksgiving
.hasFixedWeekdayHoliday("THANKSGIVING", FOURTH, THURSDAY, NOVEMBER)
.validFrom(Year.of(1863))
// FixedWeekdayBetweenFixed: first matching weekday between two fixed dates, e.g. Iceland's Husband's Day
.hasFixedWeekdayBetweenFixedHoliday("HUSBANDS_DAY", FRIDAY, MonthDay.of(JANUARY, 19), MonthDay.of(JANUARY, 25))
// FixedWeekdayRelativeToFixed: Nth weekday before/after/closest-to a fixed anchor date, e.g. Iceland's First Day of Summer
.hasFixedWeekdayRelativeToFixedHoliday("FIRST_DAY_SUMMER", FIRST, THURSDAY, AFTER, MonthDay.of(APRIL, 18))
Occurrence (FIRST/SECOND/THIRD/FOURTH/LAST) and Relation (BEFORE/AFTER/CLOSEST) come from
de.focus_shift.jollyday.core.spi. Note: Occurrence.LAST is not supported for
hasFixedWeekdayRelativeToFixedHoliday (throws UnsupportedOperationException) — production's
day-offset calculation has no defined behavior for LAST in that specific holiday type.
A fourth, related type has its own method:
// RelativeToWeekdayInMonth: a weekday before/after a *computed* weekday-in-month anchor,
// e.g. Maryland's Service Reduction Day (the Friday before the last Monday in May)
.hasRelativeToWeekdayInMonthHoliday("SERVICE_REDUCTION", FRIDAY, BEFORE, LAST, MONDAY, MAY, OBSERVANCE)
.inSubdivision("md")
Relation.CLOSEST is not supported for hasRelativeToWeekdayInMonthHoliday (throws
UnsupportedOperationException) — production's RelativeToWeekdayInMonthParser only branches on
BEFORE vs. everything-else, so CLOSEST silently behaves like AFTER rather than computing an
actual closest-day distance; no shipped config currently relies on that behavior.
every attribute)Any holiday-bearing XML element can carry an every="..." attribute (Limited.YearCycle), restricting
it to a subset of years within its valid range. .every(cycle) handles the two cycles that need no
anchor; .every(cycle, referenceYear) handles the rest, anchored at the year matching the XML's
validFrom (or validTo if there's no validFrom) — calling the wrong overload for a given cycle
throws IllegalArgumentException immediately, rather than silently misbehaving:
// EVEN_YEARS / ODD_YEARS: no reference year needed
.hasFixedHoliday("ASSUMPTION_BLESSED_VIRGIN_MARY", AUGUST, 15)
.every(EVEN_YEARS)
.validBetween(YEAR_FROM, YEAR_TO)
// TWO_YEARS/THREE_YEARS/FOUR_YEARS/FIVE_YEARS/SIX_YEARS: reference year required
.hasFixedHoliday("SOME_HOLIDAY", MARCH, 1)
.every(FOUR_YEARS, Year.of(2014))
.validBetween(Year.of(2014), Year.of(2030))
For a validBetween/validFrom/validTo range with a cycle set, years that don't match the cycle are
asserted absent (not skipped) — the check is exhaustive, not just a presence spot-check. Because of
this, be careful with overlapping XML entries that combine to a broader pattern than any single entry's
own cycle: if several XML entries for the same key have different (possibly complementary) cycles across
overlapping year ranges, don't transcribe them 1:1 — instead determine the actual combined observable
behavior (e.g. by probing the real HolidayManager output across the affected years) and assert that,
since production doesn't distinguish between the underlying XML entries, only the resulting holiday
presence per year.
// Holiday in specific region
.hasFixedHoliday("REPELLENCE_PRAYER", NOVEMBER, 20)
.inSubdivision("bw")
.check();
// Multiple regions
.hasFixedHoliday("EPIPHANY", JANUARY, 6)
.inSubdivision("by")
.and()
.hasFixedHoliday("EPIPHANY", JANUARY, 6)
.inSubdivision("bw")
.check();
package de.focus_shift.jollyday.tests;
import de.focus_shift.jollyday.core.HolidayCalendar;
import org.junit.jupiter.api.Test;
import static de.focus_shift.jollyday.tests.CalendarCheckerApi.assertFor;
import static java.time.Month.*;
class HolidayGETest {
@Test
void ensuresHolidays() {
assertFor(HolidayCalendar.GERMANY)
// National holidays
.hasFixedHoliday("NEW_YEAR", JANUARY, 1).and()
.hasFixedHoliday("LABOUR_DAY", MAY, 1).and()
.hasFixedHoliday("CHRISTMAS", DECEMBER, 25).and()
.hasFixedHoliday("SECOND_CHRISTMAS_DAY", DECEMBER, 26).and()
// Regional holidays - Bavaria
.hasFixedHoliday("EPIPHANY", JANUARY, 6)
.inSubdivision("by")
.and()
.hasFixedHoliday("ASCENSION_DAY", MAY)
.inSubdivision("by")
.and()
// Regional holidays - Baden-Württemberg
.hasFixedHoliday("EPIPHANY", JANUARY, 6)
.inSubdivision("bw")
.and()
.hasFixedHoliday("REPELLENCE_PRAYER", NOVEMBER)
.inSubdivision("bw")
.and()
// Christian holidays
.hasChristianHoliday("GOOD_FRIDAY").and()
.hasChristianHoliday("EASTER_MONDAY").and()
.hasChristianHoliday("ASCENSION_DAY").and()
.hasChristianHoliday("WHIT_MONDAY")
.check();
}
}
@Test
void ensuresMovingCondition() {
assertFor(HolidayCalendar.US)
// New Year falls on Saturday, shifts to Friday
.hasFixedHoliday("NEW_YEAR", JANUARY, 1).canBeMovedFrom(SATURDAY, FRIDAY)
.check();
}
@Test
void ensuresHistoricalChanges() {
assertFor(HolidayCalendar.GERMANY)
// Reformation Day only since 2017
.hasFixedHoliday("REFORMATION", OCTOBER, 31)
.validBetween(Year.of(2017), Year.of(2017))
.check();
}
validFrom/validTo constraints.and() to chain related assertions but not at the end of the chain before .check()YEAR_FROM = 1900 and YEAR_TO = 2173 constants, and use .validBetween(Year.of(YEAR_FROM), Year.of(YEAR_TO)) for all holiday assertions since the entire calendar's valid range is constrained by these attributes.