12

Java 中有没有办法从 Cron 表达式中找到“上次触发时间”?

例如,如果now = 25-Apr-2010 10PM,并且 cron 表达式是0 15 10 ? * *(quartz),它应该返回25-Apr-2010 10:15AM

笔记:

  1. 我不在乎我们是否使用标准的 cron 表达式(如 Unix 和 Quartz)或不太流行的表达式,如果它们可以为我获取正确的“Last Fired Time”
  2. 此外,它并不是字面上的“最后一次触发时间”,因为触发器可能没有触发,但从逻辑上讲,应该有一种方法可以告诉它(会)最后一次触发的时间。
4

9 回答 9

8

cron-utils是一个开源 Java 库,用于解析、验证、迁移支持您需要的操作的 crons。要在给定时间之前从 cron 获取上一个日期,只需:

//Get date for last execution
DateTime now = DateTime.now();
ExecutionTime executionTime = ExecutionTime.forCron(parser.parse("* * * * * * *"));
DateTime lastExecution = executionTime.lastExecution(now));

请记住,在其当前状态下,它有点错误,可能无法正确计算更复杂的 cron 表达式。

于 2016-01-11T13:29:28.853 回答
3

首先,我不知道支持这个的现有库。Quartz 可能,但标准 Java 类库肯定不会。

其次,严格来说,您所要求的最初要求是不可能的。库可以告诉您的最好的信息是 cron 表达式已经应该已经触发。唯一可以(理论上)告诉您最后一次实际触发 cron 表达式的是调度程序实例本身。

于 2010-04-26T04:21:17.490 回答
2

Quartz 似乎对 cron 表达式有一些库支持。

请参阅CronExpression 类的 Javadoc,该类有一个名为 的方法getTimeBefore。IE,

CronExpression cron = new CronExpression("0 15 10 ? * *");
Date today = new Date();
Date previousExecution = cron.getTimeBefore(today);

这可能取决于 Quartz 版本是否有效。

查看最新的源代码(撰写本文时版本为 2.3.0),此方法尚未实现,并且始终返回 null。

于 2010-04-27T05:34:15.987 回答
2
import org.springframework.scheduling.support.CronSequenceGenerator;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

public class DateCalendarUtil {

    public static Date lastRunOn(String cronExpression) {
        final Date nextExecution = fromLocalDateTime(toLocalDate(nextCronDate(cronExpression)).atTime(23, 59, 59));
        return subtract(nextExecution, numberOfDays(nextExecution, nextCronDate(cronExpression, nextExecution)).intValue());
    }

    /**
     * Converts {@link Date} to {@link LocalDate} with default system {@link ZoneId}
     *
     * @param date to be converted to {@link LocalDate}
     * @return converted {@link Date}
     */
    public static LocalDate toLocalDate(Date date) {
        return toLocalDate(date, ZoneId.systemDefault());
    }

