Search code examples
pythonscheduler

Running Python schedule daily at random times


How do I run a scheduled job at random times daily starting with today? I want to use the schedule package.

pip install schedule

import schedule

def job()
   print("foo")
   return schedule.CancelJob

while True:
   time_str = '{:02d}:{:02d}'.format(random.randint(6, 10), random.randint(0, 59))
   print("Scheduled for {}".format(time_str))
   schedule.every().day.at(time_str).do(job)
   schedule.run_pending()

The above code just spins:

Scheduled for 06:36
Scheduled for 10:00
Scheduled for 06:18

Solution

  • You're providing a moving target by putting your random time generator inside you while loop. Specifically, after reviewing the source code here, it is clear that the job will only run if datetime.datetime.now() >= self.next_run is true (see scheduler.run_pending() and job.should_run() definitions). Because you are always moving job.next_run, you will only hit it if it happens to be in the past on that particular iteration of the loop. Interestingly, I think this would cause a bug where the probability of actually running your job increases as you approach 24:00, though this has yet to be shown. I think you need to create a separate function for generating the next random time, and call it from your job function. For example:

    import schedule
    import time
    import random
    
    def job():
       print("foo")
       schedule_next_run()
       return schedule.CancelJob
    
    def schedule_next_run():
       time_str = '{:02d}:{:02d}'.format(random.randint(6, 10), random.randint(0, 59))
       schedule.clear()
       print("Scheduled for {}".format(time_str))
       schedule.every().day.at(time_str).do(job)
    
    schedule_next_run()
    
    while True:
       schedule.run_pending()
       time.sleep(60)
    

    Note that the example may not be random for the day that the job starts, as your random time may be before the time you happen to start your script. You could work in a way to pick a random time in the future on the first day to circumvent this as needed.

    To verify the above example, I used shorter time spans. The following works for me:

    import schedule
    import time
    import random
    
    def job():
       print("foo")
       schedule_next_run()
       return schedule.CancelJob
    
    def schedule_next_run():
       time_span = random.randint(1, 5)
       schedule.clear()
       print(f'Scheduled in {time_span} seconds')
       schedule.every(time_span).seconds.do(job)
    
    schedule_next_run()
    
    while True:
       schedule.run_pending()