Finding Recent Time Using Cron Expression in Java

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

eg. If now = 25-Apr-2010 10 pm, the expression cron "0 15 10? * *" (Quartz) should return to me 25-Apr-2010 10:15 in the morning

Note: 1) I don’t care if we use standard cron expressions (for example, Unix and Quartz) or less popular if they can bring me the correct “Last Fired Time” 2) It’s also not literally “Last fire time”, because A trigger may not fire, but logically there should be a way to tell when it (would) be fired last.

+8
java quartz-scheduler scheduling crontrigger
source share
9 answers

cron-utils is an open source Java library for parsing, validating, migrating clones that support the required operation. To get the previous date from cron to a specific time simply:

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

Keep in mind that in its current state it is a little buggy and cannot correctly compute more complex cron expressions.

+4
source share

First, I do not know the existing library that supports this. Quartz can, but the standard Java class libraries, of course, do not.

Secondly, strictly speaking, what you request , originally requested, is impossible. The best the library can tell you is that the cron expression should or should have fired. The only thing that could (theoretically) tell you for the last time is that the actual cron trigger is the scheduler instance itself.

+3
source share

Quartz seems to support library support for cron expressions.

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

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

It may depend on the version of Quartz, whether it works.

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

+2
source share

The solution I found with Quartz was to go back to one time interval for the trigger and calculate what would be the next firing time. Iterating over all the triggers, you can determine the most recent time that the trigger was supposed to run in the past.


Calculate the interval between each shot:

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

Find the following firing time at a time until it passes in the past:

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

I found that if you use CronTrigger , this will prevent you from asking for a fire in the past. To get around this, I change the start time, so the above snippet becomes:

 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 

Swipe through all the triggers and find the last one 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 will leave this an exercise for the reader to reorganize it into helper methods or classes. I actually use the above algorithm in the delegation delegation subclass, which I then put into a set sorted by previous shooting times.

+1
source share

If you use org.quartz, you can use context.getPreviousFireTime();

Example:

 public class TestJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { context.getPreviousFireTime(); } } 

If you use context.getTrigger().getPreviousFireTime() , you will have the job time that is currently running.

0
source share
 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))); } } 
0
source share

Surprisingly, there is still no quartz method for obtaining the previous response time based on the expression CronExpression ...

How to find out the time of the last dismissal?

If you control a basic CRON, for example, 0 0 0 * * ? (at 00:00:00 in the morning every day), you can use the solution of Joao Neves (using com.cronutils.model.time.ExecutionTime).

Edit : I did other tests, and it seems to work better with the latest version (previous tests were done with 3.1.6). Note. You need Java 8 if you want to use the & lt; 3.1.6.

Otherwise, if you are managing a complex CRON, for example, 0 30 11 ? * MON,THU * 0 30 11 ? * MON,THU * , it will not work. You will get random results (I had an environment for this ...).

The solution you can use is to save it when you start your work.


How to verify that a task has been started?

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

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

• 01/01/2019 → the task worked, we store the next fire (let it be called nextFireTime ):

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

• 02/01/2019 → day check: 02/01/2019 & lt; 02/01/2019 OK

...

• 01/02/2019 → imagine that the server is not working, the task is not running.

• 02.02.2009 → the server is turned on, check of the day: 02.02.2009> 01.02.2009 KO!

→ We know that the previous fire did not work. You can know what to do, what you want (to start work and save a new nextFireTime).


Another option that might interest you is see MISFIRE_INSTRUCTION_FIRE_NOW .

The task runs immediately after the planner detects a misfire situation. This is a smart policy. Example scenario: you have planned to clean the system at 2 a.m. Unfortunately, the application was closed due to maintenance by that time and returned at 3 a.m. So the trigger worked, and the scheduler tries to save the situation, run it as soon as possible - at 3 a.m.

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

eg.:

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

White Paper: https://www.quartz-scheduler.org/api/2.1.7/org/quartz/CronTrigger.html

0
source share
 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

-one
source share

Own 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 I get the difference between the current time and the time of the next fire and excluding this time difference from the current time, so that we get the time of the last launch

-one
source share

All Articles