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?
Asked Answered
R

1

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

Ramekin answered 24/9, 2014 at 13:40 Comment(1)
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.Derk
P
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.

Perish answered 24/9, 2014 at 14:7 Comment(6)
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.Ramekin
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 ;)Perish
Yes you can set on every trigger a end date. See documentation of TriggerBuilder : quartz-scheduler.org/api/2.2.1/org/quartz/…Perish
Why is withIntervalInMonths(2) instead of withIntervalInMonths(1) since it's every month?Expatriate
Because the question was about "every other month"Ramekin
It worked like a charm for monthly as well as weekly schedule :) Thank you !!Ardin

© 2022 - 2024 — McMap. All rights reserved.