58

I know that I can cause a thread to sleep for a specific amount of time with:

time.sleep(NUM)

How can I make a thread sleep until 2AM? Do I have to do math to determine the number of seconds until 2AM? Or is there some library function?

( Yes, I know about cron and equivalent systems in Windows, but I want to sleep my thread in python proper and not rely on external stimulus or process signals.)

Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
  • I have this monitoring python script that is listening on a socket continuously on a number of different machines. I just want to wake up a thread in that python script. I don't want to monkey with cron on every single machine that this script is kicked off on. – Ross Rogers Jan 08 '10 at 22:21
  • Generally, the problem with saying "wake up at time X" is that you can't be guaranteed that the kernel will wake up the thread at that time. The computer may be turned off, sleeping, or loaded with a really intensive operation that it can't spare cycles for you. This is why most implementations don't offer this type of sleep. Calculating the difference in seconds is probably the best approach in this case. – carl Jan 08 '10 at 22:48
  • This isn't mission critical. If it only runs every other day, it is ok. The machines it will run on are servers in a server farm, so they are always on and running multiple jobs. Also, it doesn't have to be exactly 2AM. I just want it to run when most people are asleep. – Ross Rogers Jan 08 '10 at 23:13
  • of course. And I could use `time.gmtime()` to get Greenwich Mean Time. I only care about `time.localtime()`. I don't have to coordinate across time zones -- whatever is local to the user. – Ross Rogers Jan 08 '10 at 23:33
  • something I made for a project of mine ... http://hastebin.com/yufahaciji.py ... sorry for lack of docstrings in some functions *shrug* – dylnmc Oct 15 '16 at 16:56
  • Try: https://pypi.python.org/pypi/APScheduler/1.01 – Alastair McCormack Aug 15 '17 at 14:00

15 Answers15

50

Here's a half-ass solution that doesn't account for clock jitter or adjustment of the clock. See comments for ways to get rid of that.

import time
import datetime