    /**
     * Converts {@link Date} to {@link LocalDate} with provided {@link ZoneId}
     * @param date to be converted to {@link LocalDate}
     * @param zoneId with which {@link Date} will be converted
     * @return converted {@link Date}
     */
    public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
        return date.toInstant().atZone(zoneId).toLocalDate();
    }

    /**
     * Converts {@link Date} to {@link LocalDateTime} with provided {@link ZoneId}
     * @param date to be converted to {@link LocalDateTime} with provided {@link ZoneId}
     * @param zoneId with which {@link Date} will be converted
     * @return converted {@link Date}
     */
    public static LocalDateTime toLocalDateTime(Date date, ZoneId zoneId) {
        return date.toInstant().atZone(zoneId).toLocalDateTime();
    }

    /**
     * Converts {@link Date} to {@link LocalDateTime} with system default {@link ZoneId}
     *
     * @param date to be converted to {@link LocalDateTime}
     * @return converted {@link Date}
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        return toLocalDateTime(date, ZoneId.systemDefault());
    }

    /**
     * Converts {@link LocalDate} to {@link Date} with default system {@link ZoneId}
     * @param localDate to be converted to {@link Date}
     * @return converted {@link LocalDate}
     */
    public static Date fromLocalDate(LocalDate localDate) {
        return fromLocalDate(localDate, ZoneId.systemDefault());
    }

    /**
     * Converts {@link LocalDate} to {@link Date} with provided {@link ZoneId}
     * @param localDate to be converted to {@link Date}
     * @param zoneId with which {@link LocalDate} converted
     * @return converted {@link LocalDate}
     */
    public static Date fromLocalDate(LocalDate localDate, ZoneId zoneId) {
        return Date.from(localDate.atStartOfDay(zoneId).toInstant());
    }

    /**
     * Converts {@link LocalDateTime} to {@link Date} with default system {@link ZoneId}
     *
     * @param localDateTime to be converted to {@link Date}
     * @return converted {@link LocalDateTime}
     */
    public static Date fromLocalDateTime(LocalDateTime localDateTime) {
        return fromLocalDateTime(localDateTime, ZoneId.systemDefault());
    }

    /**
     * Converts {@link LocalDateTime} to {@link Date} with provided {@link ZoneId}
     *
     * @param localDateTime to be converted to {@link Date}
     * @param zoneId        with which localDateTime converted to {@link Date}
     * @return converted {@link Date}
     */
    private static Date fromLocalDateTime(LocalDateTime localDateTime, ZoneId zoneId) {
        return Date.from(localDateTime.atZone(zoneId).toInstant());
    }

    public static Date yesterday() {
        return yesterday(TimeZone.getDefault());
    }

    public static Date yesterday(TimeZone timezone) {
        return subtract(new Date(), 1, timezone);
    }

    /**
     * Generates start time of give date with system default {@link TimeZone}
     * @param date Date of which start time to be generated
     * @return Date with start time as 00:00:00
     */
    public static Date startTime(Date date) {
        return startTime(date, TimeZone.getDefault());
    }

    /**
     * Generates start time of give date with provided {@link TimeZone}
     * @param date Date of which start time to be generated
     * @param timeZone with which {@link Calendar} created
     * @return Date with start time as 00:00:00
     */
    public static Date startTime(Date date, TimeZone timeZone) {
        Calendar calendar = Calendar.getInstance(timeZone);
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    /**
     * Generates end time of give date with system default {@link TimeZone}
     * @param date Date of which end time to be generated
     * @return Date with end time as 23:59:59
     */
    public static Date endTime(Date date) {
        return endTime(date, TimeZone.getDefault());
    }

    /**
     * Generates end time of give date with provided {@link TimeZone}
     * @param date Date of which end time to be generated
     * @param timeZone with which {@link Calendar} created
     * @return Date with end time as 23:59:59
     */
    public static Date endTime(Date date, TimeZone timeZone) {
        Calendar calendar = Calendar.getInstance(timeZone);
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        return calendar.getTime();
    }

    /**
     * Calculates number of days between from and to
     * @param from start Date
     * @param to end date
     * @return number of days including last date
     */
    public static Long numberOfDays(Date from, Date to) {
        return TimeUnit.DAYS.convert(to.getTime() - from.getTime(), TimeUnit.MILLISECONDS) + 1;
    }

    /**
     * Gives next {@link Date} from given cron expression
     * @param cronExpression cron expression
     * @return next {@link Date}
     */
    public static Date nextCronDate(String cronExpression) {
        return nextCronDate(cronExpression, new Date());
    }

    public static Date nextCronDate(String cronExpression, Date date) {
        CronSequenceGenerator generator = new CronSequenceGenerator(cronExpression, TimeZone.getTimeZone("IST"));
        return DateCalendarUtil.fromLocalDate(DateCalendarUtil.toLocalDate(generator.next(date)));
    }
}
于 2019-09-20T09:28:05.453 回答
1

我在 Quartz 中找到的一种解决方案是返回触发器的一个时间间隔并计算下一次触发时间。通过遍历所有触发器,可以确定触发器在过去应该触发的最近时间。


计算每次发射之间的间隔:

Date nextFireTime = trigger.getNextFireTime();
Date subsequentFireTime = trigger.getFireTimeAfter(nextFireTime);
long interval = subsequentFireTime.getTime() - nextFireTime.getTime();

查找下一次触发时间,直到过去的时间间隔:

Date previousPeriodTime = new Date(System.currentTimeMillis() - interval);
Date previousFireTime = trigger.getFireTimeAfter(previousPeriodTime);

我发现如果您使用的是 aCronTrigger这可以防止您在过去要求开火时间。为了解决这个问题,我修改了开始时间,所以上面的代码片段变成了:

Date originalStartTime = trigger.getStartTime(); // save the start time
Date previousPeriodTime = new Date(originalStartTime.getTime() - interval);
trigger.setStartTime(previousPeriodTime);
Date previousFireTime = trigger.getFireTimeAfter(previousPeriodTime);
trigger.setStartTime(originalStartTime); // reset the start time to be nice

遍历所有触发器并找到最近的触发器:

for (String groupName : scheduler.getTriggerGroupNames()) {
    for (String triggerName : scheduler.getTriggerNames(groupName)) {
        Trigger trigger = scheduler.getTrigger(triggerName, groupName);
        // code as detailed above...
        interval = ...
        previousFireTime = ...
    }
}

我将把它作为练习留给读者,以将其重构为辅助方法或类。我实际上在一个子类委托触发器中使用了上述算法,然后我将其放入按先前触发时间排序的集合中。

于 2012-01-19T16:24:18.540 回答
0

如果您使用的是 org.quartz,可以使用context.getPreviousFireTime();

