Cron Expression once each 2 days

Learn cron expression once each 2 days with practical examples, diagrams, and best practices. Covers java, cron, quartz-scheduler development techniques with visual explanations.

Scheduling Tasks Every Other Day with Cron Expressions

Hero image for Cron Expression once each 2 days

Learn how to craft cron expressions for Quartz Scheduler and other cron-compatible systems to execute tasks precisely once every two days, covering common pitfalls and best practices.

Scheduling recurring tasks is a fundamental requirement in many applications. Cron expressions provide a powerful and flexible way to define these schedules. While daily, weekly, or monthly schedules are straightforward, defining a task to run 'once every two days' can sometimes be tricky due to the nature of cron's time-based fields. This article will guide you through creating robust cron expressions for this specific requirement, focusing on Quartz Scheduler's syntax, which is widely adopted in Java environments.

Understanding Cron Expression Basics

A standard cron expression consists of six or seven fields, depending on the implementation. Each field represents a unit of time, from seconds to day of the week. Understanding these fields is crucial for crafting accurate schedules.

For Quartz Scheduler, the fields are typically:

  1. Seconds (0-59)
  2. Minutes (0-59)
  3. Hours (0-23)
  4. Day of Month (1-31)
  5. Month (1-12 or JAN-DEC)
  6. Day of Week (1-7 or SUN-SAT)
  7. Year (optional, empty or 1970-2099)
flowchart LR
    subgraph Cron Fields
        S[Seconds] --> M[Minutes]
        M --> H[Hours]
        H --> DOM["Day of Month"]
        DOM --> MO[Month]
        MO --> DOW["Day of Week"]
        DOW --> Y[Year (Optional)]
    end
    style S fill:#f9f,stroke:#333,stroke-width:2px
    style M fill:#bbf,stroke:#333,stroke-width:2px
    style H fill:#bfb,stroke:#333,stroke-width:2px
    style DOM fill:#fbb,stroke:#333,stroke-width:2px
    style MO fill:#fdf,stroke:#333,stroke-width:2px
    style DOW fill:#ddf,stroke:#333,stroke-width:2px
    style Y fill:#eee,stroke:#333,stroke-width:2px

Standard Cron Expression Field Order

Crafting the 'Every Other Day' Expression

The challenge with 'every other day' lies in the 'Day of Month' and 'Day of Week' fields. A simple /2 increment on the 'Day of Month' field (1/2) would mean 'every 2nd day of the month starting on the 1st', which might not align with a continuous 'every other day' schedule across month boundaries. The most reliable way to achieve a true 'every other day' schedule is to use the 'Day of Month' field with a starting day and an increment, combined with careful consideration of the 'Day of Week' field to avoid conflicts.

Let's assume we want the task to run at 9:00 AM. A common approach is to pick a starting day and then increment. For example, to run every other day starting on the 1st of the month:

0 0 9 1/2 * ? *

This expression means:

  • 0: At second 0
  • 0: At minute 0
  • 9: At hour 9 (9 AM)
  • 1/2: Every 2nd day of the month, starting on the 1st.
  • *: Every month
  • ?: No specific day of the week (important to avoid conflicts with 'Day of Month')
  • *: Any year (optional, can be omitted in some implementations)

However, this expression will run on the 1st, 3rd, 5th, etc., of each month. If a month has 31 days, it will run on the 31st. The next month, it will run on the 1st, meaning it runs on two consecutive days (31st and 1st). To truly ensure 'every other day' without this overlap, you might need to manage two separate cron jobs or use a more advanced scheduling mechanism that tracks the last execution date.

Alternative: Using Two Alternating Cron Jobs

To achieve a strict 'every other day' schedule that correctly handles month transitions, a robust solution involves setting up two cron jobs that alternate. One job runs on odd-numbered days of the month, and the other on even-numbered days. You then ensure that only one of them executes based on a shared state or a more complex logic within your task.

Job 1 (Odd Days): Runs on the 1st, 3rd, 5th, etc., of the month. 0 0 9 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31 * ? *

Job 2 (Even Days): Runs on the 2nd, 4th, 6th, etc., of the month. 0 0 9 2,4,6,8,10,12,14,16,18,20,22,24,26,28,30 * ? *

Within your scheduled task, you would then implement logic to check if it's the 'correct' day for execution based on the last run date. This approach provides more control but adds complexity to your application logic.

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class EveryOtherDayScheduler {

    public static void main(String[] args) throws Exception {

        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

        JobDetail job = JobBuilder.newJob(MyJob.class)
                .withIdentity("everyOtherDayJob", "group1")
                .build();

        // Cron expression for every other day, starting on the 1st of the month at 9 AM
        // This will run on 1st, 3rd, 5th... of each month.
        // Be aware of month boundary issues (e.g., 31st then 1st).
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("everyOtherDayTrigger", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 1/2 * ? *"))
                .build();

        sched.scheduleJob(job, trigger);
        sched.start();

        // For demonstration, let it run for a while and then shut down
        Thread.sleep(60L * 1000L); // Keep scheduler running for 1 minute
        sched.shutdown(true);
    }
}

// A simple job that prints a message
class MyJob implements org.quartz.Job {
    public void execute(org.quartz.JobExecutionContext context) {
        System.out.println("Job executed at: " + new java.util.Date());
    }
}

Example of scheduling an 'every other day' job using Quartz Scheduler in Java.

Considerations for Robust 'Every Other Day' Scheduling

For mission-critical applications requiring a strict 'every other day' schedule without any consecutive runs, relying solely on a single cron expression like 1/2 can be problematic due to month-end transitions. Here are some advanced considerations:

  1. Application-Level Logic: Implement a check within your scheduled task. Before executing the core logic, verify if the last execution was indeed two days ago. If not, skip the current execution. This requires storing the last execution timestamp.
  2. External Scheduler Features: Some advanced schedulers (e.g., Airflow, Jenkins with specific plugins) offer more sophisticated scheduling options that can handle 'every N days' more natively, often tracking the last successful run.
  3. Two-Job Approach with State: As discussed, two cron jobs (one for odd days, one for even) can be used. The job itself would then check a shared state (e.g., a database entry) to determine if it's its turn to run and update the state after execution.
  4. Leap Years and Month Lengths: Standard cron expressions handle variable month lengths and leap years automatically for 'Day of Month' fields. However, the 'every other day' logic still needs to account for the sequence across these changes.

1. Define Execution Time

Determine the exact second, minute, and hour you want your task to run. For example, 0 0 9 for 9:00:00 AM.

2. Choose Day of Month Strategy

Decide between 1/2 (every other day starting on the 1st, with potential month-end overlaps) or a more complex two-job approach for strict alternation.

3. Set Day of Week to '?'

Always use ? for the 'Day of Week' field when using Day of Month increments to prevent conflicts.

4. Test Thoroughly

Simulate your cron expression over several months, including month-end transitions and leap years, to ensure it behaves as expected.

For critical 'every other day' requirements, add logic within your task to verify the last execution date and prevent consecutive runs, even if the cron expression triggers it.