# if for some reason this script is still running
# after a year, we'll stop after 365 days
for i in xrange(0,365):
    # sleep until 2AM
    t = datetime.datetime.today()
    future = datetime.datetime(t.year,t.month,t.day,2,0)
    if t.hour >= 2:
        future += datetime.timedelta(days=1)
    time.sleep((future-t).total_seconds())
    
    # do 2AM stuff
Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
  • 1
    `t.day + (t.hour >= 2)` would be a (possibly non-Pythonic) solution to the "between 0000 and 0200" problem. Also, I'd put the `sleep` in a loop waking up periodically, in case the clock is adjusted or we wake up early, but I don't think that's very important. – ephemient Jan 08 '10 at 23:02
  • 7
    BTW, it's worth noting that naive use of *only* the `seconds` attribute can lead to unexpected results. It contains only the "remainder" of division by one day, so to speak, so if the duration is longer than one day, you'd need to add `.days * 24*3600`. Obviously not a problem in this case, but something that catches the odd person who's unfamiliar with datetime objects. – Peter Hansen Jan 09 '10 at 02:22
  • 5
    Agreed. Use `total_seconds()` instead! (It returns a float too, which `time.sleep` will accept) – cce Feb 11 '12 at 04:54
  • 3
    There is a bug in this solution - future may not exist :) What if we are in the last day of month? – mnowotka Sep 04 '13 at 08:18
  • 2
    @mnowotka, _in a Sponge Bob voice, "7 years later"_ -- `timedelta` takes care of rolling over the days, months, and years. – Ross Rogers Aug 25 '20 at 14:22
  • You got it @mnowotka ! I've cleared my scheduled for 2027. Cyah then! – Ross Rogers Aug 26 '20 at 13:42
  • If I am not mistaken, this is wrong, as datetime.today() should just give the date; not the actual time. For that, one would need to use datetime.now(). https://stackoverflow.com/questions/6545254/difference-between-system-datetime-now-and-system-datetime-today – clel May 16 '23 at 15:00
  • 1
    This is python. You are indeed mistaken. – Ross Rogers May 16 '23 at 16:49
  • Ah, thanks! (for reference: https://stackoverflow.com/questions/32517248/what-is-the-difference-between-python-functions-datetime-now-and-datetime-t) – clel Jun 02 '23 at 11:58
46

You can use the pause package, and specifically the pause.until function, for this:

import pause
from datetime import datetime

pause.until(datetime(2015, 8, 12, 2))
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
Bobby Zandavi
  • 599
  • 4
  • 7
10

Slightly more generalized solution (based off of Ross Rogers') in case you'd like to add minutes as well.

import datetime
import time

def sleepUntil(self, hour, minute):
    t = datetime.datetime.today()
    future = datetime.datetime(t.year, t.month, t.day, hour, minute)
    if t.timestamp() > future.timestamp():
        future += datetime.timedelta(days=1)
    time.sleep((future-t).total_seconds())

Please note that this will roughly sleep until the given time with some overshoot (from my own experiments I've had overshoots as bad as ~400ms after the desired 'sleep until' time). If you need millisecond accurate sleeps (<10ms~) I'd use the pause module as was suggested by Bobby.

Omrii
  • 397
  • 3
  • 14
  • 1
    See my comment above, this likely needs datetime.now() not .today(): https://stackoverflow.com/questions/6545254/difference-between-system-datetime-now-and-system-datetime-today – clel May 16 '23 at 15:01
  • Sorry, that's wrong. In Python, the function behaves as expected. – clel Jun 02 '23 at 12:01
8

Another approach, using sleep, decreasing the timeout logarithmically.

def wait_until(end_datetime):
    while True:
        diff = (end_datetime - datetime.now()).total_seconds()
        if diff < 0: return       # In case end_datetime was in past to begin with
        time.sleep(diff/2)
        if diff <= 0.1: return

Building on the answer of @MZA and the comment of @Mads Y

onetyone
  • 457
  • 4
  • 15
6

One possible approach is to sleep for an hour. Every hour, check if the time is in the middle of the night. If so, proceed with your operation. If not, sleep for another hour and continue.

If the user were to change their clock in the middle of the day, this approach would reflect that change. While it requires slightly more resources, it should be negligible.

carl
  • 49,756
  • 17
  • 74
  • 82
  • 1
    I prefer this approach for its simplicity; it's less likely to have edge cases. The only difficulty would be ensuring you didn't run it twice, for example if the script restarted. – fantabolous Aug 12 '14 at 05:29
4

I tried the "pause" pacakage. It does not work for Python 3.x. From the pause package I extracted the code required to wait until a specific datetime and made the following def.

def wait_until(execute_it_now):
    while True:
        diff = (execute_it_now - datetime.now()).total_seconds()
        if diff <= 0:
            return
        elif diff <= 0.1:
            time.sleep(0.001)
        elif diff <= 0.5:
            time.sleep(0.01)
        elif diff <= 1.5:
            time.sleep(0.1)
        else:
            time.sleep(1)
MZA
  • 999
  • 11
  • 16
  • 5
    What about doing sleep((execute_it_now-datetime.today()).total_seconds()/2)? This will logarithmically reduce the sleeping and be quite precise, unless I've overseen something. – Mads Y Feb 08 '16 at 20:45
  • The current version of [`pause`](https://pypi.org/project/pause/), v0.3 Oct 2020, works great with Python 3.10. – wisbucky Dec 28 '21 at 17:49
4

adapt this:

from datetime import datetime, timedelta
from time import sleep

now = datetime.utcnow
to = (now() + timedelta(days = 1)).replace(hour=1, minute=0, second=0)
sleep((to-now()).seconds)
  • 2
    Welcome to Stackoverflow, Iulian. There are already many other answers. Can you elaborate on how yours differ? Is it better in some or all situations? – dovetalk Apr 03 '19 at 22:55
  • It works instead of `utcnow` i just use `datetime.now()` – Chris P Oct 24 '22 at 19:09
3

Slightly beside the point of the original question:

Even if you don't want to muck around with crontabs, if you can schedule python scripts to those hosts, you might be interested to schedule anacron tasks? anacron's major differentiator to cron is that it does not rely the computer to run continuously. Depending on system configuration you may need admin rights even for such user-scheduled tasks.

A similar, more modern tool is upstart provided by the Ubuntu folks: http://upstart.ubuntu.com/ This does not yet even have the required features. But scheduling jobs and replacing anacron is a planned feature. It has quite some traction due to its usage as Ubuntu default initd replacement. (I am not affiliated with the project)

Of course, with the already provided answer, you can code the same functionality into your python script and it might suit you better in your case.

Still, for others, anacron or similar existing systems might be a better solution. anacron is preinstalled on many current linux distributions (there are portability issues for windows users).

Wikipedia provides a pointer page: https://en.wikipedia.org/wiki/Anacron

If you do go for a python version I'd look at the asynchronous aspect, and ensure the script works even if the time is changed (daylight savings, etc) as others have commented already. Instead of waiting til a pre-calculated future, I'd always at maximum wait one hour, then re-check the time. The compute cycles invested should be negligible even on mobile, embedded systems.

Krenair
  • 570
  • 5
  • 21
cfi
  • 10,915
  • 8
  • 57
  • 103
2

Asynchronous version of Omrii's solution

import datetime
import asyncio

async def sleep_until(hour: int, minute: int, second: int):
    """Asynchronous wait until specific hour, minute and second

    Args:
        hour (int): Hour
        minute (int): Minute
        second (int): Second

    """
    t = datetime.datetime.today()
    future = datetime.datetime(t.year, t.month, t.day, hour, minute, second)
    if t.timestamp() > future.timestamp():
        future += datetime.timedelta(days=1)
    await asyncio.sleep((future - t).total_seconds())
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
yEmreAk.com
  • 3,278
  • 2
  • 18
  • 37
1

What about this handy and simple solution?

from datetime import datetime
import time

pause_until = datetime.fromisoformat('2023-02-11T00:02:00') # or whatever timestamp you gonna need
time.sleep((pause_until - datetime.now()).total_seconds())
Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
0

I know is way late for this, but I wanted to post an answer (inspired on the marked answer) considering systems that might have - incorrect - desired timezone + include how to do this threaded for people wondering how.

It looks big because I'm commenting every step to explain the logic.

import pytz #timezone lib
import datetime
import time
from threading import Thread

# using this as I am, check list of timezone strings at:
## https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TIMEZONE = pytz.timezone("America/Sao_Paulo")

# function to return desired seconds, even if it's the next day
## check the bkp_time variable (I use this for a bkp thread)
## to edit what time you want to execute your thread
def get_waiting_time_till_two(TIMEZONE):
    # get current time and date as our timezone
    ## later we remove the timezone info just to be sure no errors
    now = datetime.datetime.now(tz=TIMEZONE).replace(tzinfo=None)
    curr_time = now.time()
    curr_date = now.date()

    # Make 23h30 string into datetime, adding the same date as current time above
    bkp_time = datetime.datetime.strptime("02:00:00","%H:%M:%S").time()
    bkp_datetime = datetime.datetime.combine(curr_date, bkp_time)

    # extract the difference from both dates and a day in seconds
    bkp_minus_curr_seconds = (bkp_datetime - now).total_seconds()
    a_day_in_seconds = 60 * 60 * 24

    # if the difference is a negative value, we will subtract (- with + = -)
    # it from a day in seconds, otherwise it's just the difference
    # this means that if the time is the next day, it will adjust accordingly
    wait_time = a_day_in_seconds + bkp_minus_curr_seconds if bkp_minus_curr_seconds < 0 else bkp_minus_curr_seconds

    return wait_time

# Here will be the function we will call at threading
def function_we_will_thread():
    # this will make it infinite during the threading
    while True:
        seconds = get_waiting_time_till_two(TIMEZONE)
        time.sleep(seconds)
        # Do your routine

# Now this is the part where it will be threading
thread_auto_update = Thread(target=function_we_will_thread)
thread_auto_update.start()
mrbTT
  • 1,399
  • 1
  • 18
  • 31
0

It takes only one of the very basic libraries.

import time

sleep_until = 'Mon Dec 25 06:00:00 2020' # String format might be locale dependent.
print("Sleeping until {}...".format(sleep_until))
time.sleep(time.mktime(time.strptime(sleep_until)) - time.time())
  • time.strptime() parses the time from string -> struct_time tuple. The string can be in different format, if you give strptime() parse-format string as a second argument. E.g. time.strptime("12/25/2020 02:00AM", "%m/%d/%Y %I:%M%p")
  • time.mktime() turns the struct_time -> epoch time in seconds.
  • time.time() gives current epoch time in seconds.
  • Substract the latter from the former and you get the wanted sleep time in seconds.
  • sleep() the amount.

If you just want to sleep until whatever happens to be the next 2AM, (might be today or tomorrow), you need an if-statement to check if the time has already passed today. And if it has, set the wake up for the next day instead.

import time

sleep_until = "02:00AM" # Sets the time to sleep until.

sleep_until = time.strftime("%m/%d/%Y " + sleep_until, time.localtime()) # Adds todays date to the string sleep_until.
now_epoch = time.time() #Current time in seconds from the epoch time.
alarm_epoch = time.mktime(time.strptime(sleep_until, "%m/%d/%Y %I:%M%p")) # Sleep_until time in seconds from the epoch time.

if now_epoch > alarm_epoch: #If we are already past the alarm time today.
    alarm_epoch = alarm_epoch + 86400 # Adds a day worth of seconds to the alarm_epoch, hence setting it to next day instead.

time.sleep(alarm_epoch - now_epoch) # Sleeps until the next time the time is the set time, whether it's today or tomorrow.
0

Using Croniter you can avoid having to adjust the date component of the datetime. It also is more readable as it follows the cron expression formatting.

The following will wait until 5 minutes after midnight UTC, irrespective of the day.

import croniter
import datetime
import pause

if __name__ == '__main__':
    now = datetime.datetime.utcnow()
    cron = croniter.croniter('5 00 * * *', now)
    while True:
        pause.until(cron.get_next(datetime.datetime))
        # it is now 5 minutes past midnight UTC
laker93
  • 498
  • 4
  • 9
-2
from datetime import datetime
import time, operator
time.sleep([i[0]*3600 + i[1]*60 for i in [[H, M]]][0] - [i[0]*3600 + i[1]*60 for i in [map(int, datetime.now().strftime("%H:%M").split(':'))]][0])
Tomer
  • 19
-5

Instead of using the wait() function, you can use a while-loop checking if the specified date has been reached yet:


if datetime.datetime.utcnow() > next_friday_10am:
    # run thread or whatever action
    next_friday_10am = next_friday_10am()
    time.sleep(30)

def next_friday_10am():
    for i in range(7):
        for j in range(24):
            for k in range(60):
                if (datetime.datetime.utcnow() + datetime.timedelta(days=i)).weekday() == 4:
                    if (datetime.datetime.utcnow() + datetime.timedelta(days=i, hours=j)).hour == 8:
                        if (datetime.datetime.utcnow() + datetime.timedelta(days=i, hours=j, minutes=k)).minute == 0:
                            return datetime.datetime.utcnow() + datetime.timedelta(days=i, hours=j, minutes=k)

Still has the time-checking thread check the condition every after 30 seconds so there is more computing required than in waiting, but it's a way to make it work.

naraghi
  • 430
  • 1
  • 6
  • 18