pytz and Etc/GMT-5 - python

I'm having trouble understanding the conversion between the "Etc/GMT-5" timezone and UTC in pytz.
>>> dt = datetime(2009, 9, 9, 10, 0) # September 9 2009, 10:00
>>> gmt_5 = pytz.timezone("Etc/GMT-5")
>>> gmt_5.localize(dt)
datetime.datetime(2009, 9, 9, 10, 0, tzinfo=<StaticTzInfo 'Etc/GMT-5'>)
Everything is fine so far, but then I try to convert that to UTC:
>>> gmt_5.localize(dt).astimezone(pytz.utc)
datetime.datetime(2009, 9, 9, 5, 0, tzinfo=<UTC>)
So to me it seems that when converting from 10:00 in GMT-5 to UTC I get 05:00? I would expect pytz to give me 15:00 instead.
What am I missing?
EDIT: I have confirmed that timezone conversion for the US/Eastern timezone works just as I'd expect:
>>> eastern = pytz.timezone("US/Eastern")
>>> eastern.localize(dt)
datetime.datetime(2009, 9, 9, 10, 0, tzinfo=...) # Too long
>>> pytz.utc.normalize(eastern.localize(dt).astimezone(pytz.utc))
datetime.datetime(2009, 9, 9, 14, 0, tzinfo=<UTC>)
EDIT 2: I have confirmed that when I use Etc/GMT+5 I get 15:00, which is what I'd expect to get from Etc/GMT-5. Is this a pytz bug?

This is apparently a POSIX thing. From Wikipedia:
In order to conform with the POSIX style, those zones beginning with "Etc/GMT" have their sign reversed from what most people expect. In this style, zones west of GMT have a positive sign and those east have a negative sign.

This bug report explains this behavior. Apparently they know that it is all inverted, but that's because anything else would break compatibility.

Related

python datetime .timestamp() doesnt give accurate timestamp. It gets behind by 7 minutes when I convert it back

