1

How can I Schedule a Timer to run every 10th of the month?

Thank you very much in advance

My actual method:

    @PostConstruct
    public void postConstruct()  throws Exception
    {
        
        final TimerTask task = new TimerTask()
        {
            @Override
            public void run()
            {
                myprocess();
            }
        };
        final Timer timer = new Timer();
        final long delay = 0;
        final long intevalPeriod = 20000;
        // schedules the task to be run in an interval
        timer.scheduleAtFixedRate(task, delay,
                intevalPeriod);
    }
xywz
  • 35
  • 4
  • 1
    For that kind of duration, wouldn't it be better to have the OS run a scheduled task? – WJS Aug 04 '20 at 19:57
  • @WJS I'd say there are arguments for and against scheduling it at the OS level. – JakeRobb Aug 04 '20 at 20:01
  • You can use this [Call method at date/time](https://stackoverflow.com/questions/55019047/call-method-at-date-time). Determining the correct date is simple using the modern `java.time` API. Let each task, once its done, re-schedule itself with the dynamically computed next date. – Zabuzard Aug 04 '20 at 20:03

2 Answers2

6

If you're using Spring, you can use the @Scheduled annotation with a cron expression. Just annotate any public method on a bean class as follows:

@Scheduled(cron = "0 0 0 10 * *")
public void myprocess() {
    // perform task here
}

Don't forget to enable scheduling in your application context. If you're using XML configuration:

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>

Or, if you're using a @Configuration class:

@Configuration
@EnableScheduling
public class SpringConfig {
    ...
}

The cron expression 0 0 0 10 * * breaks down as follows:

0  - at the zeroth second of the minute
0  - at the zeroth minute of the hour
0  - at the zeroth hour of the day
10 - on the 10th day of the month
*  - any month
*  - any day of the week

Spring's docs for cron expressions are here: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/support/CronSequenceGenerator.html

Here's a link with some more info about using @Scheduled: https://www.baeldung.com/spring-scheduled-tasks#schedule-a-task-using-cron-expressions

And Spring's official docs on the subject: https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling-annotation-support-scheduled

JakeRobb
  • 1,711
  • 1
  • 17
  • 32
  • 2
    This is the idiomatic way of doing it. The answer can be made more useful by adding the explanation of the cron expression. – Arvind Kumar Avinash Aug 04 '20 at 20:05
  • 1
    @Zabuzard I have removed that sentence. Thanks. You're right, and I agree that this approach is preferable. I'd use your way if for whatever reason Spring isn't an option. – JakeRobb Aug 04 '20 at 20:24
1

Timer-based

If you are searching for a vanilla solution that sticks to the "timer-concept", you can just compute the correct timespan and let the task, once its done, reschedule itself again with the correct computed time for the next execution.

Also see Call method at date/time. Will look something like:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public static ScheduledFuture<?> scheduleFor(Runnable runnable, ZonedDateTime when) {
    Instant now = Instant.now();
    // Use a different resolution if desired
    long secondsUntil = ChronoUnit.SECONDS.between(now, when.toInstant());

    return scheduler.schedule(runnable, secondsUntil, TimeUnit.of(ChronoUnit.SECONDS));
}

public static ZonedDateTime nextDate() {
    return ZonedDateTime.now().plusMonths(1);
}

And then you have your task something like

public class MyTask implements Runnable {

    // ...

    @Override
    public void run() {
        // TODO Your stuff ...

        // Reschedule
        scheduleFor(this, nextDate());
    }
}

And initially trigger it like

scheduleFor(task, ZonedDateTime.now().withDayOfMonth(10));

Disadvantages

Obviously this solution, because it is timer-based, has the big disadvantage that it can easily drift and get out of sync. For example if your system time changes or if the execution of the task takes so long that you drift into the next day.

Latter can easily be mitigated by generating the next date at start of the task, instead of the end.

If all of that is not a factor for you, it will likely work as intended.

Otherwise you should probably go for a different, crontab-like approach where the date is checked periodically and soon as it matches, it executes the task. In contrast to a fixed-offset execution as in "execute in ... seconds".

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
  • 1
    I would be inclined to use a specific time of day rather than just `ZonedDateTime.now()`. That would help the drift issue significantly. I'd also include `withDayOfMonth(10)` in `nextDate` in case the drift pushed us into the next calendar day. – JakeRobb Aug 04 '20 at 21:04
  • 1
    Also, I'd put the call to scheduler.schedule in a `finally` block so that the next invocation gets scheduled even if something goes wrong with the current invocation. – JakeRobb Aug 04 '20 at 21:07