例子:

public class TestJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        context.getPreviousFireTime(); 
    }
}

如果您使用context.getTrigger().getPreviousFireTime(),您将获得当前正在运行的 Job 的触发时间。

于 2016-11-25T08:55:40.617 回答
0

令人惊讶的是,仍然没有基于 CronExpression 获取先前触发时间的石英方法......

如何获得最后一次解雇时间?

如果您正在操作基本的 CRON,例如0 0 0 * * ?(每天上午 00:00:00),您可以使用 João Neves 的解决方案(使用 com.cronutils.model.time.ExecutionTime)。

否则,如果您正在操纵复杂的 CRON 0 30 11 ? * MON,THU *,它将无法正常工作。你会得到随机结果(我有一个星期三的这个......)。

编辑:我做了其他测试,最新版本看起来效果更好(以前的测试是用 < 3.1.6 的版本进行的)。注意:如果要使用版本 > 3.1.6,则需要 Java 8。

您可以使用的解决方案是在您的工作被触发时存储它。


如何验证作业是否已触发?

我找到的解决方案是使用getNextValidTimeAfterQuartz (CronExpression)。这个工作正常。你会问我在说什么,因为我们正在寻找以前的有效时间!你是对的,给我一秒钟!

假设我们有一个每月一次的 CRON(0 0 16 1 * ? = 每月第一天下午 16:00:00),我们想每天检查前一次执行是否有效。您必须在每次执行时存储 getNextValidTime 并将此日期与今天的日期进行比较。例如(格式 DD/MM/YYYY):

• 01/01/2019 → 作业触发,我们存储下一次触发时间(我们称之为nextFireTime):

CronExpression trigger = new CronExpression("0 0 16 1 * ?");
Date nextFireTime = trigger.getNextValidTimeAfter(new Date());
// = 01/02/2019

• 02/01/2019 → 当天验证:02/01/2019 < 01/02/2019 OK

...

• 01/02/2019 → 假设服务器已关闭,作业未触发。

• 02/02/2019 → 服务器开启,当天验证:02/02/2019 > 01/02/2019 KO !

→ 我们知道之前的开火时间没有奏效。你可以知道做你想做的事(触发工作并存储新的 nextFireTime)。


您可能感兴趣的另一个选项,请参阅MISFIRE_INSTRUCTION_FIRE_NOW

该作业在调度程序发现失火情况后立即执行。这是明智的政策。示例场景:您已安排在凌晨 2 点进行一些系统清理。不幸的是,由于当时的维护,该应用程序已关闭,并在凌晨 3 点恢复。所以触发器失火了,调度程序试图通过尽快运行来挽救这种情况——在凌晨 3 点。

来自(https://dzone.com/articles/quartz-scheduler-misfire

例如:

Trigger trigger = newTrigger().
 withSchedule(
  cronSchedule("0 0 9-17 ? * MON-FRI").
   withMisfireHandlingInstructionFireAndProceed()
 ).
 build();

官方文档:https ://www.quartz-scheduler.org/api/2.1.7/org/quartz/CronTrigger.html

于 2019-10-03T09:28:03.487 回答
-1
public class CronMircoUtils {

    /**
     * Use this method to calculate previous valid date for cron
     * @param ce CronExpression object with cron expression
     * @param date Date for find previous valid date
     * @return
     */
    public static Date getPreviousValidDate(CronExpression ce, Date date) {
        try {
            Date nextValidTime = ce.getNextValidTimeAfter(date);
            Date subsequentNextValidTime = ce.getNextValidTimeAfter(nextValidTime);
            long interval = subsequentNextValidTime.getTime() - nextValidTime.getTime();
            return new Date(nextValidTime.getTime() - interval);
        } catch (Exception e) {
            throw new IllegalArgumentException("Unsupported cron or date", e);
        }
    }
}

源代码在https://github.com/devbhuwan/cron-micro-utils

于 2015-07-06T05:46:21.073 回答
-1

其工作解决方案

   pom :
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>

   javaCode:
        //Provide your format of date  want
        String format="dd-MMM-YYYY hh:mm:ss";
        //Provide your cron expression you want
        String cronExpression="* * 12 ? * FRI *";
        SimpleDateFormat sdf=new SimpleDateFormat(format);
        CronExpression expression = new CronExpression(cronExpression);
        Date  currentDate=new Date();
        Date nextDate=expression.getNextValidTimeAfter(currentDate);
        long interval = nextDate.getTime()-currentDate.getTime();
        Date previousDate= new Date(currentDate.getTime() - interval);
        System.out.Println(sdf.format(previousDate));

在这里,我得到了当前时间和下一次触发时间之间的差异,并从当前时间排除了这个差异时间,以便我们获得最后一次触发时间

于 2018-07-12T11:01:54.210 回答