I am using python 3.6
I converted midnight of "US/Pacific" timezone to unix timestamp.
When I convert the timestamp back to "US/Pacific" local time, I see its 7 minutes before the midnight.
Can anyone shade light on what is happening?
>>> (
datetime(2021, 5, 5, 0, 0)
.replace(tzinfo=pytz.timezone("US/Pacific"))
.timestamp()
)
>>> 1620201180.0
>>> datetime.utcfromtimestamp(1620201180.0)
>>> datetime.datetime(2021, 5, 5, 7, 53)
>>> datetime.fromtimestamp(1620201180.0, tz=pytz.timezone("US/Pacific"))
>>> datetime.datetime(2021, 5, 5, 0, 53, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
FYI I am located at "Asia/Kathmandu" timezone.

Why is only one date under DST influence?

I've been reading the pytz and datetime module documentation but I can't figure out why one date is under DST and the other is not.
import pytz
import datetime
mytz = pytz.timezone('America/New_York')
od = datetime.datetime(2021, 7, 1, 4, 0)
mytz.localize(od)
# Out: datetime.datetime(2021, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
mytz.localize(od).dst()
# Out: datetime.timedelta(0, 3600)
dt = datetime.datetime(2089, 7, 1, 4, 0)
mytz.localize(dt)
# Out: datetime.datetime(2089, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
mytz.localize(dt).dst()
# Out: datetime.timedelta(0)
If you look at the source of the time zone rules, you find that they can have a keyword "max" specified that "is used to extend a rule’s application into the indefinite future" ref. For the US, you can find that here. Unless otherwise specified, DST just continues to be applied during the specified period of the year. But keep in mind that this does not mean that it will actually be the case in the future, since time zones are subject to political decisions.
As an addition to #balmy 's comment suggesting this is a deficiency of pytz, Python 3.9's zoneinfo gives the result to be expected from the above:
import datetime
from zoneinfo import ZoneInfo
od = datetime.datetime(2021, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(od.dst())
# 1:00:00
dt = datetime.datetime(2089, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(dt.dst())
# 1:00:00

Python astimezone() unexpected result

Given a variable containing the datetime of 2000-01-01 00:01 in Paris timezone (UTC+2 in winter afaik):
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
I expected the conversion to UTC to result in a datetime of 1999-12-31 22:01, but got instead:
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris')).astimezone(pytz.utc)
datetime.datetime(1999, 12, 31, 23, 52, tzinfo=<UTC>)
What am I missing ?
Thanks
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'
As you'll notice:
>>> datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=<DstTzInfo 'Europe/Paris' LMT+0:09:00 STD>)
"LMT+0:09:00 STD"…?! That's a historical offset, not a current standard.
The timezone bundles (containing all historical offsets since forever) returned by pytz aren't handled correctly by datetime, and it chooses some random (well, the first probably) offset instead of the offset pertinent to the actual time. Arguably, since it needs to interpret the time correctly first it cannot choose the right offset by time from the timezone bundle.
This library only supports two ways of building a localized time. The
first is to use the localize() method provided by the pytz library.
This is used to localize a naive datetime (datetime with no timezone
information):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
The second way of building a localized time is by converting an existing
localized time using the standard astimezone() method:
>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET+0100'
http://pytz.sourceforge.net

Saving a local datetime offset the time by 4 minutes

I'm trying to modify a datetime based on a timezone on save and on load the following way:
An input datetime, along with a input timezone are sent to the server and the server should update the datetime to reflect the timezone. So when it saves in the database (PostregSQL), the UTC time is saved (after the offset caused by the timezone, of course).
To reflect this here's a simpler example that fails in the same way:
Some imports:
>>> import datetime
>>> import pytz
>>> from apps.myapp.models import Project
Creating the two inputs:
>>> input_date = timezone.now()
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz = pytz.timezone('America/New_York')
>>> current_tz
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
As you can see, the timezone is not 5h (24 - 19 = 5), but 4h56. At this stage I'm thinking that's OK, it may be related to the Daylight Saving Time.
Now I'm replacing the timezone on the input date:
>>> input_date = input_date.replace(tzinfo=current_tz)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
As expected, the time hasn't changed, but the timezone has, which is fine.
I'll assign this value to a project (the launch_date is a DateTimeField without any specific option):
>>> project = Project.objects.get(pk=1)
>>> project.launch_date
datetime.datetime(2017, 1, 14, 8, 53, 57, 241718, tzinfo=<UTC>)
>>> project.launch_date = input_date
>>> project.launch_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
Now I'll save this into (and refresh from) the database, leaving Django/PostgreSQL do the maths:
>>> project.save()
>>> project.refresh_from_db()
>>> project.launch_date
datetime.datetime(2017, 2, 7, 21, 3, 14, 377429, tzinfo=<UTC>)
As expected the date is now 4h56 ahead of the previous date. I'm trying now to get back the local time:
>>> project.launch_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 16, 3, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
This time, the offset is perfectly 5h. And I'm missing 4 minutes.
3 questions here:
Where is this 4 minutes coming from?
Why is astimezone not using the 4 minutes as well?
How can a datetime be converted to UTC, saved, loaded and converted back to local?
pytz time zones are a little weird, as you can see by multiple questions on StackOverflow. They often won't show the correct offset or timezone name unless they are allowed to adjust themselves to the datetime they are being paired with. Here is what the documentation has to say:
This library only supports two ways of building a localized time. The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):
The second way of building a localized time is by converting an existing localized time using the standard astimezone() method:
Unfortunately using the tzinfo argument of the standard datetime constructors “does not work” with pytz for many timezones.
It does not say so explicitly, but using replace has the same problem as using the datetime constructor.
To accomplish what your code was doing without the 4-minute discrepancy, you can use localize():
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz.localize(input_date.replace(tzinfo=None))
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
I suspect that's a bug though, and you really want to do a timezone conversion from UTC:
>>> input_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 11, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)

inconsistency when switching between timezones in python

I have a datetime object created like this:
tm = datetime.datetime.strptime('2010 Aug 04 14:15:16', '%Y %b %d %H:%M:%S')
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16)
I then set the timezone like this:
tm.replace(tzinfo=pytz.timezone('UTC'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<UTC>)
Eventually, I change the timezone to US/Pacific like this:
>>> tm = tm.astimezone(pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 7, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
This is correct. But if I were to go into the other direction:
>>> tm = tm.replace(tzinfo =pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>)
As you can see, using replace() with the PST timezone incorrectly assumes it is not daylight savings time. Therefore, when I convert to UTC, it adds 8 hours instead of 7.
Why is it doing this and how do I change it so it does what I want (correctly realize DST or not)?
Yes, this is why you use .astimezone and not .replace when you have a datetime with a timezone. Using .astimezone gives the timezone a chance to adjust for things like daylight savings. Only use .replace to give a naïve datetime a tzinfo object.

Categories

Resources