Skip to content

CalendarIntervalTriggerImpl#getFireTimeAfter returns incorrect date when trigger time falls in DST missing hour (Java Calendar issue) #1352

@isha2509

Description

@isha2509

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions