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
Related
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)
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)]
I am trying to add expiry time to JWE which I am generating using jwcrypto library in the following way
from jwcrypto import jwe, jwk, jwt
from datetime import datetime, timedelta
import time
# create JWK from existing key
jwk_str = '{"k":"29Js2yXM6P_4v9K1mHDlYVHw8Xvm_GEhvMTvKTRLRzY","kty":"oct"}'
jwk_key = jwk.JWK.from_json(jwk_str)
# calculate expiry time
d = datetime.now() + timedelta(seconds=5)
epoch = datetime.utcfromtimestamp(0)
total_seconds = (d - epoch).total_seconds()
# Add exp to the claims
claims={"exp": total_seconds, "sub": "Some random payload"}
print(claims)
jwttoken = jwt.JWT(header={"alg": "A256KW", "enc": "A256CBC-HS512"}, claims=claims)
jwttoken.make_encrypted_token(jwk_key)
jwetokenstr = jwttoken.serialize()
print(jwetokenstr)
# wait for 10 seconds to cross the expiry time
time.sleep(10)
jwttoken = jwt.JWT()
jwttoken.deserialize(token, jwk_key) # Ideally this line should fail as expiry is reached but it doesn't
print(jwttoken.claims)
I am getting the payload, but expiry claim is not read and doesn't fail on expiry.
What I am doing wrong ?
This ends up reducing to a datetime manipulation bug.
The exp claim of a JSON web token should filled out with the seconds from epoch of the expiration time.
datetime.now() returns a local time (not UTC time) datetime.datetime object. The code above then goes on to subtract this local time datetime.datetime object from the UTC time datetime.datetime object of 0-epoch time and evaluates the total seconds between these two to determine the expiry time. However, because this is comparing a local time datetime to a UTC time datetime, the number of seconds here is actually off of the epoch time by a constant factor of your local timezone difference from UTC.
For example, if I live in a place where the time is 5 hours earlier than UTC, I will actually use an epoch time that is 5 * 60 * 60 seconds off of the true epoch time I want for the expiry with this code.
Instead you could simply use round(time.time()) + x where x is the number of seconds forward in the future the JWT should expire. time.time() returns the seconds from epoch (but as a float so you need to round) from epoch.
For example:
from jwcrypto import jwe, jwk, jwt
from datetime import datetime, timedelta
import time
jwk_str = '{"k":"29Js2yXM6P_4v9K1mHDlYVHw8Xvm_GEhvMTvKTRLRzY","kty":"oct"}'
jwk_key = jwk.JWK.from_json(jwk_str)
jwt_valid_seconds = 3
expiry_time = round(time.time()) + jwt_valid_seconds
claims={"exp": expiry_time, "sub": "Some random payload"}
jwttoken = jwt.JWT(header={"alg": "A256KW", "enc": "A256CBC-HS512"}, claims=claims)
jwttoken.make_encrypted_token(jwk_key)
jwetokenstr = jwttoken.serialize()
jwttoken2 = jwt.JWT()
jwttoken2.deserialize(jwetokenstr, jwk_key)
print('This should succeed because we are deserializing immediately before the JWT has expired:')
print(jwttoken2.claims)
# Wait for the JWT to expire, and then extra time for the leeway.
leeway = 60
time.sleep(leeway + jwt_valid_seconds + 1)
jwttoken2 = jwt.JWT()
print('\nThis should fail due to the JWT expiring:')
jwttoken2.deserialize(jwetokenstr, jwk_key)
gives the output
(env) $ python jwe_expiry.py
This should succeed because we are deserializing immediately before the JWT has expired:
{"exp":1576737332,"sub":"Some random payload"}
This should fail due to the JWT expiring:
Traceback (most recent call last):
File "jwe_expiry.py", line 26, in <module>
jwttoken2.deserialize(jwetokenstr, jwk_key)
File "... python3.7/site-packages/jwcrypto/jwt.py", line 493, in deserialize
self._check_provided_claims()
File "... python3.7/site-packages/jwcrypto/jwt.py", line 370, in _check_provided_claims
self._check_default_claims(claims)
File "... python3.7/site-packages/jwcrypto/jwt.py", line 351, in _check_default_claims
self._check_exp(claims['exp'], time.time(), self._leeway)
File "... python3.7/site-packages/jwcrypto/jwt.py", line 333, in _check_exp
claim, limit, leeway))
jwcrypto.jwt.JWTExpired: Expired at 1576737332, time: 1576737392(leeway: 60)
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
I have the following:
import psycopg2
from openpyxl import Workbook
wb = Workbook()
wb.active =0
ws = wb.active
ws.title = "Repair"
ws.sheet_properties.tabColor = "CCFFCC"
print(wb.sheetnames)
import datetime
import smtplib
import mimetypes
import logging
LOG_FILENAME = 'log-production.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
logging.debug('This message should go to the log file')
from datetime import date, timedelta
import os, sys
try:
conn = psycopg2.connect("connection string")
except:
print "I am unable to connect to the database"
cur = conn.cursor()
cur.execute("""SELECT ams.unit.line,ams.unit.work_order,ams.unit.model_num, ams.unit.revision ,ams.unit.serial_num,ams.unit.lpn, ams.unit_repair_detail.level_1_name as level_1,
ams.unit_repair_detail.level_2_name as level_2, ams.unit_repair_detail.level_3_name as level_3,ams.unit_repair_detail.level_4_name as level_4,ams.unit_repair.date_started AT TIME ZONE 'UTC' as date_started,ams.unit_repair.date_completed AT TIME ZONE 'UTC' as date_completed
FROM ams.unit_repair
left join
ams.unit
on ams.unit_repair.unit_id=ams.unit.id and
LOWER(ams.unit_repair.line) = LOWER(ams.unit.line)
right join
ams.unit_repair_detail
on ams.unit_repair.sid = ams.unit_repair_detail.unit_repair_sid
WHERE
LOWER(ams.unit.line) like ('%') and
ams.unit_repair_detail.date_created >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval '24 hours')
AND ams.unit_repair_detail.date_created <= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')
and LOWER(ams.unit.model_num) like LOWER('%')
order by model_num asc""")
rows = cur.fetchall()
print "\nShow me the databases:\n"
col_names = ["Line","Work order","Model number","Revision","Serial number","Lpn","Level 1","Level 2","Level 3","Level 4","Date started","Date completed"]
ws.append(col_names)
for row in rows:
ws.append(row)
This was working but after the daylight savings time change everything broke... The query returns the correct data on the db but when I run it from the python script and the file is created it is still in UTC time. I don't know what I am doing that is converting my dates back to UTC... Can anybody help me? I have tried setting the timezones at the top to be central so it converts the UTC to central with no luck
cur.execute("SET TIME ZONE 'America/Chicago';")
I have also tried
>>> import time
>>> offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
>>> offset / 60 / 60 * -1
I also tried changing my AT TIME ZONE UTC TO CST and no luck... I have tried multiple solutions on the web but nothing appears to be working. Any help will be greatly appreciated!!
just in case anybody runs into something like this in the future... I found the problem if you add the following to the query
`at time zone 'America/Chicago'
it will resolve the problem. Somehow the at time zone UTC is not enough you still need to specify the output timezone