How do I set the timezone of a datetime instance that just came out of the datastore?
When it first comes out it is in UTC. I want to change it to EST.
I'm trying, for example:
class Book( db.Model ):
creationTime = db.DateTimeProperty()
When a Book is retrieved, I want to set its tzinfo immediately:
book.creationTime.tzinfo = EST
Where I use this example for my EST object
However I get:
attribute 'tzinfo' of 'datetime.datetime' objects is not writable
I've seen a number of answers that recommend pytz and python-dateutil, but I really want an answer to this question.
datetime's objects are immutable, so you never change any of their attributes -- you make a new object with some attributes the same, and some different, and assign it to whatever you need to assign it to.
I.e., in your case, instead of
book.creationTime.tzinfo = EST
you have to code
book.creationTime = book.creationTime.replace(tzinfo=EST)
If you're receiving a datetime that's in EST, but doesn't have its tzinfo field set, use dt.replace(tzinfo=tz) to assign a tzinfo without modifying the time. (Your database should be doing this for you.)
If you're receiving a datetime that's in UDT, and you want it in EST, then you need astimezone. http://docs.python.org/library/datetime.html#datetime.datetime.astimezone
In the vast majority of cases, your database should be storing and returning data in UDT, and you shouldn't need to use replace (except possibly to assign a UDT tzinfo).
What you want is right there in the docs.
from datetime import tzinfo, timedelta, datetime
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
DSTSTART = datetime(1, 4, 1, 2)
DSTEND = datetime(1, 10, 25, 1)
def first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
class USTimeZone(tzinfo):
def __init__(self, hours, reprname, stdname, dstname):
self.stdoffset = timedelta(hours=hours)
self.reprname = reprname
self.stdname = stdname
self.dstname = dstname
def __repr__(self):
return self.reprname
def tzname(self, dt):
if self.dst(dt):
return self.dstname
else:
return self.stdname
def utcoffset(self, dt):
return self.stdoffset + self.dst(dt)
def dst(self, dt):
if dt is None or dt.tzinfo is None:
# An exception may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
# Find first Sunday in April & the last in October.
start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
# Can't compare naive to aware objects, so strip the timezone from
# dt first.
if start <= dt.replace(tzinfo=None) < end:
return HOUR
else:
return ZERO
now = datetime.now()
print now
print now.tzinfo
Eastern = USTimeZone(-5, 'Eastern', 'EST', 'EDT')
now_tz_aware = now.replace(tzinfo=Eastern)
print now_tz_aware
output:
2010-01-18 17:08:02.741482
None
2010-01-18 17:08:02.741482-05:00
Related
I am creating a class that works with time ranges,
class Time_Range:
"""A class that models time ranges."""
def __init__(self, start:str, end:str, time_format='%m/%d/%Y %H:%M'):
"""Creates a time range.
Parameters:
start : str - start time.
end : str - end time.
"""
start_time = datetime.strptime(start, time_format)
end_time = datetime.strptime(end, time_format)
if start_time > end_time:
raise ValueError("End time is before start time.")
self.start = start_time
self.end = end_time
def __len__(self):
"""Returns the elapsed time in the time range."""
return self.end - self.start
I defined len() to be the time elapsed as a timedelta object, but im getting an error
"TypeError: 'datetime.timedelta' object cannot be interpreted as an integer"
In my opinion, this is a perfectly reasonable way to implement len() in this class, but Python won't allow it. Is there a better way to do this? Do I have to resort to writing my own length method?
It seems you are trying to fit something that isn't into a class.
If your class has only two methods, one of which is __init__, then (in general) it should be a function instead:
def time_range(start:str, end:str, time_format='%m/%d/%Y %H:%M'):
start_time = datetime.strptime(start, time_format)
end_time = datetime.strptime(end, time_format)
if start_time > end_time:
raise ValueError("End time is before start time.")
return end_time - start_time
Especially since you are producing a TimeDelta anyway.
The functionality I'm looking for is to:
Call get_values() for each number of times.
A Metric object is initialised passing the date values in.
Find the datetime and then append it to the cumulative list.
Once the 'x in y' loop has finished, return the list.
The problem I've having is that I don't have any instance variables to pass through, which means I have to pass a 'None' object. I guess returning the Metric object is viable but I'd prefer if there was another option.
class Metric():
cumulative_time = []
def __init__(self, first_date, second_date):
self.first_date = first_date
self.second_date= second_date
def get_datetime(self):
datetime = self.first_date - self.second_date
return datetime
def append_cumulative_value(self, value):
self.cumulative_time.append(value)
def get_time_list(self):
return self.cumulative_time
def get_values():
first_date = ...
second_date = ...
metric = Metric(first_date, second_date)
date = metric.get_datetime()
metric.append_cumulative_value(date)
def main():
for x in y:
get_values()
cumulative_time_list = Metric(None, None).get_time_list()
if __name__ == "__main__":
main()
I have a function that uses a datetime object as default value:
from datetime import datetime, timedelta
from random import randint
def getTime(date = datetime.now()):
i = randint(1,300)
date = date - timedelta(seconds = i)
return date
Now, I need to check if the date variable inside the function was given by another function or was used the default one datetime.now(). If was used the default one, then subtract i seconds, else return the date that was given.
You can do it as follows:
def my_function(date=None):
if date is None:
# The default is used
date = datetime.now()
...
Assuming that you want "now" to be computed every time:
def getTime(date=None):
return date or datetime.now() - timedelta(seconds=randint(1,300))
Otherwise:
Introduce a default arg:
def getTime(date=None, _default=datetime.now()):
return date or _default - timedelta(seconds=randint(1,300))
Or create a decorator:
def just_return_if_provided(f):
def inner(date=None):
return date or f(date)
return inner
#just_return_if_provided
def getTime(date=datetime.now()):
return date - timedelta(seconds=randint(1,300))
I wrote a class that would allow me to add days (integers) to dates (string %Y-%m-%d). The objects of this class need to be JSON serializable.
Adding days in the form of integers to my objects works as expected. However json.dumps(obj) returns too much info ("2016-03-23 15:57:47.926362") for my original object. Why ? How would I need to modify the class to get ""2016-03-23" instead ? Please see the example below.
Code:
from datetime import datetime, timedelta
import json
class Day(str):
def __init__(self, _datetime):
self.day = _datetime
def __str__(self):
return self.day.date().isoformat()
def __repr__(self):
return "%s" % self.day.date().isoformat()
def __add__(self, day):
new_day = self.day + timedelta(days=day)
return Day(new_day).__str__()
def __sub__(self, day):
new_day = self.day - timedelta(days=day)
return Day(new_day).__str__()
if __name__ == "__main__":
today = Day(datetime.today())
print(today) # 2016-03-23
print(json.dumps(today)) # "2016-03-23 15:57:47.926362"
print(today+1) # 2016-03-24
print(json.dumps(today+1)) # "2016-03-24"
print(today-1) # 2016-03-22
print(json.dumps(today-1)) # "2016-03-22"
Update. Here's my final code for those interested:
from datetime import datetime, timedelta
import json
class Day(str):
def __init__(self, datetime_obj):
self.day = datetime_obj
def __new__(self, datetime):
return str.__new__(Day, datetime.date().isoformat())
def __add__(self, day):
new_day = self.day + timedelta(days=day)
return Day(new_day)
def __sub__(self, day):
new_day = self.day - timedelta(days=day)
return Day(new_day)
if __name__ == "__main__":
today = Day(datetime.today())
print(type(today))
print(today) # 2016-03-23
print(json.dumps(today)) # "2016-03-23"
print(today + 1) # 2016-03-24
print(json.dumps(today + 1)) # "2016-03-24"
print(today - 1) # 2016-03-22
print(json.dumps(today - 1)) # "2016-03-22"
print(json.dumps(dict(today=today))) # {"today": "2016-03-23"}
print(json.dumps(dict(next_year=today+365))) # {"next_year": "2017-03-23"}
print(json.dumps(dict(last_year=today-366))) # {"last_year": "2015-03-23"}
Cool! Let's go with it. You are seeing:
print(json.dumps(today)) # "2016-03-23 15:57:47.926362"
Because somewhere in the encoding process, when deciding how to serialize what was passed to it, json.dumps calls isinstance(..., str) on your object. This returns True and your object is serialized like this string it secretly is.
But where does the "2016-03-23 15:57:47.926362" value come from?
When you call day = Day(datetime_obj), two things happen:
__new__ is called to instantiate the object. You haven't provided a __new__ method, so str.__new__ is used.
__init__ is called to initialize the object.
So day = Day(datetime_obj) effectively translates to:
day = str.__new__(Day, datetime_obj)
For json.dumps, your object will be a str, but the value of the str is set to the default string representation of datetime_obj. Which happens to be the full format you are seeing. Builtins, man!
I played around with this, and it seems if you roll your own __new__ (which is slightly exciting territory, tread carefully) which intercepts the str.__new__ call, you ~~should~~ be fine:
class Day(str):
def __new__(self, datetime):
return str.__new__(Day, datetime.date().isoformat())
But you didn't hear it from me if the whole thing catches fire.
PS The proper way would be to subclass JSONEncoder. But there is zero fun in it.
PS2 Oh, shoot, I tested this on 2.7. I may be completely out there, and if I am, just give me a "you tried" badge.
The reason for the json.dumps(today)'s behavior is not as obvious as it might appear at the first glance. To understand the issue, you should be able to answer two questions:
where does the string value that includes the time come from?
why Day.__str__ is not called by json encoder ? Should it?
Here're some prerequisites:
datetime.today() method is similar to datetime.now() -- it includes the current time (hour, minutes, etc). You could use date.today(), to get only date.
str creates immutable objects in Python; its value is set in the __new__ method that you have not overriden and therefore the default conversion str(datetime.today()) is used to initialize Day's value as a string. It creates the string value that includes both date and time in your case. You could override __new__, to get a different string value:
def __new__(cls, _datetime):
return str.__new__(cls, _datetime.date())
Day is a str subclass and therefore its instances are encoded as JSON strings
str methods return str objects instead of the corresponding subclass objects unless you override them e.g.:
>>> class S(str):
... def duplicate(self):
... return S(self * 2)
...
>>> s = S('abc')
>>> s.duplicate().duplicate()
'abcabcabcabc'
>>> s.upper().duplicate()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'duplicate'
s.upper() returns str object instead of S here and the following .duplicate() call fails.
In your case, to create the corresponding JSON string, json.dumps(today) performs an operation (re.sub() call in json.encode.encode_basestring()) on the today object that uses its value as a string i.e., the issue is that neither re.sub() nor encode_basestring() call __str__() method on instances of str subclasses. Even if encode_basestring(s) were as simple as return '"' + s + '"'; the result would be the same: '"' + today returns a str object and Day.__str__ is not called.
I don't know whether re module should call str(obj) in functions that accept isinstance(obj, str). Or whether json.encode.encode_basestring() should do it (or neither).
If you can't fix Day class; you could patch json.encode.encode_basestring() to call str(obj), to get a desirable JSON representation for str subtype instances (if you want to get the value returned by __str__() method -- putting aside whether it is wise to override __str__() on a str subclass in the first place):
import json
for suffix in ['', '_ascii']:
function_name = 'encode_basestring' + suffix
orig_function = getattr(json.encoder, function_name)
setattr(json.encoder, function_name, lambda s,_e=orig_function: _e(str(s)))
Related Python issue: Cannot override JSON encoding of basic type subclasses
Extends
Ok, we are not having a good day today.
When you attach the correct tzinfo object to a datetime instance, and then you strftime() it, it STILL comes out in UTC, seemingly ignoring the beautiful tzinfo object I attached to it.
# python 2.5.4
now = datetime.now()
print now.strftime( "%a %b %d %X" ) # %X is "locale's appropriate time rep"
pst = now.replace( tzinfo=Pacific )
print pst.strftime( "%a %b %d %X" )
We get:
Mon Jan 18 17:30:16
Mon Jan 18 17:30:16
I found if I add %z, I can add the difference its supposed to have computed:
Mon Jan 18 17:32:38
Mon Jan 18 17:32:38 -0800
It just tacks on the -8 there, as if to say, "you do it yourself, foo."
But I want strftime() to simply give me a string WITH PRECOMPUTED LOCAL TIME.
How can I get strftime() to do the hour subtraction math for me when I strftime() it?
The full code I'm using is below.
from datetime import tzinfo, timedelta, datetime
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
# A UTC class.
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
# A class building tzinfo objects for fixed-offset time zones.
# Note that FixedOffset(0, "UTC") is a different way to build a
# UTC tzinfo object.
class FixedOffset(tzinfo):
"""Fixed offset in minutes east from UTC."""
def __init__(self, offset, name):
self.__offset = timedelta(minutes = offset)
self.__name = name
def utcoffset(self, dt):
return self.__offset
def tzname(self, dt):
return self.__name
def dst(self, dt):
return ZERO
# A class capturing the platform's idea of local time.
import time as _time
STDOFFSET = timedelta(seconds = -_time.timezone)
if _time.daylight:
DSTOFFSET = timedelta(seconds = -_time.altzone)
else:
DSTOFFSET = STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET
class LocalTimezone(tzinfo):
def utcoffset(self, dt):
if self._isdst(dt):
return DSTOFFSET
else:
return STDOFFSET
def dst(self, dt):
if self._isdst(dt):
return DSTDIFF
else:
return ZERO
def tzname(self, dt):
return _time.tzname[self._isdst(dt)]
def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.weekday(), 0, -1)
stamp = _time.mktime(tt)
tt = _time.localtime(stamp)
return tt.tm_isdst > 0
Local = LocalTimezone()
# A complete implementation of current DST rules for major US time zones.
def first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
DSTSTART = datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
# which is the first Sunday on or after Oct 25.
DSTEND = datetime(1, 10, 25, 1)
class USTimeZone(tzinfo):
def __init__(self, hours, reprname, stdname, dstname):
self.stdoffset = timedelta(hours=hours)
self.reprname = reprname
self.stdname = stdname
self.dstname = dstname
def __repr__(self):
return self.reprname
def tzname(self, dt):
if self.dst(dt):
return self.dstname
else:
return self.stdname
def utcoffset(self, dt):
return self.stdoffset + self.dst(dt)
def dst(self, dt):
if dt is None or dt.tzinfo is None:
# An exception may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
# Find first Sunday in April & the last in October.
start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
# Can't compare naive to aware objects, so strip the timezone from
# dt first.
if start <= dt.replace(tzinfo=None) < end:
return HOUR
else:
return ZERO
Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
#Central = USTimeZone(-6, "Central", "CST", "CDT")
#Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
now = datetime.now()
print now.strftime( "%a %b %d %X %z" )
pst = now.replace( tzinfo=Pacific )
print pst.strftime( "%a %b %d %X %z" )
.replace does no computation: it simply replaces one or more field in the new returned object, while copying all others from the object it's called on.
If I understand your situation correctly, you start with a datetime object which you know (through other means) is UTC, but doesn't know that itself (is has a tzinfo attribute of None, meaning "I'm totally clueless regarding what timezone I'm in).
So, first, you make a timezone-aware from your input timezone-naive object, in order to inform it that it's in timezone UTC (all other fields just get copied over):
aware = naive.replace(tzinfo=utc)
Then, you can request computations regarding timezones, and printing in consequence:
print aware.astimezone(Pacific).strftime('%a %b %d %X %z')
With dt.replace(tzinfo=tz) you're not really converting the time value, you're just saying 'hey no, wait, this time was actually in PDT, not in UTC'. You'll probably want to use datetime.astimezone(tz) instead.
I think Wim had the right idea, just backwards. If you want to know what your time would be in UTC, use:
print pst.astimezone(UTC).strftime( "%a %b %d %X" )
You'll have to dig up a definition for a UTC timezone class. I understand why Python didn't want to supply a default implementation of every possible tzinfo, but UTC should have been included in the base package.