Scheduling a Python program to sleep within a given time period - python

while True:
now = datetime.datetime.now();
if now.hour >= 22 and now.hour < 3:
print "sleep"
sleep_at = datetime.datetime.combine(datetime.date.today(),datetime.time(3))
sleep_til = (sleep_at - now).seconds
print sleep_til
time.sleep(sleep_til)
print "wake"
else:
print "break"
break
This code should make my entire program to go to sleep at 10 PM and wake up at 3 AM. MY question is.. will this work? I tried running it.. but I cannot change my system/computer Time.. so I cannot check.
Reason why I am posting this question is because my coding is using datetime.date.tday and datetime.datetime which is calling current date..
Once again.. I want my program to run before 10PM and sleep between 10PM to 3AM and rerun after 3AM..
Can someone check if this is the right way to do it?

Consider (extra-verbose for clarity):
import time, datetime
# Create time bounds -- program should run between RUN_LB and RUN_UB
RUN_LB = datetime.time(hour=22) # 10pm
RUN_UB = datetime.time(hour=3) # 3am
# Helper function to determine whether we should be currently running
def should_run():
# Get the current time
ct = datetime.datetime.now().time()
# Compare current time to run bounds
lbok = RUN_LB <= ct
ubok = RUN_UB >= ct
# If the bounds wrap the 24-hour day, use a different check logic
if RUN_LB > RUN_UB:
return lbok or ubok
else:
return lbok and ubok
# Helper function to determine how far from now RUN_LB is
def get_wait_secs():
# Get the current datetime
cd = datetime.datetime.now()
# Create a datetime with *today's* RUN_LB
ld = datetime.datetime.combine(datetime.date.today(), RUN_LB)
# Create a timedelta for the time until *today's* RUN_LB
td = ld - cd
# Ignore td days (may be negative), return td.seconds (always positive)
return td.seconds
while True:
if should_run():
print("--do something--")
else:
wait_secs = get_wait_secs()
print("Sleeping for %d seconds..." % wait_secs)
time.sleep(wait_secs)
But I do agree that sleeping is not the best way to delay your program starting. You may look into the task scheduler on Windows or cron on Linux.

Related

Schedule an iterative function every x seconds without drifting

Complete newbie here so bare with me. I've got a number of devices that report status updates to a singular location, and as more sites have been added, drift with time.sleep(x) is becoming more noticeable, and with as many sites connected now it has completely doubles the sleep time between iterations.
import time
...
def client_list():
sites=pandas.read_csv('sites')
return sites['Site']
def logs(site):
time.sleep(x)
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
stamp = time.strftime('%Y-%m-%d,%H:%M:%S')
log = open(f"{site}/log", 'a')
log.write(f",{stamp},{site},hit\n")
log.close()
os.remove(f"{site}/target/hit")
else:
stamp = time.strftime('%Y-%m-%d,%H:%M:%S')
log = open(f"{site}/log", 'a')
log.write(f",{stamp},{site},miss\n")
log.close()
...
if __name__ == '__main__':
while True:
try:
client_list()
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(logs, client_list())
...
I did try adding calculations for drift with this:
from datetime import datetime, timedelta
def logs(site):
first_called=datetime.now()
num_calls=1
drift=timedelta()
time_period=timedelta(seconds=5)
while 1:
time.sleep(n-drift.microseconds/1000000.0)
current_time = datetime.now()
num_calls += 1
difference = current_time - first_called
drift = difference - time_period* num_calls
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
...
It ends up with a duplicate entries in the log, and the process still drifts.
Is there a better way to schedule the function to run every x seconds and account for the drift in start times?
Create a variable equal to the desired system time at the next interval. Increment that variable by 5 seconds each time through the loop. Calculate the sleep time so that the sleep will end at the desired time. The timings will not be perfect because sleep intervals are not super precise, but errors will not accumulate. Your logs function will look something like this:
def logs(site):
next_time = time.time() + 5.0
while 1:
time.sleep(time.time() - next_time)
next_time += 5.0
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
# do something that takes a while
So I managed to find another route that doesn't drift. The other method still drifted over time. By capturing the current time and seeing if it is divisible by x (5 in the example below) I was able to keep the time from deviating.
def timer(t1,t2)
return True if t1 % t2 == 0 else False
def logs(site):
while 1:
try:
if timer(round(time.time(), 0), 5.0):
if os.path.isfile(os.path.join(f'{site}/target/', 'hit')):
# do something that takes a while
time.sleep(1) ''' this kept it from running again immediately if the process was shorter than 1 second. '''
...

