Code resulting in weird datetime result on Google App Engine - python

I'm running a python program on Google App Engine that uses the datetime function. It's supposed to always return UTC time, but it seems to intermittently give an incorrect time. I'm not sure if there's an error with my code, or whether this is an issue on Google's side.
To get my local time (GMT +8:00), I run this function:
def SGTOffset(obj=datetime.now()):
if isinstance(obj, datetime):
return obj + timedelta(hours=8)
return SGTOffset(datetime.now())
and in my main program:
today_date = commoncode.SGTOffset().date()
logging.debug('Today date: %s | Today datetime: %s' % (today_date.isoformat(), commoncode.SGTOffset().isoformat()))
In the logs, I get the following:
[25/Nov/2015:09:00:02 -0800] "GET ... etc ...
01:00:03.287 Today date: 2015-11-25 | Today datetime: 2015-11-25T15:38:20.804300
So, Google kindly formats the log datetime to my locale (GMT +8), showing that the code is run at 01:00:03.287 (26th Nov, GMT +8). Also, this is confirmed by the timestamp 25/Nov/2015:09:00:02 -0800 provided. So the code was run at 25/Nov/2015 17:00:02 UTC time.
However, my code is outputting the wrong time. The datetime that is being generated in the code 2015-11-25T15:38:20.804300 has the timezone of GMT-9:30 instead of UTC time. (Because SGToffset() adds 8 hours to datetime)
This is quite catastrophic as I use the local datetime in many areas of my program. This is also happening intermittently only, because yesterday, the same code ran and got this log:
[24/Nov/2015:09:00:00 -0800] "GET ... etc ...
01:00:02.237 Today date: 2015-11-25 | Today datetime: 2015-11-25T01:00:01.768140
Which is correct! (Google's log timestamp 01:00:02.237 matches the time generated by SGTOffset() which is 01:00:01)
Could I know what is wrong with my program, or whether this is an issue with Google App Engine?
Thank you spending time to read this question!

The problem lies with the code.
Python stores a default value of the parameter obj for the function SGTOffset() when it is first defined (when the function object is first instantiated), instead of whenever the function is called as I intuitively expected. So, the datetime value will reflect the start time of the instance in GAE.
In order to get the current time whenever I call SGTOffset() without any parameters, I should instead have used:
def SGTOffset(obj=None): # Instead of obj=datetime.now() which is converted to obj='specific datetime' when instantiated
if isinstance(obj, datetime):
return obj + timedelta(hours=8)
return SGTOffset(datetime.now())
In this case, datetime.now() is called dynamically whenever it is required.
I arrived at this solution after viewing a question about old datetime values being returned.

I'm adding a quick answer to give you suggestions to make your code more readable:
obj is not a good variable name because it is not informative
No need for a recursive call to the function
Better to not use isinstance because is None gives you the needed functionality and your code will not work if some other instance type is given anyway.
Here is my suggestion:
def SGTOffset(dt=None):
if dt is None:
dt = datetime.now()
return dt + timedelta(hours=8)
Or if you prefer brevity:
def SGTOffset(dt=None):
return (dt or datetime.now()) + timedelta(hours=8)

Related

How do I test an AWS Lambda Function that is triggered by EventBridge but needs to be aware of daylight savings?

I have a lambda function that needs to run at a user defined frequency, defined via cron, at 9 AM Eastern time. For example, "weekdays at 9 AM" or "every alternate day at 9 AM".
EventBridge (via CDK) only allows me to define the cron trigger based on UTC. I'm triggering the function at 1 PM as well as 2 PM UTC, to account for daylight savings, and detecting whether daylight savings is in effect in the lambda code. That way, I can skip if it isn't 9 AM Eastern time and run the actual code when it really is 9 AM.
Here's how the code looks like:
def aws_lambda(event, context):
cron_expression = os.getenv('cron_expression') # The cron in CDK also passed as env var
local_time = localized_time_now()
if local_time.hour != 9:
return
window_start = local_time - timedelta(days=1)
window_stop = local_time
iter = croniter(cron_expression, window_start, ret_val=datetime)
prev_invocation = iter.get_prev()
if prev_invocation > window_start:
raise RuntimeError("Cron expression needs to allow for 24 hours to pass between events")
# Business logic here, using window_start and window_stop to query the DB
...
How do I test this function? I can't control the server time in EventBridge or Lambda, so it seems inherently untestable. It seems like the localized_time_now() function is key, but I'll just have to trust that it will always work properly in the context of AWS?
In my opinion localized_time_now() works with the TZ environment variable (look for TZ). So You have to set this varialble to Your timezone first. This is of course possible with CDK.
environment: {
TZ: 'Europe/Berlin',
}
After that datetime.datetime.now() should return the local time.
This is kind of awful that we can only set the event schedule in UTC, but it is how it is and running the lambda every hour and checking if it is the correct one is a valid solution for me here.
The second thing is how to test this. You can of course run this lambda locally with diffrent event timestamps. You can do unit tests or probably better at this point would be to use the lambda-local package. Consider also playng around with test events in the lambda console. There You can play around with the actual runtime.

Issue with datetime module

I'm trying to make a simple Python function that prints
two things;
What day of the week it currently is.
How many days it is until Christmas.
I don't get any errors when I run this, however nothing prints either.
In fact nothing at all happens when its run.
I've checked and I've installed the datetime module correctly (I think).
I'm not really sure what I'm doing wrong so any guidance would be helpful.
As you can probably guess, I'm relatively new to Python/stackoverflow.
Thanks.
Edit: I stupidly named the file itself datetime.py because yes I am that stupid.
from datetime import *
def day_of_week():
""" Python function to return a specified day of the week"""
today_date = date.today()
thisYear = today_date.year
xmas = date(thisYear,12,25)
day_of_week =("Mon","Tue","Wed","Thu","Fri","Sat","Sun")[today.weekday]
print("It's a %s." % (day_of_week))
days_to_xmas = (xmas - today_date).days
print("There are %i days to xmas!" & days_to_xmas)
Try calling the function in the script , you defined the function but never executed it.
day_of_week()

Python convert timestamps with specific timezone to datetime in UTC

I'm trying to convert a timestamp with a specific timezone(Europe/Paris) to a datetime format in UTC.
From my laptop it works with the solution below but when I'm executing my code in a remote server(AWS- Lambda function in Ireland), I've a shift of 1 hour because the local timezone of the server is different from mine.
How can I have a code who can work on my laptop and at the same time in a remote server(dynamically handle local timezone)?
import pytz
import datetime
def convert_timestamp_in_datetime_utc(timestamp_received):
utc = pytz.timezone('UTC')
now_in_utc = datetime.datetime.utcnow().replace(tzinfo=utc).astimezone(pytz.UTC)
fr = pytz.timezone('Europe/Paris')
new_date = datetime.datetime.fromtimestamp(timestamp_received)
return fr.localize(new_date, is_dst=None).astimezone(pytz.UTC)
Thanks
I am not sure what timestamp_received is, but I think what you want is utcfromtimestamp()
import pytz
from datetime import datetime
def convert_timestamp_in_datetime_utc(timestamp_received):
dt_naive_utc = datetime.utcfromtimestamp(timestamp_received)
return dt_naive_utc.replace(tzinfo=pytz.utc)
For completeness, here is another way to accomplish the same thing by referencing python-dateutil's tzlocal time zone:
from dateutil import tz
from datetime import datetime
def convert_timestamp_in_datetime_utc(timestamp_received):
dt_local = datetime.fromtimestamp(timestamp_received, tz.tzlocal())
if tz.datetime_ambiguous(dt_local):
raise AmbiguousTimeError
if tz.datetime_imaginary(dt_local):
raise ImaginaryTimeError
return dt_local.astimezone(tz.tzutc())
class AmbiguousTimeError(ValueError):
pass
class ImaginaryTimeError(ValueError):
pass
(I added in the AmbiguousTimeError and ImaginaryTimeError conditions to mimic the pytz interface.) Note that I'm including this just in case you have a similar problem that needs to make reference to the local time zone for some reason - if you have something that will give you the right answer in UTC, it's best to use that and then use astimezone to get it into whatever local zone you want it in.
How it works
Since you expressed that you were still a bit confused about how this works in the comments, I thought I would clarify why this works. There are two functions that convert timestamps to datetime.datetime objects, datetime.datetime.fromtimestamp(timestamp, tz=None) and datetime.datetime.utcfromtimestamp(timestamp):
utcfromtimestamp(timestamp) will give you a naive datetime that represents the time in UTC. You can then do dt.replace(tzinfo=pytz.utc) (or any other utc implementation - datetime.timezone.utc, dateutil.tz.tzutc(), etc) to get an aware datetime and convert it to whatever time zone you want.
fromtimestamp(timestamp, tz=None), when tz is not None, will give you an aware datetime equivalent to utcfromtimestamp(timestamp).replace(tzinfo=timezone.utc).astimezone(tz). If tz is None, instead of converting too the specified time zone, it converts to your local time (equivalent to dateutil.tz.tzlocal()), and then returns a naive datetime.
Starting in Python 3.6, you can use datetime.datetime.astimezone(tz=None) on naive datetimes, and the time zone will be assumed to be system local time. So if you're developing a Python >= 3.6 application or library, you can use datetime.fromtimestamp(timestamp).astimezone(whatever_timezone) or datetime.utcfromtimestamp(timestamp).replace(tzinfo=timezone.utc).astimezone(whatever_timezone) as equivalents.

Python, get timestamp for specific timezone

I am new to Python and I tried to find the answer from the existing posts, and I did my attempt but I can't find what I want.
I need to validate the time(based of china timezone regardless of where the client at) diff when the client send requests to my server .
From the existing posts I can find, I had tried:
import calendar
import datetime
import pytz
import time
tz = pytz.timezone('Asia/Shanghai') # china timezone
cn_time = datetime.datetime.now(tz) # get datetime for china
print calendar.timegm(cn_time.timetuple())*1000 #try to get the milliseconds
But I find that the result is far away from my java server's answer from Joda Time:
DateTime serverDt = new DateTime(DateTimeZone.forID("Asia/Shanghai"));
long milis = serverDt.getMillis();
One test case is:
python : 1457005502000
java: 1456976702999
seonds diff from int secDiff = Seconds.secondsBetween(dt, serverDt).getSeconds(); is -28799 which is -7 hours
Note: My machine is at china timezone.
Your code tries to find the current Unix time. That value does not depend on a specific timezone (it is the same number on all computers with synchonized (e.g., using ntp) clocks whatever (perhaps different) time zones they use). Just call milis = time.time() * 1000, to get the same value as the java server.
If you need to get the Unix time that corresponds to a given timezone-aware datetime (such as created by datetime.now(tz) at some point) then just call posix_timestamp = cn_time.timestamp().
There is no datetime.timestamp() method on Python 2. You could emulate it easily:
def timestamp(aware_dt, epoch=datetime(1970, 1, 1, tzinfo=pytz.utc)):
return (aware_dt - epoch).total_seconds()
Usage: posix_timestamp = timestamp(cn_time). See more details in this answer.

Intermittent wrong date from time.strftime()

I have an Appengine app that still runs on the python2.5 runtime, which means it is single threaded. I have a school class:
class School(db.Model):
...
dateformat=db.StringProperty(default='%a %e %b',indexed=False) # For bookings
timeformat=db.StringProperty(default='%l:%M%P',indexed=False) # For bookings
def date(self,t):
return time.strftime(self.dateformat,time.gmtime(t))
def datetime(self,bt,et=None):
return time.strftime(self.dateformat+' '+self.timeformat,time.gmtime(bt))+\
(time.strftime(' - '+self.timeformat,time.gmtime(et)) if et else '')
def time(self,t):
return time.strftime(self.timeformat,time.gmtime(t))
Then when I want to format a date in the school's chosen format, I do this:
s=School.get_by_id(the_id)
date_string=s.date(the_timestamp)
Very occasionally, I get a date that is exactly a week out. So instead of "Wed 2 May" I get "Wed 9 May". This has been reported three times in the last week, out of probably tens of thousands of cases. All exactly a week out, none with any other time difference.
Nothing's changed in that part of my code for ages so I don't understand why this should suddenly start happening. Because it's single threaded, there shouldn't be issues with strftime, and I can't find any reports of thread issues with it anyway anyway.
Any ideas?

Categories

Resources