在 Java 中使用 Cron 表达式查找上次触发时间

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2710883/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 22:28:09  来源:igfitidea点击:

Finding Last Fired time using a Cron Expression in Java

javaschedulingquartz-schedulercrontrigger

提问by a-sak

Is there a way in Java to find the "Last Fired Time" from a Cron Expression.

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

E.g. If now = 25-Apr-2010 10pm, cron expression "0 15 10 ? * *" (quartz) should return me 25-Apr-2010 10:15am

例如,如果 now = 25-Apr-2010 10pm,cron 表达式“0 15 10 ? * *”(石英)应该返回我 25-Apr-2010 10:15am

Note: 1) I do not care if we use standard cron expressions (like Unix and Quartz) or less popular ones if they can fetch me the correct "Last Fired Time" 2) Also it is not literally "Last Fire time" as the trigger may not have fired, but logically there should be a way of telling when it (would have) fired last.

注意:1)我不在乎我们是使用标准的 cron 表达式(如 Unix 和 Quartz)还是不太流行的表达式,如果它们可以为我获取正确的“上次发射时间”2)它也不是字面上的“上次发射时间”作为触发器可能没有触发,但从逻辑上讲,应该有一种方式告诉它(应该)什么时候触发最后一次。

回答by Jo?o Neves

cron-utilsis an opensource Java library to parse, validate, migrate crons that supports the operation you need. To get the previous date from a cron before a given time simply:

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

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

Bear in mind that in its current state it is a bit buggy and may not compute correctly for more complex cron expressions.

请记住,在当前状态下,它有一些问题,可能无法正确计算更复杂的 cron 表达式。

回答by Stephen C

First, I am not aware of an existing library that supports this. Quartz might, but the standard Java class libraries certainly don't.

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

Second, strictly speaking what you are asking fororiginally asked for is impossible. The best that a library can tell you is that the cron expression would haveor should havefired. The only thing that could (theoretically) tell you the last time that a cron expression actually fired is the scheduler instance itself.

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

回答by waxwing

Quartz seems to have some library support for cron expressions.

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

See the Javadoc for the CronExpression class, which has a method called getTimeBefore. I.e.,

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

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

It might depend on the Quartz version whether this works.

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

Looking at the latest source (version 2.3.0 at time of writing) this method has not been implemented and always returns null.

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

回答by darrenmc

A solution I have found with Quartz is to go back one time interval for the trigger and calculate what would have been the next firing time. By iterating through all the triggers the most recent time that a trigger should have fired in the past can be determined.

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



Calculate the interval between each firing:

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

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

Find the next firing time for one time until interval in the past:

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

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

I have found that if you are using a CronTriggerthis prevents you asking for a fire time in the past. To work around this I modify the start time, so the above snippet becomes:

我发现如果您使用的是 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

Iterate through all of the triggers and find the one that is most recently in the past:

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

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

I'll leave it as an exercise to the reader to refactor this into helper methods or classes. I actually use the above algorithm in a subclassed delegating trigger that I then place in a set sorted by previous firing times.

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

回答by iPhrends

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

回答by Emerica

It's amazing that there is still no quartz method for getting the previous fired time based on a CronExpression...

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

How to get last fired time ?

如何获得上次发射时间?

If you are manipulating basic CRON like 0 0 0 * * ?(At 00:00:00am every day) you can use Jo?o Neves' solution (using com.cronutils.model.time.ExecutionTime).

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

Otherwise if you are manipulating complex CRON like 0 30 11 ? * MON,THU *it will not works. You'll get random results (I had a Wednesday for this one...).

否则,如果您像操纵复杂的 CRON 一样0 30 11 ? * MON,THU *,它将不起作用。你会得到随机的结果(我有一个星期三……)。

Edit: I did other tests and it looks working better with the latest version (previous tests were made with version < 3.1.6). Note: You need Java 8 if you want to use version > 3.1.6.

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

The solution you can use is to storeit when your job is triggered.

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



How to verify that the job has been triggered ?

如何验证作业已被触发?

The solution I found is to use getNextValidTimeAfterfrom Quartz (CronExpression). This one works fine. You'll ask me what I'm talking about as we are looking for the previous valid time ! You are right, give me one second !

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

Let's imagine we have a once a month CRON (0 0 16 1 * ? = At 16:00:00pm, on the 1st day, every month) and we want to check everyday that the previous execution worked. You'll have to store the getNextValidTime at each execution and compare this date with today's date. eg (format DD/MM/YYYY):

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

? 01/01/2019 → job triggered, we store next fire time (let's call it nextFireTime):

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

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

? 02/01/2019 → verification of the day: 02/01/2019 < 01/02/2019 OK

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

...

...

? 01/02/2019 → let's imagine server is down, the job is not triggered.

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

? 02/02/2019 → server on, verification of the day: 02/02/2019 > 01/02/2019 KO !

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

→ We know the previous fire time hasn't worked. You can know do what you want (trigger the job and store the new nextFireTime).

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



Another option that may interest you, see MISFIRE_INSTRUCTION_FIRE_NOW.

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

The job is executed immediately after the scheduler discovers misfire situation. This is the smart policy. Example scenario: you have scheduled some system clean up at 2 AM. Unfortunately the application was down due to maintenance by that time and brought back on 3 AM. So the trigger misfired and the scheduler tries to save the situation by running it as soon as it can - at 3 AM.

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

From (https://dzone.com/articles/quartz-scheduler-misfire)

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

e.g.:

例如:

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

Official doc: https://www.quartz-scheduler.org/api/2.1.7/org/quartz/CronTrigger.html

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

回答by Radu Linu

In case you are using org.quartz, is possible to use context.getPreviousFireTime();

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

Example:

例子:

public class TestJob implements Job {

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

        context.getPreviousFireTime(); 
    }
}

If you are using context.getTrigger().getPreviousFireTime(), you will have the triggered time of the Job that is running at the moment.

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

回答by Chandra Prakash Reddy

Its working solution

其工作解决方案

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

Here im getting the difference between the current time and next fire time and exluding this difference time from current time so that we will get last fired time

在这里,我得到了当前时间和下一次开火时间之间的差异,并从当前时间中排除了这个差异时间,以便我们得到上次开火时间

回答by Bhuwan Prasad Upadhyay

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

Source Code in https://github.com/devbhuwan/cron-micro-utils

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