javaquartz-schedulerjboss-eap-6

Can I use Quartz Scheduler to fire every other month on the 30th day but if month has no 30th day, fire on the last day?


Can I use Quartz Scheduler library to create schedule with following settings?:

So, the resulting schedule will be:

From what I've learned:

  1. CronTrigger doesn't allow to do so (it could be set up only to be triggered on specific months and not on intervals),
  2. CalendarIntervalTrigger will skip months that don't have 30th day (trigger created by following code)

    try {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.start();
    
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("HelloJob_CalendarIntervaled", "calendarIntervaled")
                .build();
    
        Calendar decemberThirty = Calendar.getInstance();
        decemberThirty.set(Calendar.YEAR, 2014);
        decemberThirty.set(Calendar.MONTH, Calendar.DECEMBER);
        decemberThirty.set(Calendar.DAY_OF_MONTH, 30);
    
        CalendarIntervalTrigger calendarIntervalTrigger = newTrigger()
                .withIdentity("calendarIntervalTrigger", "calendarIntervaled")
                .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
                        .withIntervalInMonths(2))
                .startAt(decemberThirty.getTime())
                .forJob(jobDetail)
                .build();
    
        scheduler.scheduleJob(jobDetail, calendarIntervalTrigger);
    
        System.out.println(calendarIntervalTrigger.getNextFireTime());
    } catch (SchedulerException e) {
        e.printStackTrace();
    }
    

If no, are there any alternatives (it should work on JBoss eap 6.2.0)?


Solution

  • You can achieve this in Quartz but you got to twist the normal behaviour by using a CalendarIntervalTrigger to trigger a Job that calculate when your 'real' Job should be scheduled.

    You schedule a trigger that fire every 1st of your scheduling months :

    [...]
    JobDetail jobDetail = newJob(SchedulingCalculationJob.class)
            .withIdentity("SchedulingCalculation_CalendarIntervaled", "calendarIntervaled")
            .build();
    
    CalendarIntervalTrigger calendarIntervalTrigger = newTrigger()
            .withIdentity("calendarIntervalCalculationTrigger", "calendarIntervaled")
            .withSchedule(calendarIntervalSchedule()
                    .withIntervalInMonths(2))
            .startAt(decemberFirst.getTime())
            .forJob(jobDetail)
            .build();
    
    scheduler.scheduleJob(jobDetail, calendarIntervalTrigger);
    

    And in the SchedulingCalculationJob Job, you calculate your 'real' Job scheduling day :

    public class SchedulingCalculationJob implements Job {
    
        public void execute(JobExecutionContext context)
                throws JobExecutionException {
    
            Calendar calendar = calculateJobFiringDate();
    
            // Create and schedule a dedicated trigger
            Trigger calculateFiring = calculateFiring = newTrigger()
                   .withSchedule(SimpleSchedulerBuilder.simpleScheduler())
                   .startAt(calendar.getTime())
                   .forJob(yourRealJobDetail)
                   .build();
    
            scheduler.scheduleJob(yourRealJobDetail, calculateFiring);
        }
    
        public static Calendar calculateJobFiringDate() {
            Calendar result = Calendar.getInstance();
    
            // Set up the scheduling day
            if (isThereThirtyDaysInCurrentMonth()) {
                // the 30th of the current month
                calendar.set(Calendar.DAY_OF_MONTH, 30);
            } else {
                // the last day of the current month
                calendar.add(Calendar.MONTH, 1);
                calendar.add(Calendar.DATE, -1);
            }
    
            // Set up time of day
            calendar.set(Calendar.HOUR, ...);
            calendar.set(Calendar.MINUTE, ...);
            calendar.set(Calendar.SECOND, ...);
    
            return result;
        }
    
        public static boolean isThereThirtyDaysInCurrentMonth() {
            Calendar thirtydaysInCurrentMonthCalendar = Calendar.getInstance();
    
            Integer currentMonth = thirtydaysInCurrentMonthCalendar.get(Calendar.MONTH);
            thirtydaysInCurrentMonthCalendar.add(Calendar.DATE, 29);
    
            return (currentMonth == thirtydaysInCurrentMonthCalendar.get(Calendar.MONTH);
        }
    }
    

    It's a bit sioux but I already use it and i works fine.