bunkat / later

A javascript library for defining recurring schedules and calculating future (or past) occurrences for them. Includes support for using English phrases and Cron schedules. Works in Node and in the browser.
http://bunkat.github.io/later/
MIT License
2.42k stars 244 forks source link

Please help with complex schedule definition #65

Open mitselek opened 10 years ago

mitselek commented 10 years ago

Could you please kindly assist with defining schedule that has:

mitselek commented 10 years ago

Currently Cron expressions are the most compact way to describe a schedule, but are slightly less flexible (no direct support for composite or exception schedules)

Does it mean my composite schedule cant be defined right now?

bunkat commented 10 years ago

If you are just looking to calculate occurrences, you can just supply the start and end dates to later.schedule(s).next.

var s = later.parse.cron('1,2 * * * *')
later.schedule(s).next(1, new Date(2014,10,1), new Date(2014,10,3))
> Sat Nov 01 2014 00:01:00 GMT-0700 (Pacific Daylight Time)

If you want to bake the start and end dates into the schedule itself, you can parse the cron into a schedule and then manually added the additional constraints. All schedules are just JSON objects so you can manually add whatever constraints you want to them. In this case, I added a year, month, and day constraint so that it is only valid during your range.

var s = later.parse.cron('1,2 * * * *')
s.schedules[0].Y = [2014]
s.schedules[0].M = [11]
s.schedules[0].D = [1,2,3]
later.schedule(s).next(1)
> Sat Nov 01 2014 00:01:00 GMT-0700 (Pacific Daylight Time)
mitselek commented 10 years ago

Great! If you some day happen to look for a job, give me a call :p

mitselek commented 10 years ago
var s = later.parse.cron('1,2 * * * *')
s.schedules[0].Y = [2014]
s.schedules[0].M = [11]
s.schedules[0].D = [1,2,3]

This makes my schedule valid between 2014-11-01 and 2014-11-03 (very poor choice of range from my side)
If I want to define ranges that overlap months (between 2014-10-28 and 2014-11-03), then alas.

Maybe You should allow start and end dates as optional parameters to timeout and interval methods?

mitselek commented 10 years ago

I will solve this for now with something like this - just added date range to your code:

var swSetTimeout = function(fn, sched, startDate, endDate) {
    if (endDate !== undefined ? endDate < Date.now() : false) {
        return {
            clear: function() {
                return
            }
        }
    }
    var s = later.schedule(sched), t
    if (startDate !== undefined ? startDate > Date.now() : false) {
        t = setTimeout(scheduleTimeout, startDate - Date.now())
        return {
            clear: function() {
                clearTimeout(t)
            }
        }
    }
    scheduleTimeout()
    function scheduleTimeout() {
        var now = Date.now(), next = s.next(2, now), diff = next[0].getTime() - now
        if (diff < 1e3) {
            diff = next[1].getTime() - now
        }
        if (diff < 2147483647) {
            t = setTimeout(fn, diff)
        } else {
            t = setTimeout(scheduleTimeout, 2147483647)
        }
    }
    return {
        clear: function() {
            clearTimeout(t)
        }
    }
}
var swSetInterval = function(fn, sched, startDate, endDate) {
    var t = swSetTimeout(scheduleTimeout, sched, startDate, endDate), done = false
    function scheduleTimeout() {
        if (!done) {
            fn()
            t = swSetTimeout(scheduleTimeout, sched, startDate, endDate)
        }
    }
    return {
        clear: function() {
            done = true
            t.clear()
        }
    }
}
bunkat commented 10 years ago

There is also a full date constraint that you could use alongside before and after modifiers to limit the schedule between two dates. I haven't fully tested/documented this but this is what I use when scheduling tasks.

var s = later.parse.cron('1,2 * * * *');
var start = (new Date(2014,9,28).getTime());
var end = (new Date(2014,10,3).getTime());

s.schedules[0].fd_a = [start];
s.schedules[0].fd_b = [end];

later.schedule(s).next(10);

I agree it would be nice to be able to pass start and end to the timeout and interval function as well.