It's a bit weird it seems that when I want to get a timezone for Europe/Paris with pytz it gets me to the PMT timezone instead of GMT+1 when it seems to work for Europe/Berlin.
Not clear ? Well look at this snippet :
#!/usr/bin/python
import os
import datetime
from pytz.tzfile import build_tzinfo
base='/usr/share/zoneinfo/'
tz = build_tzinfo('Europe/Paris',
open(os.path.join(base,'Europe','Paris'), 'rb'))
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
print datetime.datetime(2009, 01, 30, 9, 00, tzinfo=tz).strftime(fmt)
tz = build_tzinfo('Europe/Berlin',
open(os.path.join(base,'Europe','Berlin'), 'rb'))
print datetime.datetime(2009, 01, 30, 9, 00, tzinfo=tz).strftime(fmt)
the output is :
2009-01-30 09:00:00 PMT+0009
2009-01-30 09:00:00 CET+0100
when really paris should be as well CET+1.
Constructing from datetime.datetime.now(tz) would get the thing right no matter what.
Anybody have an idea ?
The docs say you can't use datetime.datetime(..., tzinfo) like you're doing:
Unfortunately using the tzinfo argument of the standard datetime constructors does not work with pytz for many timezones.
And curiously, despite all signs that the Europe/Paris timezone is wrong, when you actually use with localize as it recommends, it works nonetheless:
>>> tz= pytz.timezone('Europe/Paris') # using built-in zoneinfo
>>> tz
<DstTzInfo 'Europe/Paris' PMT+0:09:00 STD> # what? Pierre et Miquelon Time?
>>> datetime.datetime(2010,1,1,12,0,0, tzinfo=tz)
datetime.datetime(2010, 1, 1, 12, 0, tzinfo=<DstTzInfo 'Europe/Paris' PMT+0:09:00 STD>) # bad
>>> tz.localize(datetime.datetime(2010,1,1,12,0,0))
datetime.datetime(2010, 1, 1, 12, 0, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>) # OK
Note that the tzinfo property of the localized datetime references a completely different object to the tz it was created from, sharing only the name.
It's a mystery to me why this is happening. It seems to be a problem with city files that understand multiple timezone names, but why you don't get the default timezone for a city until you call localize, I've no idea.
(To be honest I've never trusted Python's datetime and tzinfo stuff. Much prefer working with int UTC timestamps.)
Related
This question already has answers here:
How to add timezone into a naive datetime instance in python [duplicate]
(2 answers)
Closed 7 years ago.
I ran across an interesting situation today. Can anyone explain why the offsets for ts1 and ts2 are different? ts1 is a datetime object that is timezone-aware right off the bat. ts2 is a datetime object that starts off timezone-naive and has its tzinfo replaced. However, they end up with different offsets.
>>> from pytz import timezone
>>> EST = timezone('America/New_York')
>>> ts1 = datetime.datetime.now(tz=EST)
>>> ts2 = datetime.datetime.now()
>>> ts2 = ts2.replace(tzinfo=EST)
>>> print ts1
2014-05-16 11:25:16.749748-04:00
>>> print ts2
2014-05-16 11:25:19.581710-05:00
When you call ts2.replace(tzinfo=EST), the tzinfo object you're getting doesn't match the one you get with ts1:
>>> ts1
datetime.datetime(2014, 5, 16, 11, 51, 7, 916090, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> ts2
datetime.datetime(2014, 5, 16, 11, 51, 30, 922692, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
You end up with LMT instead of EDT.
The pytz documentation actually notes that using pytz with the tzinfo argument of standard datetime objects simply doesn't work for many timezones:
Unfortunately using the tzinfo argument of the standard datetime
constructors ''does not work'' with pytz for many timezones.
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt) '2002-10-27 12:00:00 LMT+0020'
It is safe for timezones without daylight saving transitions though,
such as UTC:
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt) '2002-10-27 12:00:00 UTC+0000'
I'm not exactly sure why the first one works; perhaps because it doesn't actually have to convert anything when the object is initially constructed with the tzinfo object.
Edit:
Ah, the Python documentation notes that using datetime.datetime.now() with the tz arg is equivalent to:
EST.fromutc(datetime.utcnow().replace(tzinfo=EST))
Which means you're converting from UTC, which is safe with pytz. So that's why the first one works.
According to the documentation, the correct way to apply a time zone to a naive datetime is with the localize method.
ts1 = eastern.localize(datetime.datetime.now())
Also, I recommend you use avoid EST as a variable name, since it typically standard for "Eastern Standard Time", and America/New_York comprises both "Eastern Standard Time" (EST) and "Eastern Daylight Time" (EDT).
I'm pulling data from a London based service and they are giving me date&time info in London local time.So UTC in winter and BST(UTC+1) in summer.
Internally we use UTC for everything, in Python how do I convert the London stuff to UTC in a way that will account for daylight savings?
I appreciate that some times around the DST rollover are ambiguous, that's acceptable as long as it works the rest of the year.
For completeness, I'm getting the following info from them:
dt="2012-10-12T19:30:00"
lcnid="LDN"
locale="en-gb"
You need to use a timezone object; these don't come with Python itself as the data changes too often. The pytz library is easily installed though.
Example conversion:
>>> import pytz
>>> import datetime
>>> bst = pytz.timezone('Europe/London')
>>> dt = datetime.datetime.strptime('2012-10-12T19:30:00', '%Y-%m-%dT%H:%M:%S')
>>> dt
datetime.datetime(2012, 10, 12, 19, 30)
>>> bst.localize(dt)
datetime.datetime(2012, 10, 12, 19, 30, tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>)
>>> bst.localize(dt).astimezone(pytz.utc)
datetime.datetime(2012, 10, 12, 18, 30, tzinfo=<UTC>)
import pytz
utc = pytz.utc
print(utc.localize(datetime.datetime(2012,10,12,19,30,00)))
pytz asks you to use the .astimezone method for all time conversion to and from UTC. However, in one special case — datetime.fromtimestamp — it looks like you should be able to use the Python library's datetime methods.
It seems to work here:
>>> import datetime
>>> import pytz
>>> ambigtime = 1352017800 # http://www.wolframalpha.com/input/?i=1352017800+unix+time+in+Los+Angeles
>>> amla = pytz.timezone('America/Los_Angeles')
>>> datetime.datetime.fromtimestamp(ambigtime, tz=amla)
datetime.datetime(2012, 11, 4, 1, 30, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
>>> datetime.datetime.fromtimestamp(ambigtime + 3600, tz=amla)
datetime.datetime(2012, 11, 4, 1, 30, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
Are there situations where datetime.fromtimestamp won't give you the correct results with pytz timezones?
As far as I know, pytz.timezone() will give you an instance of tzinfo (or rather a subclass thereof), and as such is totally fine to use with datetime.fromtimestamp().
As long as pytz has updated zoneinfo files, you can create localized datetimes using that method. Converting a datetime between two zones however is really loads easier to do with the astimezone() method. If I am correct, it basically switches the tzinfo property on the datetime.
I am writing a program which deals a lot with timezones and crossing them. The two things I deal with most are creating a datetime object from "now" and then localizing a naive datetime object.
To create a datetime object from now in the pacific timezone, I am currently doing this (python 2.7.2+)
from datetime import datetime
import pytz
la = pytz.timezone("America/Los_Angeles")
now = datetime.now(la)
Is this correct with regards to DST? If not, I suppose I should be doing:
now2 = la.localize(datetime.now())
My question is why? Can anyone show me a case where the first is wrong and the seconds is right?
As for my seconds question, suppose I had a naive date and time from some user input for 9/1/2012 at 8:00am in Los Angeles, CA. Is the right way to make the datetime like this:
la.localize(datetime(2012, 9, 1, 8, 0))
If not, how should I be building these datetimes?
From the pytz documentation:
The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans.
So ideally you should be using utcnow instead of now.
Assuming for some reason that your hands are tied and you need to work with local times, you can still run into a problem with trying to localize the current time if you're doing it during the daylight saving transition window. The same datetime might occur twice, once during daylight time and again during standard time, and the localize method doesn't know how to settle the conflict unless you tell it explicitly with the is_dst parameter.
So to get the current UTC time:
utc = pytz.timezone('UTC')
now = utc.localize(datetime.datetime.utcnow())
And to convert it to your local time (but only when you must):
la = pytz.timezone('America/Los_Angeles')
local_time = now.astimezone(la)
Edit: as pointed out in the comments by #J.F. Sebastian, your first example using datetime.now(tz) will work in all cases. Your second example fails during the fall transition as I outlined above. I still advocate using UTC instead of local time for everything except display.
The first solution is correct with regards to DST, and the second solution is bad.
I'll give an example. Here in Europe, when running this code:
from datetime import datetime
import pytz # $ pip install pytz
la = pytz.timezone("America/Los_Angeles")
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
now = datetime.now(la)
now2 = la.localize(datetime.now())
now3 = datetime.now()
print(now.strftime(fmt))
print(now2.strftime(fmt))
print(now3.strftime(fmt))
I get the following:
2012-08-30 12:34:06 PDT-0700
2012-08-30 21:34:06 PDT-0700
2012-08-30 21:34:06
datetime.now(la) creates a datetime with the current time in LA, plus the timezone information for LA.
la.localize(datetime.now()) adds timezone information to the naive datetime, but does no timezone conversion; it just assumes the time was already in this timezone.
datetime.now() creates a naive datetime (without timezone information) with the local time.
As long as you are in LA, you will not see the difference, but if your code ever runs somewhere else, it will probably not do what you wanted.
Apart from that, if you ever need to deal seriously with timezones, it is better to have all your times in UTC, saving yourself a lot of trouble with DST.
This works:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# Convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# show as in LA time zone (not converting here)
d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we get Pacific Daylight Time: PDT
# add 1 day to UTC date
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 16, 43, 45, tzinfo=<UTC>)
d.astimezone(pst) # now cast to LA time zone
>>> datetime.datetime(2016, 11, 6, 8, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
# Daylight saving is applied -> we get Pacific Standard Time PST
This DOES NOT work:
# naive datetime
d = datetime.datetime(2016, 11, 5, 16, 43, 45)
utc = pytz.UTC # UTC timezone
pst = pytz.timezone('America/Los_Angeles') # LA timezone
# convert to UTC timezone aware datetime
d = utc.localize(d)
>>> datetime.datetime(2016, 11, 5, 16, 43, 45, tzinfo=<UTC>)
# convert to 'America/Los_Angeles' timezone: DON'T DO THIS
d = d.astimezone(pst)
>>> datetime.datetime(2016, 11, 5, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# we are in Pacific Daylight Time PDT
# add 1 day to LA local date: DON'T DO THAT
d = d + datetime.timedelta(days=1)
>>> datetime.datetime(2016, 11, 6, 9, 43, 45,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
# Daylight Saving is NOT respected, we are still in PDT time, not PST
Conclusion:
datetime.timedelta() DOES NOT account for daylight saving.
Do your time add/subtract in UTC timezone ALWAYS.
Cast to local time only for output / display.
The pytz website says:
Unfortunately using the tzinfo argument of the standard datetime
constructors ‘’does not work’’ with pytz for many timezones.
So you should not use datetime.now(la). I don't know the specifics, but some timezones operate on more exotic rules then we are used to, and python's datetime code can't handle them. By using pytz's code, they should be handled correctly since that's pytz's purpose. It may also have issues for the times that occur twice thanks to jumping times in daylight savings.
As for the second question, that's exactly what the documentation shows, so you should be good.
I'm having some weird issues with pytz's .localize() function. Sometimes it wouldn't make adjustments to the localized datetime:
.localize behaviour:
>>> tz
<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD>
>>> d
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421)
>>> tz.localize(d)
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421,
tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
>>> tz.normalize(tz.localize(d))
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421,
tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
As you can see, time has not been changed as a result of localize/normalize operations.
However, if .replace is used:
>>> d.replace(tzinfo=tz)
datetime.datetime(2009, 9, 2, 14, 45, 42, 91421,
tzinfo=<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD>)
>>> tz.normalize(d.replace(tzinfo=tz))
datetime.datetime(2009, 9, 2, 15, 1, 42, 91421,
tzinfo=<DstTzInfo 'Africa/Abidjan' GMT0:00:00 STD>)
Which seems to make adjustments into datetime.
Question is - which is correct and why other's wrong?
localize just assumes that the naive datetime you pass it is "right" (except for not knowing about the timezone!) and so just sets the timezone, no other adjustments.
You can (and it's advisable...) internally work in UTC (rather than with naive datetimes) and use replace when you need to perform I/O of datetimes in a localized way (normalize will handle DST and the like).
localize is the correct function to use for creating datetime aware objects with an initial fixed datetime value. The resulting datetime aware object will have the original datetime value. A very common usage pattern in my view, and one that perhaps pytz can better document.
replace(tzinfo = ...) is unfortunately named. It is a function that is random in its behaviour. I would advise avoiding the use of this function to set timezones unless you enjoy self-inflicted pain. I have already suffered enough from using this function.
This DstTzInfo class is used for timezones where the offset from UTC changes at certain points in time. For example (as you are probably aware), many locations transition to "daylight savings time" at the beginning of Summer, and then back to "standard time" at the end of Summer. Each DstTzInfo instance only represents one of these timezones, but the "localize" and "normalize" methods help you get the right instance.
For Abidjan, there has only ever been one transition (according to pytz), and that was in 1912:
>>> tz = pytz.timezone('Africa/Abidjan')
>>> tz._utc_transition_times
[datetime.datetime(1, 1, 1, 0, 0), datetime.datetime(1912, 1, 1, 0, 16, 8)]
The tz object we get out of pytz represents the pre-1912 timezone:
>>> tz
<DstTzInfo 'Africa/Abidjan' LMT-1 day, 23:44:00 STD>
Now looking up at your two examples, see that when you call tz.localize(d) you do NOT get this pre-1912 timezone added to your naive datetime object. It assumes that the datetime object you give it represents local time in the correct timezone for that local time, which is the post-1912 timezone.
However in your second example using d.replace(tzinfo=tz), it takes your datetime object to represent the time in the pre-1912 timezone. This is probably not what you meant. Then when you call dt.normalize it converts this to the timezone that is correct for that datetime value, ie the post-1912 timezone.
I realize I'm a little late on this...
but here is what I found to work well.
Work in UTC as Alex stated:
tz = pytz.timezone('Africa/Abidjan')
now = datetime.datetime.utcnow()
Then to localize:
tzoffset = tz.utcoffset(now)
mynow = now+tzoffset
And this method does handle DST perfectly