Monthly jobs on every 30th day using Quartz

。_饼干妹妹 提交于 2019-12-05 15:14:45

The 'L' character is allowed for the day-of-month and day-of-week fields. This character > is short-hand for "last", but it has different meaning in each of the two fields. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". You can also specify an offset from the last day of the month, such as "L-3" which would mean the third-to-last day of the calendar month. When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing/unexpected results.

http://quartz-scheduler.org/api/2.0.0/org/quartz/CronExpression.html

new CronExpression("0 0 0 L JAN-DEC ? *");

Edit:

I would just do something like this then

Calendar tCalendar = Calendar.getInstance();
tCalendar.set(2009, Calendar.FEBRUARY/*int*/, 1); // for example Feb, 2009 -- day doesn't matter here
if(userSelectedDay > tCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) ){
    //Fix user day
    //fixedDay = tCalendar.getActualMaximum(Calendar.DAY_OF_MONTH)

    // Or, for that month 
    //new CronExpression("0 0 0 L JAN-DEC ? *");
}

Here is my solution. The idea is trying to build a set of cron expressions which will be passed in to a trigger builder.

  • If input day < 28. Use a single expression:
  • If input day = 29 or = 30. Use 2 expressions: The last day of Feb, and the specific day of the other months.
  • If input day = 31. Use the last day of the month.

    public static Set<String> byDay(int day) {
        if (day < 1 || day > 31) {
            throw new IllegalArgumentException("The input day must be in range: 1 <= day <= 31");
        }
        if (day <= 28) {
            return Collections.singleton(String.format("0 0 0 %d JAN-DEC ? *", day));
        }
        if (day == 29 || day == 30) {
            Set<String> expressions = new HashSet<String>();
            expressions.add("0 0 0 L FEB ? *");
            expressions.add(String.format("0 0 0 %d JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC ? *", day));
            return Collections.unmodifiableSet(expressions);
        }
        // day == 31
        return Collections.singleton("0 0 0 L * ? *");
    }
    

Try this snippet, it creates from 1 to 3 triggers (covers a whole year) depending on day:

        Set<Trigger> triggers = new HashSet<>(3);

        CronScheduleBuilder interval = CronScheduleBuilder.monthlyOnDayAndHourAndMinute(dayNumber, 0, 0);

        if (dayNumber > 28) {
            CronTrigger trigger28 = TriggerBuilder.newTrigger()
                    .withIdentity("payment_trigger28_" + merchantServiceTemplate.getId(), "payment_triggers")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 28 2 ? *"))
                    .endAt(merchantServiceTemplate.getEndScheduleDate())
                    .build();
            triggers.add(trigger28);

            if (dayNumber == 31) {
                CronTrigger trigger30 = TriggerBuilder.newTrigger()
                        .withIdentity("payment_trigger30_" + merchantServiceTemplate.getId(), "payment_triggers")
                        .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 30 4,6,9,11 ? *"))
                        .endAt(merchantServiceTemplate.getEndScheduleDate())
                        .build();
                triggers.add(trigger30);
            }
        }

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("payment_triggerAll_" + merchantServiceTemplate.getId(), "payment_triggers")
                .withSchedule(interval)
                .endAt(merchantServiceTemplate.getEndScheduleDate())
                .build();
        triggers.add(trigger);


        scheduler.scheduleJob(job, triggers, false);

As said before, you have to create multiple CronExpressions for each case of days in month, and a trigger for each one, and then add all the triggers to your required Job.

This is my version:

CronExpressions creation:

public static List<CronExpression> getCronExpressionList(int seconds, int minutes,
            int hours, int dayInMonth, Month month,
            DayOfWeek dayOfWeek) {
    final String monthsWith30Days = Month.APR + "," + Month.JUN + ","
                    + Month.SEP + "," + Month.NOV;
    List<CronExpression> crons = new LinkedList<CronExpression>();

    String timeString = String.format(("%s %s %s "), seconds, minutes,
                    hours, 0, 0, 0);
    String dateString = "%s %s %s";
    String cron = null;

    cron = timeString + String.format(dateString, dayInMonth, "*", "?");
    crons.add(new CronExpression(cron));
    if (dayInMonth > 28) {
        String febCron = timeString + getFebruarLastDayDateString(dateString);
        crons.add(new CronExpression(febCron));
        if (dayInMonth == 31) {
            String monthsWithThirtyDaysCron = timeString + String.format(dateString,
                    "L", monthsWith30Days, "?");
            crons.add(new CronExpression(monthsWithThirtyDaysCron));
        }
    }
    return crons;
}

private static String getFebruarLastDayDateString(String initialCron) 
               throws ParseException {
    return String.format(initialCron, "L", Month.FEB, "?");
}

Pay attention that I used "L" in the February cron, because otherwise you would have a bug in a leap year.

Trigger creation:

        Set<CronTrigger> triggers = new HashSet<>();

        int i = 1;
        for (CronExpression cronEx : cronsList) {
            CronTrigger trigger = newTrigger()
                    .withIdentity("trigger" + i, groupName)
                    .withSchedule(cronSchedule(cronEx))
                    .build();
                triggers.add(trigger);
                i++;
        }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!