58

I am try creating a datetime object in python using datetime and pytz, the offset shown is wrong.

import datetime
from pytz import timezone

start = datetime.datetime(2011, 6, 20, 0, 0, 0, 0, timezone('Asia/Kolkata'))
print start

The output shown is

datetime.datetime(2011, 6, 20, 0, 0, tzinfo=<DstTzInfo 'Asia/Kolkata' HMT+5:53:00 STD>)

Note that 'Asia/Kolkata' is IST which is GMT+5:30 and not HMT+5:53. This is a standard linux timezone, why do I get this wrong, and how do I solve it?

FObersteiner
  • 22,500
  • 8
  • 42
  • 72
compbugs
  • 775
  • 1
  • 6
  • 10
  • [pytz bug report tool](https://bugs.launchpad.net/pytz) – kojiro Jun 20 '11 at 12:21
  • @kojiro: It's not a bug. See http://pytz.sourceforge.net/ – Ferdinand Beyer Jun 20 '11 at 12:39
  • @FerdinandBeyer it wasn't a bug when you wrote your comment, but it is now. The `datetime` interface can work properly now, as evidenced by the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html) introduced in Python 3.9 - it doesn't require a `localize` function to work properly. – Mark Ransom Nov 18 '21 at 17:16
  • see also: [Weird timezone issue with pytz](https://stackoverflow.com/q/11473721/10197418) – FObersteiner Oct 12 '22 at 08:19

2 Answers2

93

See: http://bytes.com/topic/python/answers/676275-pytz-giving-incorrect-offset-timezone

In the comments, someone proposes to use tzinfo.localize() instead of the datetime constructor, which does the trick.

>>> tz = timezone('Asia/Kolkata')
>>> dt = tz.localize(datetime.datetime(2011, 6, 20, 0, 0, 0, 0))
>>> dt
datetime.datetime(2011, 6, 20, 0, 0, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>)

UPDATE: Actually, the official pytz website states that you should always use localize or astimezone instead of passing a timezone object to datetime.datetime.

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • The explanation in the bytes link is superb, thank you for this! – Maxim Mar 27 '17 at 10:06
  • 4
    This answer could be improved by adding a small reasoning why `pytz` as a default uses the extremely uncommon timezones with minutes offset and doesn't use them when using the method `.localize()`. This is weird as hell. – Alfe Apr 29 '20 at 23:16
  • @Alfe I don't think anybody knows why pytz uses the defaults it does. My guess is that the default is the oldest entry in its table, rather than the newest which would be more likely to be correct. That may be deliberate to make it obvious when you didn't use `localize`. The reason localize can work correctly is because it is passed the datetime so it can use it to determine the correct historical context and make adjustments. The `zoneinfo` module added in Python 3.9 doesn't need `localize`, I think because `datetime` was changed to support it better. – Mark Ransom Nov 18 '21 at 16:56
7

This has been fixed in python >=3.9 by the zoneinfo module in the standard library. The solution in >= 3.9 is probably to stop using pytz.

In [1]: import datetime

In [2]: from zoneinfo import ZoneInfo

In [3]: start = datetime.datetime(2011, 6, 20, 0, 0, 0, 0, ZoneInfo('Asia/Kolkata'))

In [4]: print(start)
2011-06-20 00:00:00+05:30

The reason for this extremely confusing behavior is that time zones used to not be standardized at :30 or :00 offsets. Around the turn of the 20th century most of them came into a standard offset. In the example in OP, the timezone switched in 1906. For US/Central, this happened in 1901.

from datetime import datetime, timedelta, date
from pytz import timezone

d = datetime.combine(date.today(), time.min)
for tz in ('Asia/Kolkata', "US/Central"):
    while d > datetime(1800, 1, 1):
        localized = timezone(tz).localize(d)
        if localized.isoformat()[-2:] not in ("00", "30"):
            print(tz)
            print(localized.isoformat())
            print(timezone(tz).localize(d + timedelta(days=1)).isoformat())
            break
        d -= timedelta(days=1)

That outputs:

Asia/Kolkata
1906-01-01T00:00:00+05:21
1906-01-02T00:00:00+05:30
US/Central
1901-12-13T00:00:00-05:51
1901-12-14T00:00:00-06:00

Pytz seems to just use the oldest offset when it doesn't have date information, even if it was a very long time ago. In some very natural constructions like passing tzinfo to the datetime constructor, the timezone object is not given that data.

Lucas Wiman
  • 10,021
  • 2
  • 37
  • 41