Поиск времени последнего запуска с использованием выражения Cron в Java

Есть ли способ в Java найти время последнего запуска из выражения Cron?

Например. Если now = 25-Apr-2010 10PM, а выражение cron равно 0 15 10 ? * * (кварц), оно должно вернуть 25-Apr-2010 10:15AM.

Примечание:

  1. Меня не волнует, используем ли мы стандартные выражения cron (например, Unix и Quartz) или менее популярные, если они могут получить мне правильное время последнего запуска.
  2. Кроме того, это не буквально время последнего срабатывания, поскольку триггер мог не сработать, но логически должен быть способ определить, когда он (сработал) последним.

person a-sak    schedule 26.04.2010    source источник


Ответы (9)


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

Имейте в виду, что в своем текущем состоянии он немного глючит и может неправильно вычислять более сложные выражения cron.

person João Neves    schedule 11.01.2016
comment
привет Жоао, Это работает, даже если задание не запускается (пример: сервер отключен)? - person Marin; 14.09.2016
comment
@NunoMarinho Да, это просто вычисляет предыдущую дату из выражения cron. Сама библиотека ничего не знает о том, что вы делаете с выражением cron. Эта библиотека не планирует выполнение действий или заданий, она просто вычисляет даты на основе выражений cron. Однако я не использовал эту библиотеку в течение длительного времени, убедитесь, что она соответствует вашим потребностям, и тщательно протестируйте свое приложение после ее использования. - person João Neves; 14.09.2016

Во-первых, я не знаю о существующей библиотеке, поддерживающей это. Quartz может, но стандартные библиотеки классов Java, конечно, нет.

Во-вторых, строго говоря, то, о чем вы просите изначально просили, невозможно. Лучшее, что может сказать вам библиотека, это то, что выражение cron должно было или должно было сработать. Единственное, что может (теоретически) сказать вам, когда в последний раз действительно запускалось выражение cron, — это сам экземпляр планировщика.

person Stephen C    schedule 26.04.2010
comment
Привет Стивен, Спасибо за ваш отзыв. Я добавил немного больше информации в примечаниях к моему вопросу, чтобы решить мою проблему. - person a-sak; 27.04.2010


Решение, которое я нашел с 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 = ...
    }
}

Я оставлю читателю в качестве упражнения рефакторинг во вспомогательные методы или классы. На самом деле я использую описанный выше алгоритм в подклассе делегирующего триггера, который затем помещаю в набор, отсортированный по времени предыдущего срабатывания.

person darrenmc    schedule 19.01.2012
comment
Разница между последующими пожарами не всегда будет одинаковой. - person Ahamed; 17.12.2013
comment
Ахамед прав. Этот код в целом неверен и корректен только в небольшом подмножестве случаев. - person Husain; 31.08.2018

Если вы используете org.quartz, можно использовать context.getPreviousFireTime();

Пример:

public class TestJob implements Job {

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

        context.getPreviousFireTime(); 
    }
}

Если вы используете context.getTrigger().getPreviousFireTime(), у вас будет время срабатывания задания, которое выполняется в данный момент.

person Radu Linu    schedule 25.11.2016

Удивительно, что до сих пор нет кварцевого метода для получения предыдущего времени обжига на основе 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). Примечание. Вам потребуется Java 8, если вы хотите использовать версию > 3.1.6.

Решение, которое вы можете использовать, состоит в том, чтобы сохранить его при запуске вашего задания.


Как убедиться, что задание запущено?

Решение, которое я нашел, состоит в том, чтобы использовать getNextValidTimeAfter от Quartz (CronExpression). Этот работает нормально. Вы спросите меня, о чем я говорю, поскольку мы ищем предыдущее допустимое время! Вы правы, дайте мне одну секунду!

Давайте представим, что у нас есть CRON раз в месяц (0 0 16 1 * ? = В 16:00:00 1-го числа каждого месяца), и мы хотим каждый день проверять, что предыдущее выполнение сработало. Вам нужно будет сохранять getNextValidTime при каждом выполнении и сравнивать эту дату с сегодняшней датой. например (формат ДД/ММ/ГГГГ):

• 01.01.2019 → задание запущено, мы сохраняем время следующего срабатывания (назовем его nextFireTime):

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

• 01.02.2019 → проверка дня: 01.02.2019 ‹ 02.01.2019 ОК

...

• 02.01.2019 → допустим сервер не работает, задание не запускается.

• 02.02.2019 → сервер включен, проверка дня: 02.02.2019 > 02.01.2019 КО!

→ Мы знаем, что предыдущее время пожара не сработало. Вы можете делать то, что хотите (запустить задание и сохранить новое значение nextFireTime).


Другой вариант, который может вас заинтересовать, см. в разделе MISFIRE_INSTRUCTION_FIRE_NOW.

Задание выполняется сразу после того, как планировщик обнаружит ситуацию осечки. Это разумная политика. Пример сценария: вы запланировали очистку системы на 2 часа ночи. К сожалению, к тому времени приложение было отключено из-за технического обслуживания и вернулось в 3 часа ночи. Итак, триггер дал осечку, и планировщик пытается спасти ситуацию, запустив его как можно быстрее — в 3 часа ночи.

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

e.g.:

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

person Emerica    schedule 03.10.2019

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

person Bhuwan Prasad Upadhyay    schedule 06.07.2015
comment
Это будет работать только для периодических выстрелов. Что происходит, когда subsequentNextValidTime нет или стрельба нерегулярна (например, стрельба на 10-й, 12-й, 11-й, 17-й, 37-й минуте)? - person João Neves; 11.01.2016

Его рабочее решение

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

Здесь я получаю разницу между текущим временем и временем следующего срабатывания и исключаю это время разницы из текущего времени, чтобы мы получили время последнего срабатывания.

person ChandraPrakash    schedule 12.07.2018
comment
Хотя этот код может ответить на вопрос, предоставление информации о том, как и почему он решает проблему, повышает его ценность в долгосрочной перспективе. - person L_J; 12.07.2018
comment
В этом коде есть все виды сумасшедших предположений; почему currentDate будет ровно посередине между двумя исполнениями? Кроме того, это хороший пример того, как НЕ манипулировать датами. Как насчет перехода на дневное время и дополнительных секунд? - person Jan Larsen; 12.03.2019

person    schedule
comment
Код выглядит великолепно, но нет определения используемой вами функции вычитания. Пожалуйста, добавьте это. - person Baldeep Singh Kwatra; 16.04.2020