Skip to content
Michi Oshima edited this page Mar 31, 2014 · 7 revisions

Scheduler (aka, "rollover-time" problem)

Background

Last year I was tasked with extending the CronTrigger of Quartz Scheduler. I did this originally in .NET (hence against Quartz.NET). I thought it'd be a good exercise to recreate the code in Clojure. But there was one main function I couldn't figure out how to write. I ended up asking for help at SNH Clojure Group.

More Background

If you care to look at Quartz's CronTrigger source code, you'll notice the CronTrigger is pretty hard to extend. So I actually did a rewrite of CronTrigger. And while at it, I changed the way the schedule is written: from Cron expression to JSON, like this:

Schedule: "Everyday at 11:30am"

  1. Cron: "0 30 11 ? * *"
  2. JSON: "{ Hour: 11, Minute: 30 }"

JSON was the thing to do for C#/.NET. Now that I'm doing this in Clojure, I wanted to write my schedules like this:

  • "{ :hour 11, :minute 30 }"

Nice, better, yes?

Next-Time Function

Given a schedule, like "Everyday at 11:30am", and a particular time, a Quartz trigger's main task is to calculate when the next scheduled time is.

  • next-time function: F(schedule, time_t) ==> next_time

This is what I had difficulty writing in Clojure. (I eventually did, but the code was ugly.)

Below is a pseudo-code to get this done in a Java-like language:

GregCalendar nextTime(GregCalendar time_t, Schedule sched) {

    GregCalendar t = time_t;
    boolean rollOver;

    while (true) {

        [t, rollOver] = nextSecond(t, sched);
        if (rollOver)
            continue;
        [t, rollOver] = nextMinute(t, sched);
        if (rollOver)
            continue;
        [t, rollOver] = nextHour(t, sched);
        if (rollOver)
            continue;
        [t, rollOver] = nextDay(t, sched);
        if (rollOver)
            continue;
        [t, rollOver] = nextMonth(t, sched);
        if (rollOver)
            continue;
        [t, rollOver] = nextYear(t, sched);
        if (rollOver)
            continue;

        break;
    }

    return t;
}

Above looks simple enough, but I need to explain what nextSecond(), nextMinute() and the likes do. I'll try to do that with a couple of examples

Next-Time Example #1

Given a schedule and time_t:

  • Schedule: { :minute [0 15 30 45], :second [15 45] } (Can somebody help me write this schedule in plain English?)
  • time_t: 2014/03/26, 20:27:11

The next-time function would calculate:

  • next-time: 2014/03/26, 20:30:15

How do we figure?

  1. We first take the "second" value of time_t, which is 11. Then we look at the schedule for seconds, which is [15 45], and ask what the next scheduled time after 11 is. It's 15.
  2. We then take the "minute" value of time_t, which is 27. Then we look at the schedule for minutes, which is [0 15 30 45]. The next scheduled minute is 30.

The operation is pretty simple. If a schedule specifies hours, days, months, and years, we'd repeat pretty much the same operation on those time units.

There is just one complication, however. It's rollover. On to the next example.

Next-Time Example 2

  • Schedule: { :minute [0 15 30 45], :second [15 45] }
  • time_t:

Clone this wiki locally