Repository: focus-shift/jollyday (java)

Issue:
Provide actual and observed holiday dates separately
### More information

Currently, the `Holiday` class does not allow separate retrieval of the actual holiday date and the observed holiday date. The `getDate()` method only returns the actual date if no valid `MovingCondition` is configured; otherwise, it returns the observed date.

To address this limitation, we plan to enhance the API by introducing two new methods: `getActualDate()` and `getObservedDate()`. These methods will explicitly provide the actual holiday date and the observed holiday date, respectively, while preserving the existing behaviour of `getDate()` for backward compatibility.

The `getDate()` method returns the calculated holiday date.  

- If the holiday is movable and has been moved, it returns the observed holiday date.  
- Otherwise, it returns the actual holiday date.  

This is the current behaviour and will not change.

Pull request description:
Provide actual and observed holiday date separately
closes #713

<!--

Thanks for contributing.
Please review the following notes before submitting you pull request.

Please look for other issues or pull requests which already work on this topic. Is somebody already on it? Do you need to synchronize?

# Security Vulnerabilities

🛑 STOP! 🛑 If your contribution fixes a security vulnerability, please do not submit it.
Instead, please write an E-Mail to to one of the maintainer with all the information
to recreate the security vulnerability.

# Describing Your Changes

If, having reviewed the notes above, you're ready to submit your pull request, please
provide a brief description of the proposed changes.

If they:
 🐞 fix a bug, please describe the broken behaviour and how the changes fix it.
    Please label with 'type: bug' and 'status: new'
    
 🎁 make an enhancement, please describe the new functionality and why you believe it's useful.
    Please label with 'type: enhancement' and 'status: new'
 
If your pull request relates to any existing issues,
please reference them by using the issue number prefixed with #.

-->


Patch:
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/Holiday.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/Holiday.java
index 6a744e60..197cf06a 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/Holiday.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/Holiday.java
@@ -4,6 +4,8 @@ import de.focus_shift.jollyday.core.util.ResourceUtil;
 
 import java.time.LocalDate;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  * Represents the holiday and contains the actual date and an localized
