cron.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListReverse(list lst) {
    if (llGetListLength(lst) <= 1) return lst;
    return wasListReverse(
        llList2List(
            lst, 
            1, 
            llGetListLength(lst)
        )
    ) + llList2List(lst, 0, 0);
}
 
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasDayOfWeek(integer year, integer month, integer day) {
    return llList2String(
        [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 
            "Saturday", "Sunday" ],
        (
            day
            + ((153 * (month + 12 * ((14 - month) / 12) - 3) + 2) / 5)
            + (365 * (year + 4800 - ((14 - month) / 12)))              
            + ((year + 4800 - ((14 - month) / 12)) / 4)
            - ((year + 4800 - ((14 - month) / 12)) / 100)
            + ((year + 4800 - ((14 - month) / 12)) / 400)
            - 32045
        ) % 7
    );
}
 
// for notecard reading
integer line = 0;
// time to execute
list time = [];
// message to send
list exec = [];
 
default {
    state_entry() {
        if(llGetInventoryType("crontab") != INVENTORY_NOTECARD) {
            llSay(DEBUG_CHANNEL, "Sorry, could not find the crontab notecard.");
            return;
        }
        llGetNotecardLine("crontab", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) state cron;
        if(data == "") jump continue;
        integer i = llSubStringIndex(data, "#");
        if(i != -1) data = llDeleteSubString(data, i, -1);
        if(data == "") jump continue;
        list data = llParseString2List(data, [" "], []);
        // *  *  *  *  * message to send to the link-set
        if(llGetListLength(data) < 6) jump continue;
        list t = llList2List(data, 0, 4);
        // normalize 0x
        i = llGetListLength(t)-1;
        do {
            if(llList2String(t, i) == "*") jump wildcard;
            t = llListReplaceList(t, [ llList2Integer(t, i) ], i, i);
@wildcard;
        } while(--i>-1);
        time += llDumpList2String(t, " ");
        exec += llDumpList2String(llList2List(data, 5, -1), " ");
@continue;
        llGetNotecardLine("crontab", ++line);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) 
            llResetScript();
    }
}
 
state cron {
    state_entry() {
        // cron runs ever minute
        llSetTimerEvent(60);
    }
    timer() {
        // build the current date
        list stamp = llParseString2List(
            llGetTimestamp(),
            ["-",":","T"],[""]
        );
        list ymd = llList2List(stamp, 0, 2);
        integer weekDay = llListFindList(
            // convert to cron syntax where Sunday counts as day 0 or 7
            [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
            [
                wasDayOfWeek(
                    llList2Integer(ymd, 0), 
                    llList2Integer(ymd, 1), 
                    llList2Integer(ymd, 2)
                )
            ]
        );
        // minute, hour, day, month, day of week
        list date  = wasListReverse(llList2List(stamp, 1, -2)) + weekDay;
        integer i = llGetListLength(date)-1;
        // normalize 0x
        do {
            date = llListReplaceList(date, [ llList2Integer(date, i) ], i, i);
        } while(--i>-1);
 
        // check if it is time
        list times = time;
        list execs = exec;
        do {
            list cron = llParseString2List(llList2String(times, 0), [" "], []);
            do {
                if(
                    // treat 0 or 7 weekday as Sunday
                    (
                        llGetListLength(cron) == 1 && 
                        llList2Integer(date, 0) == 0 && 
                        llList2Integer(cron, 0) != 0 && 
                        llList2Integer(cron, 0) != 7
                    ) ||
                    (
                        llGetListLength(cron) != 1 && 
                        llList2String(date, 0) != 
                        llList2String(cron, 0) && 
                        llList2String(cron, 0) != "*"
                    )
                ) jump continue;
                date = llDeleteSubList(date, 0, 0);
                cron = llDeleteSubList(cron, 0, 0);
            } while(llGetListLength(cron));
            llMessageLinked(LINK_SET, 0, llList2String(execs, 0), "");
@continue;
            times = llDeleteSubList(times, 0, 0);
            execs = llDeleteSubList(execs, 0, 0);
        } while(llGetListLength(times));
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) 
            llResetScript();
    }
}