11

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

  • Starting from Dec, 30, 2014
  • Execute each 30th day
  • Every 2nd month consequently
  • If month doesn't have 30th day, action should occur on the last day of month.

So, the resulting schedule will be:

  • Dec 30, 2014
  • Feb 28, 2015
  • Apr 30, 2015
  • ... and so on

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)?

n1k1ch
  • 2,594
  • 3
  • 31
  • 35
  • 4
    Dumbest implementation: fire the job daily, check if the day is valid at the start of the job. I don't know how practical that would be in your case (setup costs and the like), but it would allow you to use the date APIs, move the correct-day logic to a method, document and test it, etc. – ssube Sep 24 '14 at 14:03

1 Answers1

9

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.

Kraiss
  • 919
  • 7
  • 22
  • Nice one! Upvoting, coz now I have something to stick to :). I thought maybe there are either more "scheduled" way to do this without additional calculations or some Quartz methods to add specific dates to CalendarIntervalTrigger... I'll wait some time and, if no, I will accept your answer. – n1k1ch Sep 24 '14 at 14:21
  • 1
    I enhance the code a little bit, you'll only need to retrieve your 'real' job detail and scheduler reference in the 'execute' method and the rest should work ;) – Kraiss Sep 24 '14 at 14:30
  • Yes you can set on every trigger a end date. See documentation of TriggerBuilder : http://www.quartz-scheduler.org/api/2.2.1/org/quartz/TriggerBuilder.html#endAt(java.util.Date) – Kraiss Apr 07 '16 at 14:44
  • Why is `withIntervalInMonths(2)` instead of `withIntervalInMonths(1)` since it's every month? – ps0604 Mar 22 '19 at 12:04
  • 1
    Because the question was about "every other month" – n1k1ch Mar 26 '19 at 11:38
  • 1
    It worked like a charm for monthly as well as weekly schedule :) Thank you !! – DfrDkn Mar 28 '22 at 02:43