I'm having issues with timezones and time offsets when working with Python, the Google Calendar API, and a client device operating in local time.
I have a Raspberry Pi which acts as a booking display outside a shared room. A Python script is called periodically to pull in the next upcoming calendar event from the Google Calendar API, split the result into variables, then pass those variables back to the bash script which called it.
I built it in the winter—when the UK's timezone has equivalence to UTC—and it worked perfectly. Now, in British Summer Time, it doesn't. The comparison of time objects to check whether or not the meeting is currently in progress doesn't work, and the room is showing as available when it isn't.
I've read and read (then re-read and re-read) articles and explainers about working with timezones and time offsets in Python, and I just cannot get my head around it. It doesn't help that I don't really understand object-based programming, so the example scripts provided don't mean much to me!
Ideally, I would simply like everything to work using local time. The Raspberry Pi is connected to the internet and its time is updated automatically, and the Google Calendar is correctly set to the UK timezone. How could I go about making all references to time 'local', so the time objects everywhere in the Python script are always current UK time?
Please help. Stack Overflow has been such a rich source of knowledge in the past that I've never needed to ask a question myself, but this is making my brain hurt! Thank you!
This is what I've got, which is a modified version of Google's own example script:
service = build('calendar', 'v3', credentials=creds)
# Call the Calendar API
timeStamp_now = datetime.datetime.utcnow()
timeStamp_12 = timeStamp_now + timedelta(hours = 12)
now = timeStamp_now.isoformat() + 'Z' # 'Z' indicates UTC time
now12 = timeStamp_12.isoformat() + 'Z' # 'Z' indicates UTC time
events_result = service.events().list(calendarId='################resource.calendar.google.com', timeZone="Europe/London", timeMin=now,
maxResults=1, singleEvents=True, showDeleted=False, timeMax=now12,
orderBy='startTime').execute()
events = events_result.get('items', [])
if not events:
print('meetingStart="NoEvents"')
for event in events:
startTime=event['start'].get('dateTime')
endTime=event['end'].get('dateTime')
eventTitle=event['summary']
eventOrganizer=event['creator'].get('email')
try:
eventConferencing=event['conferenceData'].get('conferenceSolution').get('name')
except:
eventConferencing=(' ')
if startTime <= now <= endTime:
inProgress=('true')
else:
inProgress=('false')
safeEventTitle= ""
for i in eventTitle:
num = ord(i)
if (num >=0) :
if (num <=127) :
safeEventTitle= safeEventTitle + i
print('meetingStart="' + startTime + '"')
print('meetingEnd="' + endTime + '"')
print('meetingTitle="' + safeEventTitle + '"')
print('meetingOrganizer="' + eventOrganizer + '"')
print('meetingConferencing="' + eventConferencing + '"')
print('meetingInProgress="' + inProgress + '"')
The Raspberry Pi's time is set correctly:
pi#raspberrypi:~ $ date
Fri 16 Jul 20:31:38 BST 2021
The Python script returns this when run:
pi#raspberrypi:~ python /myPythonScript.sh
meetingStart="2021-07-16T20:00:00+01:00"
meetingEnd="2021-07-16T21:00:00+01:00"
meetingTitle="Test"
meetingOrganizer="abc#def.com"
meetingConferencing=" "
meetingInProgress="false"
See a sample code below that will adjust your datetime to UTC regardless of Daylight Saving Time.
Code:
import datetime
import pytz
timeZone = pytz.timezone("Europe/London")
dt = datetime.datetime.utcnow()
print("datetime now is:\t", dt)
local_dt = timeZone.localize(dt, is_dst=None)
utc_dt = local_dt.astimezone(pytz.utc)
print("Non-DST time is:\t", utc_dt) # prints utc regardless of DST
Output using bst datetime:
Output using utc datetime:
In your case:
timeZone = pytz.timezone("Europe/London")
dt = datetime.datetime.utcnow()
local_dt = timeZone.localize(dt, is_dst=None)
# timeStamp_now below should be in UTC already
timeStamp_now = local_dt.astimezone(pytz.utc)
Reference:
How to convert local time string to UTC?
Python daylight savings time
So I'm pulling data from a druid database for a given time period. For example:
time_from = '2020-11-26T06:00:00'
time_to = '2020-11-29T06:00:00'
Between those times^.
However, depending on the day, the time is either MST (MOUNTAIN standard time) or MDT (MOUNTAIN daylight savings time), which determines the -06:00 or -07:00 at the end.
Is the following code a correct way for accounting for daylight savings time?
from datetime import datetime
import pytz #must run 'pip install pytz'
#ENTER TIMES HERE FOR MAIN.PY
time_from_1='2020-11-26T06:00:00'
time_to_1='2020-11-29T06:00:00'
#CHECKING MST/MDT AND UPDATING TIMES IF NECESSARY
naive = datetime.strptime(time_from_1, '%Y-%m-%dT%H:%M:%S')
mountain = pytz.timezone('America/Denver')
time_from_aware = mountain.localize(naive, is_dst=None)
naive = datetime.strptime(time_to_1, '%Y-%m-%dT%H:%M:%S')
mountain = pytz.timezone('America/Denver')
time_to_aware = mountain.localize(naive, is_dst=None)
def is_dst(aware_dt):
assert aware_dt.tzinfo is not None
assert aware_dt.tzinfo.utcoffset(aware_dt) is not None
return bool(aware_dt.dst())
if is_dst(time_from_aware)==True:
time_from=time_from_1+'-06:00'
else:
time_from=time_from_1+'-07:00'
if is_dst(time_to_aware)==True:
time_to=time_to_1+'-06:00'
else:
time_to=time_to_1+'-07:00'
print(time_from)
print(time_to)
schedule.wednesday.at("13:15").do(job)
With this code I can run fuction on wednesday 13:15. But I want to make action like, "run function next day morning '10:00' " How can do this?
Wrap job to return schedule.CancelJob such that it runs only once. Then schedule it to run every day at 10:00.
def job_once():
job()
return schedule.CancelJob
schedule.every().day.at('10:00').do(job_once)
Caveat: if the current time is before 10:00, then the job will run this day at 10:00. There's no way to define an initial delay in the schedule library - but we could get the name of the next day and schedule on this day.
import datetime
tomorrow = (datetime.datetime.now() + datetime.timedelta(days=1)).strftime('%A').lower()
getattr(schedule.every(), tomorrow).at('10:00').do(job_once)
Python contains a simple library called sched:
import sched, time
def functionYouWantToRun():
...
s = sched.scheduler(time.localtime, time.sleep)
s.enterabs(time.strptime('[three character day] [three character month] [day of month] [time (hh:mm:ss)] [year]')
s.run()
def job_that_executes_once():
# Do some work that only needs to happen once...
return schedule.CancelJob
if __name__ == "__main__":
from datetime import datetime, timedelta
import schedule
schedule.every().day.at("10:00").do(job_that_executes_once)
I was going to go with time offset, but then realised the above is easier. That will run it the next day.
If you need to run several days in the future, then you are back to datetime offsets:
x=datetime.today()
y = x.replace(day=x.day, hour=10, minute=0, second=0, microsecond=0) +
timedelta(days=1)
yDay = y.strftime('%A')
How can I run a function in Python, at a given time?
For example:
run_it_at(func, '2012-07-17 15:50:00')
and it will run the function func at 2012-07-17 15:50:00.
I tried the sched.scheduler, but it didn't start my function.
import time as time_module
scheduler = sched.scheduler(time_module.time, time_module.sleep)
t = time_module.strptime('2012-07-17 15:50:00', '%Y-%m-%d %H:%M:%S')
t = time_module.mktime(t)
scheduler_e = scheduler.enterabs(t, 1, self.update, ())
What can I do?
Reading the docs from http://docs.python.org/py3k/library/sched.html:
Going from that we need to work out a delay (in seconds)...
from datetime import datetime
now = datetime.now()
Then use datetime.strptime to parse '2012-07-17 15:50:00' (I'll leave the format string to you)
# I'm just creating a datetime in 3 hours... (you'd use output from above)
from datetime import timedelta
run_at = now + timedelta(hours=3)
delay = (run_at - now).total_seconds()
You can then use delay to pass into a threading.Timer instance, eg:
threading.Timer(delay, self.update).start()
Take a look at the Advanced Python Scheduler, APScheduler: http://packages.python.org/APScheduler/index.html
They have an example for just this usecase:
http://packages.python.org/APScheduler/dateschedule.html
from datetime import date
from apscheduler.scheduler import Scheduler
# Start the scheduler
sched = Scheduler()
sched.start()
# Define the function that is to be executed
def my_job(text):
print text
# The job will be executed on November 6th, 2009
exec_date = date(2009, 11, 6)
# Store the job in a variable in case we want to cancel it
job = sched.add_date_job(my_job, exec_date, ['text'])
Might be worth installing this library: https://pypi.python.org/pypi/schedule, basically helps do everything you just described. Here's an example:
import schedule
import time
def job():
print("I'm working...")
schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
while True:
schedule.run_pending()
time.sleep(1)
Here's an update to stephenbez' answer for version 3.5 of APScheduler using Python 2.7:
import os, time
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime, timedelta
def tick(text):
print(text + '! The time is: %s' % datetime.now())
scheduler = BackgroundScheduler()
dd = datetime.now() + timedelta(seconds=3)
scheduler.add_job(tick, 'date',run_date=dd, args=['TICK'])
dd = datetime.now() + timedelta(seconds=6)
scheduler.add_job(tick, 'date',run_date=dd, kwargs={'text':'TOCK'})
scheduler.start()
print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
try:
# This is here to simulate application activity (which keeps the main thread alive).
while True:
time.sleep(2)
except (KeyboardInterrupt, SystemExit):
# Not strictly necessary if daemonic mode is enabled but should be done if possible
scheduler.shutdown()
I've confirmed the code in the opening post works, just lacking scheduler.run(). Tested and it runs the scheduled event. So that is another valid answer.
>>> import sched
>>> import time as time_module
>>> def myfunc(): print("Working")
...
>>> scheduler = sched.scheduler(time_module.time, time_module.sleep)
>>> t = time_module.strptime('2020-01-11 13:36:00', '%Y-%m-%d %H:%M:%S')
>>> t = time_module.mktime(t)
>>> scheduler_e = scheduler.enterabs(t, 1, myfunc, ())
>>> scheduler.run()
Working
>>>
I ran into the same issue: I could not get absolute time events registered with sched.enterabs to be recognized by sched.run. sched.enter worked for me if I calculated a delay, but is awkward to use since I want jobs to run at specific times of day in particular time zones.
In my case, I found that the issue was that the default timefunc in the sched.scheduler initializer is not time.time (as in the example), but rather is time.monotonic. time.monotonic does not make any sense for "absolute" time schedules as, from the docs, "The reference point of the returned value is undefined, so that only the difference between the results of consecutive calls is valid."
The solution for me was to initialize the scheduler as
scheduler = sched.scheduler(time.time, time.sleep)
It is unclear whether your time_module.time is actually time.time or time.monotonic, but it works fine when I initialize it properly.
dateSTR = datetime.datetime.now().strftime("%H:%M:%S" )
if dateSTR == ("20:32:10"):
#do function
print(dateSTR)
else:
# do something useful till this time
time.sleep(1)
pass
Just looking for a Time of Day / Date event trigger:
as long as the date "string" is tied to an updated "time" string, it works as a simple TOD function. You can extend the string out to a date and time.
whether its lexicographical ordering or chronological order comparison,
as long as the string represents a point in time, the string will too.
someone kindly offered this link:
String Comparison Technique Used by Python
had a really hard time getting these answers to work how i needed it to,
but i got this working and its accurate to .01 seconds
from apscheduler.schedulers.background import BackgroundScheduler
sched = BackgroundScheduler()
sched.start()
def myjob():
print('job 1 done at: ' + str(dt.now())[:-3])
dt = datetime.datetime
Future = dt.now() + datetime.timedelta(milliseconds=2000)
job = sched.add_job(myjob, 'date', run_date=Future)
tested accuracy of timing with this code:
at first i did 2 second and 5 second delay, but wanted to test it with a more accurate measurement so i tried again with 2.55 second delay and 5.55 second delay
dt = datetime.datetime
Future = dt.now() + datetime.timedelta(milliseconds=2550)
Future2 = dt.now() + datetime.timedelta(milliseconds=5550)
def myjob1():
print('job 1 done at: ' + str(dt.now())[:-3])
def myjob2():
print('job 2 done at: ' + str(dt.now())[:-3])
print(' current time: ' + str(dt.now())[:-3])
print(' do job 1 at: ' + str(Future)[:-3] + '''
do job 2 at: ''' + str(Future2)[:-3])
job = sched.add_job(myjob1, 'date', run_date=Future)
job2 = sched.add_job(myjob2, 'date', run_date=Future2)
and got these results:
current time: 2020-12-10 19:50:44.632
do job 1 at: 2020-12-10 19:50:47.182
do job 2 at: 2020-12-10 19:50:50.182
job 1 done at: 2020-12-10 19:50:47.184
job 2 done at: 2020-12-10 19:50:50.183
accurate to .002 of a second with 1 test
but i did run a lot of tests and accuracy ranged from .002 to .011
never going under the 2.55 or 5.55 second delay
#everytime you print action_now it will check your current time and tell you should be done
import datetime
current_time = datetime.datetime.now()
current_time.hour
schedule = {
'8':'prep',
'9':'Note review',
'10':'code',
'11':'15 min teabreak ',
'12':'code',
'13':'Lunch Break',
'14':'Test',
'15':'Talk',
'16':'30 min for code ',
'17':'Free',
'18':'Help ',
'19':'watever',
'20':'watever',
'21':'watever',
'22':'watever'
}
action_now = schedule[str(current_time.hour)]
Hi Everyone i have googles to my hearts content but have not found the answer.
Basically I want to add user inputted time to the current time.
This is just a small project I'm working on while learning Python.
So if the current time is 17:16 and the user wants to add 1hr 30 to that. how would i do it.
This is what i have:
import datetime
flex = input("Enter your flex amount in HHMM:")
flex = flex[0]+flex[1]+"-"+flex[2]+flex[3]
time = datetime.datetime.now().strftime("%H-%M")
balance = time+flex
print(time)
print(flex)
print(balance)
I have now tried
import datetime
flex = input("Enter your flex amount in HHMM:")
time = datetime.datetime.now().strftime("%H-%M")
flex = flex[0]+flex[1]+"-"+flex[2]+flex[3]
time = time[0]+time[1]+"-"+time[2]+time[3]
balance = datetime.timedelta(hours=int(time[0]+time[1]),
minutes=int(time[2]+time[3]) +
datetime.timedelta(hours=int(flex[0]+flex[1]),
minutes=int(flex[2]+flex[3]))
But now its complaining about its expecting an integer. but if i change it ot an integer will that not defeat the purpose of me wanting to add is as time.
Thanks
I got it to work using the answer. This is what it looks like now thanks pal.
from datetime import timedelta as td
import datetime as da
#flex = input("Enter your flex amount in HHMM:")
flex = "0134"
now = da.datetime.now()
user_hours = int(flex[:2])
user_minute = int(flex[2:5])
delay = td(hours=user_hours, minutes=user_minute)
balance = da.datetime.now()+delay
print("Lunch: " +str(lunch))
print("Time when balance at 00:00 : " +str(balance))
print("Now: " +str(now))
Simple using timedelta create an offset indicated by timedelta object and ad it to your time object (working the same with date and datetime too).
from datetime import timedelta, datetime
actual_time = datetime.now()
user_hours = int(flex[:3])
user_minute = int(flex[2:5])
delay = timedelta(hours=user_hours, minutes=user_minute)
print(datetime.now()+delay)
So if the current time is 17:16 and the user wants to add 1hr 30 to
that. how would i do it.
You can use timedelta, i.e.:
new_time = datetime.datetime.now() + datetime.timedelta(hours=1, minutes=30) # or simply minutes=90, etc...
Cool so when tring
balance = datetime.timedelta(hours=int(time[0]+time[1]), minutes=int(time[2]+time[3]) + datetime.timedelta(hours=int(flex[0]+flex[1]), minutes=int(flex[2]+flex[3]))
its complaining that its expecting an interger not a time delta