@@ -14,15 +16,19 @@ public final class Holiday implements Comparable<Holiday> {
    * The calculated hashcode cached for performance.
    */
   private int hashCode = 0;
+
   /**
    * The date the holiday occurs.
    */
-  private final LocalDate date;
+  private final LocalDate actualDate;
+  /**
+   * The observed date of the holiday.
+   */
+  private final LocalDate observedDate;
   /**
    * The properties key to retrieve the description with.
    */
   private final String propertiesKey;
-
   /**
    * The type of holiday. e.g. official holiday or not.
    */
@@ -32,26 +38,67 @@ public final class Holiday implements Comparable<Holiday> {
    * Constructs a holiday for a date using the provided properties key to
    * retrieve the description with.
    *
-   * @param date          a {@link LocalDate} object.
+   * @param actualDate    a {@link LocalDate} object.
    * @param propertiesKey a {@link java.lang.String} object.
    * @param type          a {@link HolidayType} object.
    */
-  public Holiday(final LocalDate date, final String propertiesKey, final HolidayType type) {
+  public Holiday(final LocalDate actualDate, final String propertiesKey, final HolidayType type) {
+    this(actualDate, null, propertiesKey, type);
+  }
+
+  /**
+   * Constructs a holiday for a date using the provided properties key to
+   * retrieve the description with.
+   *
+   * @param actualDate    a {@link LocalDate} object.
+   * @param observedDate  a {@link LocalDate} object.
+   * @param propertiesKey a {@link java.lang.String} object.
+   * @param type          a {@link HolidayType} object.
+   */
+  public Holiday(final LocalDate actualDate, final LocalDate observedDate, final String propertiesKey, final HolidayType type) {
     super();
     this.type = type;
-    this.date = date;
+    this.actualDate = actualDate;
+    this.observedDate = observedDate;
     this.propertiesKey = propertiesKey == null ? "" : propertiesKey;
   }
 
+  /**
+   * Returns the calculated holiday date.
+   * <p>
+   * If the holiday is {@link de.focus_shift.jollyday.core.spi.Movable} and the holiday was moved, then:
+   * <ul>
+   *  <li>the observed holiday date is given</li>
+   *  <li>otherwise, the actual holiday date</li>
+   * </ul>
+   *  @return if holiday was moved the observed date, otherwise the actual date
+   */
+  public LocalDate getDate() {
+    return Optional.ofNullable(observedDate).orElse(actualDate);
+  }
+
   /**
    * <p>
-   * Getter for the field <code>date</code>.
+   * Returns the actual date.
+   * <p>
+   * If you want the observed holiday date then use {@link #getObservedDate()}
    * </p>
    *
-   * @return the holiday date
+   * @return the actual holiday date
    */
-  public LocalDate getDate() {
-    return date;
+  public LocalDate getActualDate() {
+    return actualDate;
+  }
+
+  /**
+   * <p>
+   * Getter for the field <code>observedDate</code>.
+   * </p>
+   *
+   * @return the observed holiday date as optional
+   */
+  public Optional<LocalDate> getObservedDate() {
+    return Optional.ofNullable(observedDate);
   }
 
   /**
@@ -86,31 +133,27 @@ public final class Holiday implements Comparable<Holiday> {
 
   @Override
   public boolean equals(final Object obj) {
-    if (obj == this) {
-      return true;
-    }
-    if (obj instanceof Holiday) {
-      final Holiday other = (Holiday) obj;
-      return other.date.equals(this.date) && other.propertiesKey.equals(this.propertiesKey) && type.equals(other.type);
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
     }
-    return false;
+
+    final Holiday holiday = (Holiday) obj;
+    return Objects.equals(getDate(), holiday.getDate()) &&
+      Objects.equals(propertiesKey, holiday.propertiesKey) &&
+      type == holiday.type;
   }
 
   @Override
   public int hashCode() {
     if (hashCode == 0) {
-      int hash = 1;
-      hash = hash * 31 + date.hashCode();
-      hash = hash * 31 + propertiesKey.hashCode();
-      hash = hash * 31 + type.hashCode();
-      hashCode = hash;
+      hashCode = Objects.hash(getDate(), propertiesKey, type);
     }
     return hashCode;
   }
 
   @Override
   public String toString() {
-    return date.toString() + " (" + getDescription() + ")";
+    return getDate().toString() + " (" + getDescription() + ")";
   }
 
   /**
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/CreateHoliday.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/CreateHoliday.java
index 1c085ff6..4509e9ad 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/CreateHoliday.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/CreateHoliday.java
@@ -8,14 +8,20 @@ import java.util.function.Function;
 
 public class CreateHoliday implements Function<Described, Holiday> {
 
-  private final LocalDate localDate;
+  private final LocalDate actualDate;
+  private final LocalDate observedDate;
 
-  public CreateHoliday(LocalDate localDate) {
-    this.localDate = localDate;
+  public CreateHoliday(final LocalDate actualDate) {
+    this(actualDate, null);
+  }
+
+  public CreateHoliday(final LocalDate actualDate, final LocalDate observedDate) {
+    this.actualDate = actualDate;
+    this.observedDate = observedDate;
   }
 
   @Override
   public Holiday apply(final Described described) {
-    return new Holiday(localDate, described.descriptionPropertiesKey(), described.holidayType());
+    return new Holiday(actualDate, observedDate, described.descriptionPropertiesKey(), described.holidayType());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelative.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelative.java
index 9a34fd69..fe3d0a19 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelative.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelative.java
@@ -4,13 +4,14 @@ import de.focus_shift.jollyday.core.parser.predicates.ValidMovingCondition;
 import de.focus_shift.jollyday.core.spi.Movable;
 
 import java.time.LocalDate;
+import java.util.Optional;
 import java.util.function.Function;
 
 import static de.focus_shift.jollyday.core.spi.Movable.MovingCondition.With.NEXT;
 import static java.time.temporal.TemporalAdjusters.nextOrSame;
 import static java.time.temporal.TemporalAdjusters.previousOrSame;
 
-public class MoveDateRelative implements Function<Movable, LocalDate> {
+public class MoveDateRelative implements Function<Movable, Optional<LocalDate>> {
 
   private final LocalDate date;
 
@@ -19,10 +20,10 @@ public class MoveDateRelative implements Function<Movable, LocalDate> {
   }
 
   @Override
-  public LocalDate apply(final Movable movable) {
+  public Optional<LocalDate> apply(final Movable movable) {
     return movable.conditions().stream()
       .filter(new ValidMovingCondition(date))
       .map(condition -> date.with(condition.with() == NEXT ? nextOrSame(condition.weekday()) : previousOrSame(condition.weekday())))
-      .findFirst().orElse(date);
+      .findFirst();
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/ChristianHolidayParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/ChristianHolidayParser.java
index 9531dcd0..d0b1d612 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/ChristianHolidayParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/ChristianHolidayParser.java
@@ -24,62 +24,70 @@ public class ChristianHolidayParser implements HolidayParser {
   public List<Holiday> parse(final Year year, final Holidays holidays) {
     return holidays.christianHolidays().stream()
       .filter(new ValidLimitation(year))
-      .map(ch -> {
-        LocalDate easterSunday = new CalculateEasterSunday(year).apply(ch.chronology());
-        switch (ch.type()) {
+      .map(christianHolidayConfiguration -> {
+
+        final LocalDate easterSunday = new CalculateEasterSunday(year).apply(christianHolidayConfiguration.chronology());
+
+        final LocalDate actualDate;
+        switch (christianHolidayConfiguration.type()) {
           case EASTER:
+            actualDate = easterSunday;
             break;
           case CLEAN_MONDAY:
           case SHROVE_MONDAY:
-            easterSunday = easterSunday.minusDays(48);
+            actualDate = easterSunday.minusDays(48);
             break;
           case MARDI_GRAS:
           case CARNIVAL:
-            easterSunday = easterSunday.minusDays(47);
+            actualDate = easterSunday.minusDays(47);
             break;
           case ASH_WEDNESDAY:
-            easterSunday = easterSunday.minusDays(46);
+            actualDate = easterSunday.minusDays(46);
             break;
           case MAUNDY_THURSDAY:
-            easterSunday = easterSunday.minusDays(3);
+            actualDate = easterSunday.minusDays(3);
             break;
           case GOOD_FRIDAY:
-            easterSunday = easterSunday.minusDays(2);
+            actualDate = easterSunday.minusDays(2);
             break;
           case EASTER_SATURDAY:
-            easterSunday = easterSunday.minusDays(1);
+            actualDate = easterSunday.minusDays(1);
             break;
           case EASTER_MONDAY:
-            easterSunday = easterSunday.plusDays(1);
+            actualDate = easterSunday.plusDays(1);
             break;
           case EASTER_TUESDAY:
-            easterSunday = easterSunday.plusDays(2);
+            actualDate = easterSunday.plusDays(2);
             break;
           case GENERAL_PRAYER_DAY:
-            easterSunday = easterSunday.plusDays(26);
+            actualDate = easterSunday.plusDays(26);
             break;
           case ASCENSION_DAY:
-            easterSunday = easterSunday.plusDays(39);
+            actualDate = easterSunday.plusDays(39);
             break;
           case PENTECOST:
           case WHIT_SUNDAY:
-            easterSunday = easterSunday.plusDays(49);
+            actualDate = easterSunday.plusDays(49);
             break;
           case WHIT_MONDAY:
           case PENTECOST_MONDAY:
-            easterSunday = easterSunday.plusDays(50);
+            actualDate = easterSunday.plusDays(50);
             break;
           case CORPUS_CHRISTI:
-            easterSunday = easterSunday.plusDays(60);
+            actualDate = easterSunday.plusDays(60);
             break;
           case SACRED_HEART:
-            easterSunday = easterSunday.plusDays(68);
+            actualDate = easterSunday.plusDays(68);
             break;
           default:
-            throw new IllegalArgumentException("Unknown christian holiday type " + ch.type());
+            throw new IllegalArgumentException("Unknown christian holiday type " + christianHolidayConfiguration.type());
         }
-        easterSunday = new MoveDateRelative(easterSunday).apply(ch);
-        return new CreateHoliday(easterSunday).apply(ch);
+
+        return new MoveDateRelative(actualDate).apply(christianHolidayConfiguration)
+          .map(observedDate -> new CreateHoliday(actualDate, observedDate))
+          .orElseGet(() -> new CreateHoliday(actualDate))
+          .apply(christianHolidayConfiguration);
+
       })
       .collect(toList());
   }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/DescribedDateHolder.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/DescribedDateHolder.java
index 91d9ee1e..8690625c 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/DescribedDateHolder.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/DescribedDateHolder.java
@@ -6,16 +6,26 @@ import java.time.LocalDate;
 
 class DescribedDateHolder {
 
-  private final LocalDate date;
+  private final LocalDate actualDate;
+  private final LocalDate observedDate;
   private final Described described;
 
-  public DescribedDateHolder(final Described described, final LocalDate date) {
-    this.date = date;
+  DescribedDateHolder(final Described described, final LocalDate actualDate) {
+    this(described, actualDate, null);
+  }
+
+  DescribedDateHolder(final Described described, final LocalDate actualDate, final LocalDate observedDate) {
+    this.actualDate = actualDate;
+    this.observedDate = observedDate;
     this.described = described;
   }
 
-  public LocalDate getDate() {
-    return date;
+  public LocalDate getActualDate() {
+    return actualDate;
+  }
+
+  public LocalDate getObservedDate() {
+    return observedDate;
   }
 
   public Described getDescribed() {
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedParser.java
index 7f7dd605..ccff5700 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedParser.java
@@ -8,6 +8,7 @@ import de.focus_shift.jollyday.core.parser.functions.MoveDateRelative;
 import de.focus_shift.jollyday.core.parser.predicates.ValidLimitation;
 import de.focus_shift.jollyday.core.spi.Holidays;
 
+import java.time.LocalDate;
 import java.time.Year;
 import java.util.List;
 
@@ -22,8 +23,12 @@ public class FixedParser implements HolidayParser {
   public List<Holiday> parse(final Year year, final Holidays holidays) {
     return holidays.fixed().stream()
       .filter(new ValidLimitation(year))
-      .map(fixed -> new DescribedDateHolder(fixed, new MoveDateRelative(new FixedToLocalDate(year).apply(fixed)).apply(fixed)))
-      .map(describedDateHolder -> new CreateHoliday(describedDateHolder.getDate()).apply(describedDateHolder.getDescribed()))
+      .map(fixedConfiguration -> {
+        final LocalDate actualDate = new FixedToLocalDate(year).apply(fixedConfiguration);
+        final LocalDate observedDate = new MoveDateRelative(actualDate).apply(fixedConfiguration).orElse(null);
+        return new DescribedDateHolder(fixedConfiguration, actualDate, observedDate);
+      })
+      .map(describedDateHolder -> new CreateHoliday(describedDateHolder.getActualDate(), describedDateHolder.getObservedDate()).apply(describedDateHolder.getDescribed()))
       .collect(toList());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayBetweenFixedParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayBetweenFixedParser.java
index a4c27590..62f41a16 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayBetweenFixedParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayBetweenFixedParser.java
@@ -29,7 +29,7 @@ public class FixedWeekdayBetweenFixedParser implements HolidayParser {
           ).apply(fwm)
         )
       )
-      .map(holder -> new CreateHoliday(holder.getDate()).apply(holder.getDescribed()))
+      .map(holder -> new CreateHoliday(holder.getActualDate()).apply(holder.getDescribed()))
       .collect(toList());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayInMonthParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayInMonthParser.java
index dde60bd1..6c779def 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayInMonthParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayInMonthParser.java
@@ -22,7 +22,7 @@ public class FixedWeekdayInMonthParser implements HolidayParser {
     return holidays.fixedWeekdays().stream()
       .filter(new ValidLimitation(year))
       .map(fwm -> new DescribedDateHolder(fwm, new FindWeekDayInMonth(year).apply(fwm)))
-      .map(holder -> new CreateHoliday(holder.getDate()).apply(holder.getDescribed()))
+      .map(holder -> new CreateHoliday(holder.getActualDate()).apply(holder.getDescribed()))
       .collect(toList());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayRelativeToFixedParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayRelativeToFixedParser.java
index d8900d21..8b2e5c78 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayRelativeToFixedParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/FixedWeekdayRelativeToFixedParser.java
@@ -28,7 +28,7 @@ public class FixedWeekdayRelativeToFixedParser implements HolidayParser {
           new FindWeekDayRelativeToDate(new FixedToLocalDate(year).apply(weekdayRelativeToFixed.day())).apply(weekdayRelativeToFixed)
         )
       )
-      .map(holder -> new CreateHoliday(holder.getDate()).apply(holder.getDescribed()))
+      .map(holder -> new CreateHoliday(holder.getActualDate()).apply(holder.getDescribed()))
       .collect(toList());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParser.java
index ea0ce6e9..3d1c05f2 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParser.java
@@ -80,8 +80,8 @@ public class IslamicHolidayParser implements HolidayParser {
         }
 
         return islamicHolidays
-          .map(date -> new MoveDateRelative(date).apply(islamicHoliday))
-          .map(date -> new CreateHoliday(date).apply(islamicHoliday));
+          .map(date -> new DescribedDateHolder(islamicHoliday, date, new MoveDateRelative(date).apply(islamicHoliday).orElse(null)))
+          .map(describedDateHolder -> new CreateHoliday(describedDateHolder.getActualDate(), describedDateHolder.getObservedDate()).apply(islamicHoliday));
 
       })
       .collect(toList());
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/RelativeToEasterSundayParser.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/RelativeToEasterSundayParser.java
index f8af31d2..b597a3ac 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/RelativeToEasterSundayParser.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/parser/impl/RelativeToEasterSundayParser.java
@@ -22,7 +22,7 @@ public class RelativeToEasterSundayParser implements HolidayParser {
     return holidays.relativeToEasterSunday().stream()
       .filter(new ValidLimitation(year))
       .map(res -> new DescribedDateHolder(res, new CalculateEasterSunday(year).apply(res.chronology()).plus(res.days())))
-      .map(holder -> new CreateHoliday(holder.getDate()).apply(holder.getDescribed()))
+      .map(holder -> new CreateHoliday(holder.getActualDate()).apply(holder.getDescribed()))
       .collect(toList());
   }
 }
diff --git a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/spi/Holidays.java b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/spi/Holidays.java
index 594346b4..9a55f6d7 100644
--- a/jollyday-core/src/main/java/de/focus_shift/jollyday/core/spi/Holidays.java
+++ b/jollyday-core/src/main/java/de/focus_shift/jollyday/core/spi/Holidays.java
@@ -11,7 +11,7 @@ public interface Holidays {
   /**
    * see {@link Fixed}
    *
-   * @return list of all fixed holidays
+   * @return list of all fixed holidays configurations
    */
   List<Fixed> fixed();
 
diff --git a/pom.xml b/pom.xml
index 324a6310..d6586110 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,8 +39,8 @@
     <tests.groups>!BenchmarkTest</tests.groups>
 
     <junit.version>5.11.4</junit.version>
-    <mockito.version>5.15.2</mockito.version>
-    <assertj.version>3.27.1</assertj.version>
+    <mockito.version>5.14.2</mockito.version>
+    <assertj.version>3.27.0</assertj.version>
     <jqwik.version>1.9.2</jqwik.version>
     <jmh.version>1.37</jmh.version>
 


Test patch:
diff --git a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/HolidayTest.java b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/HolidayTest.java
index 69240005..c494e8d3 100644
--- a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/HolidayTest.java
+++ b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/HolidayTest.java
@@ -51,12 +51,34 @@ class HolidayTest {
   }
 
   @Test
-  void testHolidayEquals() {
+  void ensureToReturnObservedDateIfPresent() {
+    final LocalDate observedDate = LocalDate.of(2011, 2, 10);
+    final Holiday holiday = new Holiday(LocalDate.of(2011, 2, 2), observedDate, "CHRISTMAS", PUBLIC_HOLIDAY);
+    assertThat(holiday.getDate()).isEqualTo(observedDate);
+  }
+
+  @Test
+  void ensureToReturnActualDateIfObservedDateIsNotPresent() {
+    final LocalDate date = LocalDate.of(2011, 2, 2);
+    final Holiday holiday = new Holiday(date, "CHRISTMAS", PUBLIC_HOLIDAY);
+    assertThat(holiday.getDate()).isEqualTo(date);
+  }
+
+  @Test
+  void ensuresHolidayAreEqual() {
     final Holiday h1 = new Holiday(LocalDate.of(2011, 2, 2), "CHRISTMAS", PUBLIC_HOLIDAY);
     assertThat(h1).isEqualTo(h1);
 
     final Holiday h2b = new Holiday(LocalDate.of(2011, 2, 2), "CHRISTMAS", PUBLIC_HOLIDAY);
     assertThat(h1).isEqualTo(h2b);
+  }
+
+  @Test
+  void ensuresHolidayAreNotEqual() {
+    final Holiday h1 = new Holiday(LocalDate.of(2011, 2, 2), "CHRISTMAS", PUBLIC_HOLIDAY);
+    assertThat(h1)
+      .isNotEqualTo(null)
+      .isNotEqualTo(new Object());
 
     final Holiday h2 = new Holiday(LocalDate.of(2011, 2, 1), "CHRISTMAS", PUBLIC_HOLIDAY);
     assertThat(h1).isNotEqualTo(h2);
diff --git a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelativeTest.java b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelativeTest.java
index ab3d23e4..0a43691d 100644
--- a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelativeTest.java
+++ b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/functions/MoveDateRelativeTest.java
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
 import java.util.List;
+import java.util.Optional;
 
 import static java.time.Month.FEBRUARY;
 import static java.time.Month.MARCH;
@@ -18,8 +19,8 @@ class MoveDateRelativeTest {
 
     final Movable.MovingCondition movingCondition = new Movable.MovingCondition() {
       @Override
-      public DayOfWeek substitute() {
-        return DayOfWeek.WEDNESDAY;
+      public DayOfWeek weekday() {
+        return DayOfWeek.SUNDAY;
       }
 
       @Override
@@ -28,18 +29,15 @@ class MoveDateRelativeTest {
       }
 
       @Override
-      public DayOfWeek weekday() {
-        return DayOfWeek.SUNDAY;
+      public DayOfWeek substitute() {
+        return DayOfWeek.WEDNESDAY;
       }
     };
 
     final Movable movable = () -> List.of(movingCondition);
 
-    final LocalDate localDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
-    assertThat(localDate)
-      .hasYear(2024)
-      .hasMonth(MARCH)
-      .hasDayOfMonth(3);
+    final Optional<LocalDate> maybeMovedDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
+    assertThat(maybeMovedDate).hasValue(LocalDate.of(2024, MARCH, 3));
   }
 
   @Test
@@ -64,15 +62,12 @@ class MoveDateRelativeTest {
 
     final Movable movable = () -> List.of(movingCondition);
 
-    final LocalDate localDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
-    assertThat(localDate)
-      .hasYear(2024)
-      .hasMonth(FEBRUARY)
-      .hasDayOfMonth(25);
+    final Optional<LocalDate> maybeMovedDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
+    assertThat(maybeMovedDate).hasValue(LocalDate.of(2024, FEBRUARY, 25));
   }
 
   @Test
-  void ensureToReturnProvidedLocalDateIfMovingConditionIsFalse() {
+  void ensureToReturnEmptyIfMovingConditionIsFalse() {
 
     final Movable.MovingCondition movingCondition = new Movable.MovingCondition() {
       @Override
@@ -93,10 +88,7 @@ class MoveDateRelativeTest {
 
     final Movable movable = () -> List.of(movingCondition);
 
-    final LocalDate localDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
-    assertThat(localDate)
-      .hasYear(2024)
-      .hasMonth(FEBRUARY)
-      .hasDayOfMonth(28);
+    final Optional<LocalDate> maybeMovedDate = new MoveDateRelative(LocalDate.of(2024, 2, 28)).apply(movable);
+    assertThat(maybeMovedDate).isEmpty();
   }
 }
diff --git a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParserTest.java b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParserTest.java
index 6508d6c5..fd359216 100644
--- a/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParserTest.java
+++ b/jollyday-core/src/test/java/de/focus_shift/jollyday/core/parser/impl/IslamicHolidayParserTest.java
@@ -10,6 +10,7 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.time.DayOfWeek;
+import java.time.LocalDate;
 import java.time.Year;
 import java.util.List;
 
@@ -54,5 +55,7 @@ class IslamicHolidayParserTest {
     // ID_UL_ADHA_2 will be on a sunday in 2022
     final List<Holiday> calculatedHoliday = sut.parse(Year.of(2022), holidays);
     assertThat(calculatedHoliday.get(0).getDate().getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY);
+    assertThat(calculatedHoliday.get(0).getActualDate().getDayOfWeek()).isEqualTo(DayOfWeek.SUNDAY);
+    assertThat(calculatedHoliday.get(0).getObservedDate()).map(LocalDate::getDayOfWeek).hasValue(DayOfWeek.MONDAY);
   }
 }