CalendarIntervalTrigger

In the Quartz scheduling framework, CalendarIntervalTrigger serves as a robust tool for time-based job execution driven by calendar units. Unlike SimpleTrigger, which relies strictly on fixed-millisecond intervals, this trigger increments time based on practical calendar units such as seconds, minutes, hours, days, weeks, months, and years. It natively accommodates calendar boundaries, including varying month lengths, leap years, and daylight saving time (DST) transitions. This makes it ideal for recurring tasks that need to run "every $N$ calendar units," such as nightly database backups, quarterly financial report generation, or weekly status updates.

Constructed via CalendarIntervalScheduleBuilder, CalendarIntervalTrigger requires configuring key parameters such as the interval magnitude, the time unit, start and end boundaries, and misfire handling policies.
The behavior of a CalendarIntervalTrigger is defined by several essential attributes:

  • Interval: A positive integer representing the frequency of the repeat units.
  • IntervalUnit: The temporal unit derived from the IntervalUnit enumeration, supporting SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, and YEAR.
  • StartTime: The initial activation timestamp. All subsequent execution times are calculated relative to this anchor point.
  • EndTime: An optional boundary that specifies when the trigger should stop firing.
  • PreserveHourOfDayAcrossDaylightSavings: A boolean flag determining whether to maintain the specific hour of the day during DST shifts.
  • SkipDayIfHourDoesNotExist: A boolean option that dictates whether to bypass a day entirely if the targeted hour ceases to exist due to a DST transition.
  • MisfireInstruction: The strategy applied when a scheduled firing is missed due to system downtime or thread pool exhaustion.

The interplay between StartTime and IntervalUnit is particularly crucial, as it establishes the mathematical foundation for every future execution slot.

Comparison with SimpleTrigger and CronTrigger

  • SimpleTrigger: Operates on fixed millisecond offsets (e.g., every 5000 milliseconds). It lacks calendar awareness, making it impossible to express requirements like "once a month."
  • CronTrigger: Utilizes powerful cron expressions to target precise schedules, such as "10:00 AM on the first day of every month." While highly flexible, the syntax can be difficult to read and maintain.
  • CalendarIntervalTrigger: Occupies the middle ground. It combines the programmatic simplicity of a fixed interval with the intelligence of calendar-unit awareness. It is well-suited for repetitive, interval-based tasks, though it cannot handle rigid calendar constraints like specific weekdays of a month.

For instance, scheduling a task every two months requires a simple configuration of interval 2 and unit MONTH using CalendarIntervalTrigger. Achieving the same semantic intent with a CronTrigger would require a complex expression like 0 0 0 1 */2 * ?, which strictly locks execution to the first day of the bi-monthly cycle.

Implementation Example

The following Java snippet demonstrates how to configure and schedule a trigger that fires daily at 10:00 AM:

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class CalendarIntervalExample {
    public static void main(String[] args) throws SchedulerException {
        JobDetail job = JobBuilder.newJob(MyJob.class)
                .withIdentity("dailyJob", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("dailyTrigger", "group1")
                .startAt(DateBuilder.todayAt(10, 0, 0))
                .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
                        .withIntervalInDays(1)
                        .preserveHourOfDayAcrossDaylightSavings(true)
                        .skipDayIfHourDoesNotExist(false))
                .build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        scheduler.scheduleJob(job, trigger);
    }
}

To adjust this schedule to a monthly frequency, developers simply swap withIntervalInDays(1) for withIntervalInMonths(1). The time of day remains anchored to whatever hour, minute, and second were specified in the startAt method.

Handling Daylight Saving Time and Calendar Boundaries

Daylight saving time (DST) introduces complex edge cases into time-based scheduling. CalendarIntervalTrigger addresses these challenges with two dedicated configuration settings:

  • preserveHourOfDayAcrossDaylightSavings(true): Ensures that the targeted hour of execution remains consistent even when clocks are shifted forward or backward. If a job is scheduled for 2:30 AM but that specific time is skipped during a DST transition, enabling this property shifts execution to 3:30 AM.
  • skipDayIfHourDoesNotExist(true): Instructs the scheduler to skip the affected day entirely if the designated hour is completely missing due to a clock adjustment.

Additionally, because the trigger relies on the internal add mechanics of java.util.Calendar, starting on the final day of a month can introduce drift. For example, initiating a monthly interval on January 31st results in subsequent fires on February 28th (or 29th), March 28th, and so forth—not the 31st of each month. Workloads requiring strict end-of-month alignment should utilize CronTrigger instead.

Best Practices and Common Use Cases

Ideal Scenarios:

  • Periodic routines running at fixed calendar intervals (daily, weekly, monthly, or yearly).
  • Workloads that require automated adjustments for varying month lengths and leap years.
  • Requirements where cron syntax is overly complex, and a fluent Java API is preferred.

Key Considerations:

  • Future trigger timestamps are persistently derived from the initial StartTime, meaning slight execution delays will not compound into permanent schedule shifts.
  • Select a misfire policy aligned with your application's tolerance for missed executions.
  • Decorate your job classes with @DisallowConcurrentExecution if the processing duration might exceed the scheduled interval window.
  • For sophisticated calendar requirements—such as executing on the second Tuesday of every month—CronTrigger remains the superior choice.

By combining an intuitive API with calendar-aware scheduling, CalendarIntervalTrigger serves as an essential component in Quartz for managing predictable, recurring periodic tasks.