How to sleep python script for xx minutes after every hour execution?

I am trying to make a python script that works in a loop mode with iteration through a text file to run for periods of one hour and make 30minute pauses between each hour loop .
After some searching I found this piece of code :
import datetime
import time
delta_hour = 0
while:
now_hour = datetime.datetime.now().hour
if delta_hour != now_hour:
# run your code
delta_hour = now_hour
time.sleep(1800) # 1800 seconds sleep
# add some way to exit the infinite loop
This code has a few issues though :
It does not consider one hour periods since the script starts running
It does not seem to work continuously for periods over one hour
Considering what I am trying to achieve (running script 1hour before each time it pauses for 30mins) what is the best approach to this ? Cron is not an option here .
For clarification :
1hour run -- 30min pause -- repeat
Thanks
Here is a so simple code, I have written for teaching purposes, which is very clear
from datetime import datetime
class control_process():
def __init__(self, woking_period, sleeping_period):
self.woking_period = woking_period # working period in minutes
self.sleeping_period = sleeping_period # sleeping period in minutes
self.reset()
def reset(self):
self.start_time = datetime.utcnow() # set starting point
def manage(self):
m = (datetime.utcnow() - self.start_time).seconds / 60 # how long since starting point
if m >= self.woking_period: # if exceeded the working period
time.sleep(self.sleeping_period * 60) # time to sleep in seconds
self.reset() # then reset time again
return # go to continue working
cp = control_process(60, 30) # release for 60 minutes and sleep for 30 minutes
while True: # you code loop
cp.manage()
'''
your code
'''
in which 'control_processobject - I calledcp- callscp.manage()` inside your executing loop.
you reset time via cp.reset() before going in the loop or whenever you want
Based on Comments
The simplicity I mean is to add this class to your general library so you can use it whenever you want by instantiation of cp then one or two controlling functions 'cp.manage()` which control the working cycles, and cp.reset() if you want to use it in another location of the code. I believe that use a function is better than a long condition statement.
Using the default library you could do something like call the script itself using subprocess. By checking whether conditions are met the process could do a task and call itself. Extending the logic with a kill pill would make it stop (I leave that up to you).
import argparse, time
from subprocess import call
DELAY = 60 * 30 # minutes
WORK_TIME = 60 * 60 # minutes
parser = argparse.ArgumentParser()
parser.add_argument("-s",
help = "interval start time",
type = float,
default = time.time())
parser.add_argument("-t",
help = "interval stop time",
type = float,
default = time.time() + WORK_TIME)
def do_task():
# implement task
print("working..")
return
if __name__ == "__main__":
args = parser.parse_args()
start = args.s
stop = args.t
# work
if start < time.time() < stop:
do_task()
# shift target
else:
start = time.time() + DELAY
stop = start + WORK_TIME
call(f"python test.py -t {stop} -s {start}".split())
The simplest solution I could come up with was the following piece of code, which I added inside my main thread :
start_time = int(time())
... #main thread code
#main thread code end
if int(time() - start_time >= 60 * 60):
print("pausing time")
sleep(30 * 60)
start_time = int(time())
From the moment the script starts this will pause every hour for 30mins and resume afterwards .
Simple yet effective !

How to make simple alarms on Python

