使用Java中的Cron Expression查找上次触发时间

时间:2010-04-26 02:51:08

标签: java scheduling quartz-scheduler crontrigger

在Java中是否有办法从Cron Expression中找到“Last Fired Time”。

E.g。如果现在= 2010年4月25日晚上10点,cron表达“0 15 10?* *”(石英)应该会在2010年4月25日上午10:15返回

注意: 1)我不在乎我们是否使用标准的cron表达式(如Unix和Quartz)或不那么受欢迎的表达式,如果他们可以获取正确的“最后发射时间” 2)它也不是字面上的“最后火焰时间”,因为触发器可能没有被触发,但从逻辑上讲应该有一种方法可以告诉它(最终会)最后一次触发。

9 个答案:

答案 0 :(得分:3)

首先,我不知道现有的库支持这一点。 Quartz可能,但标准的Java类库当然不会。

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

答案 1 :(得分:3)

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表达式。

答案 2 :(得分:2)

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

请参阅the Javadoc for the CronExpression class,其中有一个名为getTimeBefore的方法。即,

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

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

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

答案 3 :(得分: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);

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

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 = ...
    }
}

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

答案 4 :(得分:1)

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)));
    }
}

答案 5 :(得分:0)

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

示例:

public class TestJob implements Job {

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

        context.getPreviousFireTime(); 
    }
}

如果您使用的是context.getTrigger().getPreviousFireTime(),那么您将拥有当前正在运行的作业的触发时间。

答案 6 :(得分:0)

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

如何获取上次触发时间?

如果您要操纵0 0 0 * * ?这样的基本CRON(每天凌晨00:00:00),则可以使用JoãoNeves的解决方案(使用com.cronutils.model.time.ExecutionTime)。

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

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

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


如何验证作业已触发?

我找到的解决方案是使用Quartz(CronExpression)的getNextValidTimeAfter。这个很好用。 您会问我在寻找先前有效时间时我在说什么!你是对的,给我一秒钟!

让我们想象一下,我们每月有一次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好

...

•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

答案 7 :(得分:-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

中的源代码

答案 8 :(得分:-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));

我在此获取当前时间与下一次触发时间之间的时差 并排除当前时间与当前时间的差值,以便我们获得上次触发的时间