[Buildbot-devel] Cron-style scheduling

Dobes Vandermeer dobesv at gmail.com
Sat Oct 29 00:48:44 UTC 2005

This is a first take on a cron-style scheduler.  Insert it into
scheduler.py after class Periodic.  To run a build once per weekday
(which I'm sure will be the most popular usage) at 3am, pass hour=3,
minute=0, dayOfWeek=range(5).  Also to run every even five minutes,
pass minute=range(0,60,5).

Comments/suggestions appreciated!

def addTime(timetuple, secs):
    return time.localtime(time.mktime(timetuple)+secs)
def findFirstValueAtLeast(values, value, default=None):
    for v in values:
        if v >= value: return v
    return default

class CronStyleScheduler(BaseUpstreamScheduler):
    """ Imitate "cron" scheduling.

        Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek;
        each may be a single number or a list of valid values.

        For dayOfWeek and dayOfMonth, builds are triggered if the date matches
        either of them.  Month and day numbers start at 1, not zero.

    compare_attrs = ('name', 'builderNames',
                     'minute', 'hour', 'dayOfMonth', 'month',
'dayOfWeek', 'branch')

    def __init__(self, name, builderNames, minute=None, hour=None,
dayOfMonth=None, month=None, dayOfWeek=None,
        BaseUpstreamScheduler.__init__(self, name)
        self.builderNames = builderNames
        self.minute = minute
        self.hour = hour
        self.dayOfMonth = dayOfMonth
        self.month = month
        self.dayOfWeek = dayOfWeek
        self.branch = branch
        self.delayedRun = None
        self.nextRunTime = None

    def setTimer(self):
        self.nextRunTime = self.calculateNextRunTime()
        self.delayedRun = reactor.callLater(self.nextRunTime -
time.time(), self.doPeriodicBuild)

    def startService(self):
        print 'CronStyleScheduler.__init__', self.name,
time.asctime(), 'next run',

    def stopService(self):

    def calculateNextRunTime(self):
        dateTime = time.localtime()

        dateTime = addTime(dateTime, 60-dateTime[5]) # Remove seconds
by advancing to at least the next minue

        # Now we just keep adding minutes until we find something that matches
#        print 'minute', self.minute
#        print 'hour', self.hour
#        print 'dayOfMonth', self.dayOfMonth
#        print 'month', self.month
#        print 'dayOfWeek', self.dayOfWeek
#        print 'dateTime', time.asctime(dateTime)

        def isRunTime(timetuple):
            def check(ourvalue, value):
                if not ourvalue: return True
                if isinstance(ourvalue, int): return value == ourvalue
                return (value in ourvalue)

            if not check(self.minute, timetuple[4]):
#                print 'bad minute', timetuple[4], self.minute
                return False

            if not check(self.hour, timetuple[3]):
#                print 'bad hour', timetuple[3], self.hour
                return False

            if not check(self.month, timetuple[1]):
#                print 'bad month', timetuple[1], self.month
                return False

            if self.dayOfMonth and self.dayOfWeek: # Specified day(s)
of month AND day(s) of week
                if not (check(self.dayOfMonth, timetuple[2]) or
check(self.dayOfWeek, timetuple[6])):
    #                print 'bad day'
                    return False
                if not check(self.dayOfMonth, timetuple[2]):
                    #print 'bad day of month'
                    return False

                if not check(self.dayOfWeek, timetuple[6]):
                    #print 'bad day of week'
                    return False

            return True

        # It not an efficient algorithm, but it'll *work* for now
        yearLimit = dateTime[0]+2
        while not isRunTime(dateTime):
            dateTime = addTime(dateTime, 60)
#            print 'Trying', time.asctime(dateTime)
            assert dateTime[0] < yearLimit, 'Something is wrong with this code'
        return time.mktime(dateTime)

    def listBuilderNames(self):
        return self.builderNames

    def getPendingBuildTimes(self):
        # TODO: figure out when self.timer is going to fire next and report
        # that
        if self.nextRunTime is None: return []
        return [self.nextRunTime]

    def doPeriodicBuild(self):
        # Schedule the next run
        print 'CronStyleScheduler.doPeriodicBuild', self.name,
time.asctime(), 'next run',

        # And trigger a build
        bs = buildset.BuildSet(self.builderNames,

    def addChange(self, change):

More information about the devel mailing list