Workaround for CronSequenceGenerator Last day of month?
Asked Answered
B

4

8

Ok so here it is I want to schedule a task to run on last day of every month on 10:10 AM.My cron expression is

0 10 10 L * ?

Now the problem is CronSequenceGenerator is throwing NumberFormatException for 'L' value.This means Spring's CronSequenceGenerator does'nt support this kind of expression.How to do this in any other way (workaround).I don't want to use quartz or Does spring's gonna support this in new releases.

Here is full stacktrace:

Exception in thread "main" java.lang.NumberFormatException: For input string: "L"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.valueOf(Integer.java:582)
    at org.springframework.scheduling.support.CronSequenceGenerator.getRange(CronSequenceGenerator.java:324)
    at org.springframework.scheduling.support.CronSequenceGenerator.setNumberHits(CronSequenceGenerator.java:297)
    at org.springframework.scheduling.support.CronSequenceGenerator.setDays(CronSequenceGenerator.java:275)
    at org.springframework.scheduling.support.CronSequenceGenerator.setDaysOfMonth(CronSequenceGenerator.java:266)
    at org.springframework.scheduling.support.CronSequenceGenerator.parse(CronSequenceGenerator.java:239)
    at org.springframework.scheduling.support.CronSequenceGenerator.<init>(CronSequenceGenerator.java:81)
    at org.springframework.scheduling.support.CronTrigger.<init>(CronTrigger.java:54)
    at org.springframework.scheduling.support.CronTrigger.<init>(CronTrigger.java:44)
    at com.hcdc.coedp.datantar.scheduler.SchedulerUtil.start(SchedulerUtil.java:75)
    at com.hcdc.coedp.datantar.scheduler.SchedulerUtil.changeTrigger(SchedulerUtil.java:106)
    at com.hcdc.coedp.datantar.scheduler.SchedulingService.scheduleTransfer(SchedulingService.java:70)
    at com.hcdc.coedp.datantar.scheduler.Scheduler.schedule(Scheduler.java:107)
    at main.Main.main(Main.java:47)

Update:

Following is my scheduling method

 /**
    * Schedule a task {@link Task} with a specified cron expression.
    * @param task {@link Task}
    * @param cronExpression cron expression to be applied must be a vaild one.
    * @param taskName
    * @return 
    */
     public String start(Task task, String cronExpression, String taskName) {
        CronTrigger trigger = new CronTrigger(cronExpression);//line 2

        CronSequenceGenerator generator = new CronSequenceGenerator(cronExpression, TimeZone.getTimeZone("GMT+5:30"));
        List<Date> dateList = new ArrayList<>(5);
        Date currentDate = new Date();
        for (int i = 0; i < 5; i++) {
            currentDate = generator.next(currentDate);
            dateList.add((currentDate));
            System.out.println("Next Exceution times are" + currentDate);
        }
        ScheduledFuture sf = tps.schedule(task, trigger);

        //TODO Save this scheduled future with a specific task name.
        ContextHolder.schduledFutureMap.put(taskName, sf);
        return cronExpression;
    }

And on line 2 it throws NumberFormatException when I pass specified cron expression.

Bertram answered 6/8, 2013 at 11:46 Comment(5)
I looked around a bit, and it seems that L is actually supported. How are you defining the job? Annotation? You should probably add this to your question.Dislocate
Great. Could you also paste the relevant code that calls the start() method? I'm sorry about this, but I really think using L should work.Dislocate
Actually that won't affect the output you could test it in a simple main program just pass cron expression with 'L' in start method You will get the exception.Bertram
@Magnilex: I'm looking at the code and I can't see why you think it must work.Aldred
@Aldred Well done. I actually didn't research too much, and thought that L was part of the Crontab pattern. What I really wanted to make sure though was that OP really posted a correct String, which he convinced me that he does.Dislocate
A
8

This feature is not in standard cron expression syntax. So probably Spring will never implement it. Looking at the code, I can't see any surgical solution extending CronSequenceGenerator. So why you just don't use Quartz since it's a particular feature?

Depending on your exact need, you could implement your own Trigger. Something like:

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

public class LastDayOfMonthTrigger implements Trigger {

    private final LocalTime time;

    public LastDayOfMonthTrigger(LocalTime time) {
        this.time = time;
    }

    @Override
    public Date nextExecutionTime(TriggerContext ctx) {
        Date last = ctx.lastScheduledExecutionTime();
        LocalDate date = last == null ? new LocalDate() : new LocalDate(last).plusDays(1);
        LocalDate lastDay = date.dayOfMonth().withMaximumValue();
        return lastDay.toDateTime(time).toDate();
    }
}
Aldred answered 12/8, 2013 at 23:36 Comment(1)
Thanks ,And I could'nt use quartz because It demands architecture- level refactoring..."They'll kill me"....And I will try your CustomTrigger idea It seems like it will work...Bertram
W
6

As a workaround I would schedule the execution for all dates

0 10 10 * * ?

and checked the actual date in the scheduled method

public void scheduledTask() {
    Calendar c = Calendar.getInstance();
    if (c.get(Calendar.DATE) == c.getActualMaximum(Calendar.DATE)) {
        ...
    }
}
Wernsman answered 6/8, 2013 at 11:57 Comment(4)
It would work, but I would only schedule the method for the possible last days of month, something like day 27-31.Dislocate
Yes, I think it would be some optimizationWernsman
But you said you wanted the last day of every monthWernsman
Sorry I have put myself incorrectly..."not only last days"....I want to schedule monthly and day could be any day of month..But problem arrives only for last days...i.e feb 29th..Also my cron expression is dynamic for single task.Check update..Bertram
F
2

Optimized version which runs only on the last day of a month:

@Scheduled(cron = "0 55 23 28-31 * ?")
public void doStuffOnLastDayOfMonth() {
    final Calendar c = Calendar.getInstance();
    if (c.get(Calendar.DATE) == c.getActualMaximum(Calendar.DATE)) {
        // do your stuff
    }
}
Fari answered 16/8, 2016 at 7:36 Comment(0)
C
0

There is another solution:

Generate one month of data. The program should run on the first day of the next month to ensure that all data for the entire month is captured.

import org.apache.commons.lang3.time.DateUtils;

@Scheduled(cron = "0 0 0 1 * ?") // runs on the first day of each month

public void doStuffOnFirstDayOfMonth() {

    Date now = DateUtils.addDays(new Date(), -1); // "now" is now on the last day of the month

}
Cubital answered 28/10, 2019 at 3:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.