I'm trying to make a simple alarm using Python but whatever I try it doesn't seem to work. I've just recently made a timer but an alarm would be a little more useful.
I'm also pretty new to Python so I'm not really aware of all the rules and syntax.
import datetime
import os
stop = False
while stop == False:
rn = str(datetime.datetime.now().time())
print(rn)
if rn == "18:00:00.000000":
stop = True
os.system("start BTS_House_Of_Cards.mp3")
When I run the file, it prints the time but goes completely past the time I want the alarm to go off at.
The technical problem here is that if you call datetime.now() over and over again, you can't always call it fast enough to get all of the possible values. So == should instead be >=. However, this still isn't very good.
A much better way to do this is to use time.sleep() instead of looping.
import datetime
import os
import time
now = datetime.datetime.now()
# Choose 6PM today as the time the alarm fires.
# This won't work well if it's after 6PM, though.
alarm_time = datetime.datetime.combine(now.date(), datetime.time(18, 0, 0))
# Think of time.sleep() as having the operating system set an alarm for you,
# and waking you up when the alarm fires.
time.sleep((alarm_time - now).total_seconds())
os.system("start BTS_House_Of_Cards.mp3")
Just replace:
if rn == "18:00:00.000000":
With:
if rn >= "18:00:00.000000":
Use the following to round to the next minute (or adapt for seconds etc)
import datetime as dt
rn = dt.datetime.now()
# round to the next full minute
rn -= dt.timedelta( seconds = rn.second, microseconds = rn.microsecond)
rn += dt.timedelta(minutes=1)
To adapt for seconds remove seconds = rn.second and then change minutes in the next line to seconds
How it works
Removes the seconds and microseconds from the current time and then adds on 1 minute therefore rounding it to the next whole minute.
I Had the same issue for my IoT based project here is a simple tryout code written by me in python to invoke certain function at a given time here goes code
import datetime
from time import sleep
tempdatetimeobj = datetime.datetime.now()
try:
while True:
print("#################")
now = str(datetime.datetime.now().time()) # convert a time in string
print("Time is:- " + str(now))
timestring = now[:5]
# Slice the time for eg time is 17:02:52:23656 it will
slice it to 17:02 only
print("SLICE MAGIC :- "+ timestring)
if timestring == '17:26' : # after slicing operation only you can compare time
print('Triggered')
elif timestring == '17:30' :
print('again triggered')
else :
print('-_-')
sleep(60)
except KeyboardInterrupt:
print('unHandled Exception Occured')
How about using this technique? It's simple and works smoothly.
import datetime
import winsound # exclusively for windows only,
# if you are on any other system you can use 'playsound' or 'simpleaudio' module.
alarm_hour = int(input("Set hour: "))
alarm_minutes = int(input("Set minutes: "))
am_pm = input("am or pm? ")
print(f"Waiting for time: {alarm_hour}:{alarm_minutes} {am_pm}")
# time conversion
# because datetime module returns time in military form i.e. 24 hrs format
if am_pm == 'pm': # to convert pm to military time
alarm_hour += 12
elif alarm_hour == 12 and am_pm == 'am': # to convert 12am to military time
alarm_hour -= 12
else:
pass
while True: # infinite loop starts to make the program running until time matches alarm time
# ringing alarm + execution condition for alarm
if alarm_hour == datetime.datetime.now().hour and alarm_minutes == datetime.datetime.now().minute:
print("\nIt's the time!")
winsound.Beep(1000, 1000)
break
Obviously you can do more with the program by doing data validation for the inputs. Using the 'simpleaudio' module, you can add some interesting functions. And the number of other things you can do to increase the efficiency of the program.
There is another alternative that hasn't been mentioned which might work for you, depending on what your goals are: signal.alarm.
signal.alarm is similar to the alarm(3) library call on Unix where setting a time will result in a SIGALRM signal being sent to a parent process in the future to denote when an asynchronous action should be taken (the default with an uncaught signal is a dead process).
Example:
$ python
Python 2.7.16 (default, Mar 20 2019, 12:15:19)
[GCC 7.4.0] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import signal
>>> signal.alarm(1)
0
>>> Alarm clock
The key points when using signal.alarm(..) is the following:
- The solution is best for a single process application; it should not be used if you need to have per-thread or multi-process timers.
- You will need to setup a signal handler for signal.SIGALRM.
The key point when handling signals is:
- Handle asynchronous
This approach is fairly basic and very OS-centric, but it's a simple, clean approach for doing things. There are other alternatives that could potentially be employed using the kqueue/select module (poll, etc, come to mind), other calls within the signal module, concurrent.futures/multiprocessing (there's a timeout option on some APIs).
Just one potential tool to use in your toolbox of timers.
A complete solution using signal.alarm can be found here (I adjusted the end time so I wouldn't have to wait forever for it to complete).
$ date; python3 play_house_of_cards.py ; date
Wed 10 Jul 2019 04:54:16 PM PDT
would have run `start BTS_House_Of_Cards.mp3`
Wed 10 Jul 2019 04:55:00 PM PDT
$
I would suggest using while loop with True like this:
loop = True
while loop:
rn = str(datetime.datetime.now().time())
if rn >= "18:00:00.00000":
loop = False
this alarm has almost every feature so it might be helpful for you if you want to make a perfect alarm
from pygame import mixer
from os import path
from sys import path as p
from time import sleep,strftime
try:
while True: #it will give you another chance if you fill in letters or symbols
hour=input('hour=')
if hour.isdigit():# if input is digit
if len(hour)==1:#if the input has one digit
hour2='0'+hour #it will add 0 in front of the input because time module gives 01 not 1 so it will convert 1(input) into 01
else:#if the input has two digit
hour2=hour
else:
print('press any key to try again')
min=input('Minute=')
if min.isdigit():
if len(min)==1:
min2='0'+min
else:
min2=min
ap=input('am/pm=')
if ap=='am' or 'pm' or 'AM' or 'PM' or 'Am' or 'Pm' or 'aM' or 'pM':
apm=ap.upper()
break
while True:
x=strftime('%I')
y=strftime('%M')
z=strftime('%p')
if x==hour2 and y==min2 and z==apm :
print("Type 'h' for help")
while True:
mixer.init()
you don't have to write the location of a file(c:\music\alone.mp3) just save the file in the same folder as the .py file
music_=(path.join(p[0],'alone.mp3' ))
mixer.music.load(music_)
mixer.music.play()
stop=input('X=')
if stop=='s' : #it will snooze the alarm for 5 min
time_=strftime('%I')
time2=strftime('%M')
time3=strftime('%S %p')
print('Alarm will sound at ',time_,':',int(time2)+5,':',time3)
mixer.music.stop()
sleep(300 )
elif stop=='h':
print("1.Type 's' for snooze alarm / 2.Press any key except 's' and 'h' for turn off the alarm" )
elif stop!='s' or 'h': #it will stop alarm
mixer.music.stop()
break
break
except:
print('Invalid Input: Please try again')

Generate some "random" start times for scripts to run based on a period of time in python

I'm trying to generate some random seeded times to tell my script when to fire each of the scripts from within a main script.
I want to set a time frame of:
START_TIME = "02:00"
END_TIME = "03:00"
When it reaches the start time, it needs to look at how many scripts we have to run:
script1.do_proc()
script2.alter()
script3.noneex()
In this case there are 3 to run, so it needs to generate 3 randomized times to start those scripts with a minimum separation of 5 mins between each script but the times must be within the time set in START_TIME and END_TIME
But, it also needs to know that script1.main is ALWAYS the first script to fire, other scripts can be shuffled around (random)
So we could potentially have script1 running at 01:43 and then script3 running at 01:55 and then script2 might run at 02:59
We could also potentially have script1 running at 01:35 and then script3 running at 01:45 and then script2 might run at 01:45 which is also fine.
My script so far can be found below:
import random
import pytz
from time import sleep
from datetime import datetime
import script1
import script2
import script3
START_TIME = "01:21"
END_TIME = "03:00"
while 1:
try:
# Set current time & dates for GMT, London
CURRENT_GMTTIME = datetime.now(pytz.timezone('Europe/London')).strftime("%H%M")
CURRENT_GMTDAY = datetime.now(pytz.timezone('Europe/London')).strftime("%d%m%Y")
sleep(5)
# Grab old day for comparisons
try:
with open("DATECHECK.txt", 'rb') as DATECHECK:
OLD_DAY = DATECHECK.read()
except IOError:
with open("DATECHECK.txt", 'wb') as DATECHECK:
DATECHECK.write("0")
OLD_DAY = 0
# Check for new day, if it's a new day do more
if int(CURRENT_GMTDAY) != int(OLD_DAY):
print "New Day"
# Check that we are in the correct period of time to start running
if int(CURRENT_GMTTIME) <= int(START_TIME.replace(":", "")) and int(CURRENT_GMTTIME) >= int(END_TIME.replace(":", "")):
print "Correct time, starting"
# Unsure how to seed the start times for the scripts below
script1.do_proc()
script2.alter()
script3.noneex()
# Unsure how to seed the start times for above
# Save the current day to prevent it from running again today.
with open("DATECHECK.txt", 'wb') as DATECHECK:
DATECHECK.write(CURRENT_GMTDAY)
print "Completed"
else:
pass
else:
pass
except Exception:
print "Error..."
sleep(60)
EDIT 31/03/2016
Let's say I add the following
SCRIPTS = ["script1.test()", "script2.test()", "script3.test()"]
MAIN_SCRIPT = "script1.test()"
TIME_DIFFERENCE = datetime.strptime(END_TIME, "%H:%M") - datetime.strptime(START_TIME, "%H:%M")
TIME_DIFFERENCE = TIME_DIFFERENCE.seconds
We now have the the number of scripts to run
We have the list of the script to run.
We have the name of the main script, the one to run first.
We have the time in seconds to show how much time we have in total to run all the scripts within.
Surely there is a way we can just plug some sort of loop to make it do it all..
for i in range(len(SCRIPTS)), which is 3 times
Generate 3 seeds, making sure the minimum time is of 300 and all together the 3 seeds must not exceed TIME_DIFFERENCE
Create the start time based on RUN_TIME = START_TIME and then RUN_TIME = RUN_TIME + SEED[i]
First loop would check that that MAIN_SCRIPT exists within SCRIPTS, if it does then it would run that script first, delete itself from SCRIPTS and then on next loops, as it doesn't exist in SCRIPTS it would switch to randomly calling one of the other scripts.
Seeding the times
The following appears to work, there might be an easier way of doing this though.
CALCULATE_SEEDS = 0
NEW_SEED = 0
SEEDS_SUCESSS = False
SEEDS = []
while SEEDS_SUCESSS == False:
# Generate a new seed number
NEW_SEED = random.randrange(0, TIME_DIFFERENCE)
# Make sure the seed is above the minimum number
if NEW_SEED > 300:
SEEDS.append(NEW_SEED)
# Make sure we have the same amount of seeds as scripts before continuing.
if len(SEEDS) == len(SCRIPTS):
# Calculate all of the seeds together
for SEED in SEEDS:
CALCULATE_SEEDS += SEED
# Make sure the calculated seeds added together is smaller than the total time difference
if CALCULATE_SEEDS >= TIME_DIFFERENCE:
# Reset and try again if it's not below the number
SEEDS = []
else:
# Exit while loop if we have a correct amount of seeds with minimum times.
SEEDS_SUCESSS = True
Use datetime.timedelta to compute time differences. This code assumes all three processes run on the same day
from datetime import datetime, timedelta
from random import randint
YR, MO, DY = 2016, 3, 30
START_TIME = datetime( YR, MO, DY, 1, 21, 00 ) # "01:21"
END_TIME = datetime( YR, MO, DY, 3, 0, 0 ) # "3:00"
duration_all = (END_TIME - START_TIME).seconds
d1 = ( duration_all - 600 ) // 3
#
rnd1 = randint(0,d1)
rnd2 = rnd1 + 300 + randint(0,d1)
rnd3 = rnd2 + 300 + randint(0,d1)
#
time1 = START_TIME + timedelta(seconds=rnd1)
time2 = START_TIME + timedelta(seconds=rnd2)
time3 = START_TIME + timedelta(seconds=rnd3)
#
print (time1)
print (time2)
print (time3)
Values of rnd1, rnd2and rnd3 are at least 5 minutes (300 seconds) apart.
Values of rnd3 cannot be greater than the total time interval (3 * d1 + 600). So all three times occur inside the interval.
NB You did not specify how much time each script runs. That is why I did not use time.sleep. A possible option would be threading.Timer (see python documentation).
Assume you store all the method.func() in an array and, as u described, subsequent scripts must be at least 5 mins after script1. They can be executed randomly, so we can launch multiple processes and let them sleep for a period before they can automatically start. (Timing is in seconds)
from multiprocessing import Process
import os
import random
import time
#store all scripts you want to execute here
eval_scripts = ["script1.test()","script2.test()", "script3.test()"]
#run job on different processes. non-blocking
def run_job(eval_string,time_sleep):
#print out script + time to test
print eval_string + " " + str(time_sleep)
time.sleep(time_sleep) #wait to be executed
#time to start
eval(eval_string)
def do_my_jobs():
start_time = []
#assume the duration between start_time and end_time is 60 mins, leave some time for other jobs after the first job (5-10 mins). This is just to be careful in case random.randrange returns the largest number
#adjust this according to the duration between start_time and end_time since calculating (end_time - star_time) is trivial.
proc1_start_time = random.randrange(60*60 - 10*60)
start_time.append(proc1_start_time)
#randomize timing for other procs != first script
for i in range(len(eval_scripts)-1):
#randomize time from (proc1_start_time + 5 mins) to (end_time - star_time)
start_time.append(random.randint(proc1_start_time+5*60, 60*60))
for i in range(len(eval_scripts)):
p_t = Process(target = run_job, args = (eval_scripts[i],start_time[i],))
p_t.start()
p_t.join()
Now all you need to do is to call do_my_jobs() only ONCE at START_TIME every day.

In Python, how can I put a thread to sleep until a specific time?

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.)
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
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))
Slightly more generalized solution (based off of Ross Rogers') in case you'd like to add minutes as well.
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())
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
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.
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)
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)
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.
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())
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()
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.
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())
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])
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.

Categories

Resources