-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Summary:
CalendarIntervalTriggerImpl#getFireTimeAfter returns an incorrect next fire time when:
The start time is set before a Daylight Saving Time (DST) transition,
The target date (for getFireTimeAfter) is after the DST shift,
And the trigger time falls into a missing hour during the DST transition (e.g., 2:30 AM on "spring forward" day).
Example:
Time Zone : Europe/Amsterdam
Start Time: 2025-03-01T02:30:00+01:00 (Europe/Amsterdam) (1st March 2025, 2:30AM)
Interval Unit: DAILY
Repeat Interval: 1
Input to getFireTimeAfter(...): 2025-04-01T02:00:00+02:00 (1st April 2025, 2:00AM)
Expected Result:
2025-04-01T02:30:00+02:00 (Valid local time, next scheduled trigger after the input)
Actual Result:
2025-04-02T02:30:00+02:00 (Incorrectly skips a day)
Cause:
The issue stems from Quartz's reliance on java.util.Calendar.add(...) for time computation. On the DST "spring forward" day (March 30, 2025), Calendar.add(...) resolves the missing 02:30 hour to 01:30, effectively shifting the time backward by an hour. This leads to Quartz computing an incorrect fire time of 2025-04-01T01:30:00+02:00, which is less than the input time (2025-04-01T02:00), so it continues to the next day.
Only after computing the next day's time (2025-04-02T01:30:00+02:00) does Quartz apply DST hour preservation logic, resulting in 2025-04-02T02:30:00+02:00. However, the correct next trigger should have been 2025-04-01T02:30:00+02:00.
Impact:
Triggers may be skipped unexpectedly, especially when scheduled to fire at times that fall within missing DST hours (typically around 2:00–3:00 AM on spring-forward days).
Suggested Fix:
Refactor Quartz's internal time handling logic to use the Java 8+ java.time API (ZonedDateTime) instead of java.util.Calendar, which provides clearer semantics and correct handling of DST transitions, including missing or repeated hours.