-
Notifications
You must be signed in to change notification settings - Fork 0
Scheduler
Help me write the "next-time" function in Clojure. The "next-time" function is described below.
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 rewrite the code in Clojure. But there was one main function I couldn't figure out how to write. So I ended up asking for help at SNH Clojure Group.
(OK, I eventually did manage to write the function, but it was very ugly.)
If you were to look at Quartz's CronTrigger source code, you'd notice the CronTrigger is pretty hard to extend:
- Example: CronExpression.getTimeAfter(Date) (lines 1094--1486. That's 392 lines of goodness!)
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:15am and 11:45am"
- Cron:
0 15,45 11 ? * *
- JSON:
{ Hour: 11, Minute: [15, 45] }
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 [15 45] }
Nice, better, yes, yes?
Given a schedule 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.
Below is a pseudo-code to get this done in a Java-like language. Note, functions like nextSecond()
, nextMinute()
, etc., aren't defined in the pseudo code, and I'll describe them later on:
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;
}
I'll explain what nextSecond()
, nextMinute()
and the likes do with a couple of examples.
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 does it do that?
- 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. So, we updatetime_t
, setting second to 15:- updated time_t: 2014/03/26, 20:27:15
- 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.- updated time_t: 2014/03/26, 20:30:15
- Done.
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. So this is what the likes of nextSecond()
and nextMinute()
do. The variable t
is the updated time_t
these functions return.
There is just one complication, however. It's rollover. On to the next example.
- Schedule:
{ :minute [0 15 30 45], :second [15 45] }
- time_t: 2014/03/26, 20:46:28
Next-time would calculate:
- next-time: 2014/03/26, 21:00:15
Time rolls over to the next hour. Let's see how we figure this:
- Second for
time_t
is 28. The schedule specifies[15 45]
for seconds. So, the next second is 45.- updated time_t: 2014/03/26, 20:46:45
- Minute for
time_t
is 46. The schedule specifies[0 15 30 45]
for minutes, so the next minute is 0 of the next hour. What we must do now is to updatetime_t
like this:- updated time_t: 2014/03/26, 21:00:00 (Increment the hour, and zero out minutes and seconds.)
- Then, we start over and calculate second and minute values with the updated
time_t
.
This roll-over action is what I do with the boolean rollOver
and keyword continue
in my pseudo code.