5

伙计们,

我有用户安排的每月工作(使用 Quartz)。用户提供开始日期或第一个运行的作业,它可以是 1-31 月的任何一天

我的问题是如何使用 cron 触发器来安排这个,请记住并非所有月份都有第 31、30、29 天。在这种情况下,作业应该在本月最接近的前一天运行。因此,假设 4 月只有 30 天,因此作业必须在 4 月 30 日运行。

可以使用单个 cron 触发器来完成吗?还是应该是触发器的组合?我尝试使用 CronExpression 来查看它如何处理此类情况:

CronExpression ce = new CronExpression("0 0 0 30 JAN-DEC ? *");
Date nextValidTime = ce.getNextValidTimeAfter(//**27th of February**//);

我的 nextValidTime 等于30th of March,所以 cron 只是“跳过”了二月。任何帮助将不胜感激。提前致谢。

4

4 回答 4

6

'L' 字符允许用于日期和星期几字段。这个字符 > 是“last”的简写,但它在两个字段中的每一个都有不同的含义。例如,day-of-month 字段中的值“L”表示“月的最后一天”——非闰年 1 月的第 31 天,2 月的第 28 天。如果单独在星期几字段中使用,它仅表示“7”或“SAT”。但如果在星期几字段中使用另一个值,则表示“本月的最后 xxx 天”——例如“6L”表示“本月的最后一个星期五”。您还可以指定与该月最后一天的偏移量,例如“L-3”,表示日历月的倒数第三天。使用“L”选项时,

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

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

编辑:

那时我会做这样的事情

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 ? *");
}
于 2012-07-11T22:20:37.500 回答
1

这是我的解决方案。这个想法是尝试构建一组将传递给触发器构建器的 cron 表达式。

  • 如果输入日期 < 28。使用单个表达式:
  • 如果输入 day = 29 或 = 30。使用 2 个表达式:2 月的最后一天,以及其他月份的特定日期。
  • 如果输入天 = 31。使用该月的最后一天。

    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 * ? *");
    }
    
于 2018-08-08T08:19:01.690 回答
0

如前所述,您必须为每个月的天数创建多个 CronExpressions,并为每个天数创建一个触发器,然后将所有触发器添加到所需的作业中。

这是我的版本:

CronExpressions 创建:

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, "?");
}

请注意,我在二月的 cron 中使用了“L”,否则在闰年会出现错误。

触发器创建:

        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++;
        }
于 2014-10-12T12:04:44.230 回答
0

试试这个片段,它会根据日期创建 1 到 3 个触发器(涵盖一整年):

        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);
于 2013-12-04T13:59:50.977 回答