11

when I run this code:

#!/usr/bin/env python3
from datetime import datetime, timedelta
from dateutil import tz
from pytz import timezone

time = "2020-01-15 10:14:00"
time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S")

print("time1 = " + str(time))

time = time.replace(tzinfo=timezone('America/New_York'))
print("time2 = " + str(time))

time = time.astimezone(tz.gettz('UTC')) # explicity convert to UTC time
print("time3 = " + str(time))

time = datetime.strftime(time, "%Y-%m-%d %H:%M:%S")  # output format
print("done time4 = " + str(time))

I get this output:

time1 = 2020-01-15 10:14:00
time2 = 2020-01-15 10:14:00-04:56
time3 = 2020-01-15 15:10:00+00:00
done time4 = 2020-01-15 15:10:00

I would have expected the final time to be "2020-01-15 15:14:00" anyone have any ideas why it's off by 4 mintutes? I don't understand why the offset in time2 would by "-04:56" instead of "-05:00"

JohnK
  • 6,865
  • 8
  • 49
  • 75
Daniel E
  • 394
  • 3
  • 13
  • 2
    2021: as we have Python 3.9 for a while now, awoid pytz's "localize trap" by using [zoneinfo](https://docs.python.org/3/library/zoneinfo.html), see e.g. https://stackoverflow.com/a/63628816/10197418 – FObersteiner Sep 20 '21 at 10:15

2 Answers2

11

From pytz documentation:

This library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method documented in this document. In addition, if you perform date arithmetic on local times that cross DST boundaries, the result may be in an incorrect timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get 2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT).

So, you are using pytz incorrectly.

Following is both correct, and erroneous code Following code shows results of your use of pytz (datetime.replace(tzinfo=pytz.timezone)), and the recommended way of using pytz with datetime (pytz.timezone.localize(datetime)).

from datetime import datetime, date, time, timezone
from dateutil import tz
import pytz


d = date(2019, 1, 27)
t = time(19, 32, 00)

t1 = datetime.combine(d, t)
t1_epoch = t1.timestamp()
print("t1_epoch " + str(t1_epoch))
print("t1 " + str(t1))


# your approach/code
nytz = pytz.timezone('America/New_York')
t3 = t1.replace(tzinfo=nytz)
t3_epoch = t3.timestamp()
print("t3_epoch " + str(t3_epoch))
print("t3 " + str(t3))

# recommended approach/code using localize
nytz = pytz.timezone('America/New_York')
t6 = nytz.localize(t1)
t6_epoch = t6.timestamp()
print("t6_epoch " + str(t6_epoch))
print("t6 " + str(t6))

Output of above code:

t1_epoch 1548617520.0
t1 2019-01-27 19:32:00
t3_epoch 1548635280.0
t3 2019-01-27 19:32:00-04:56
t6_epoch 1548635520.0
t6 2019-01-27 19:32:00-05:00

t3 is what you are doing, and it is giving incorrect offset (-4:56). Note that POSIX time is also incorrect in this case. POSIX time, by definition, does not change with timezone.

t6 has been created using pytz.timezone.localize() method, and gives correct UTC offset (-5:00).

Update: Updated language of the answer as one user found the answer confusing.

narendra-choudhary
  • 4,582
  • 4
  • 38
  • 58
  • 1
    Although correct, the answer seems rather confusing to me (correct AND erroneous code?). Basically, the answer is: use `localize()` from the `timezone` class of the `pytz` module instead of `replace()` from the `datetime` class. Btw., to check a UTC offset, you could simply use `dt.utcoffset() / timedelta(hours=1)` (assuming `dt` is an aware `datetime` object). – FObersteiner Apr 03 '20 at 08:31
  • @MrFuppes I've updated the "correct, and erroneous code" part to avoid confusion (though there was a serial comma to disambiguate). And yes, we can get offset using the code you've suggested. OP was already using `str(datetime.datetime)` so it made sense to this to display offset. – narendra-choudhary Apr 03 '20 at 11:33
  • @narendra-choudhary you're the best! Much appreciated :) – Daniel E Apr 03 '20 at 15:32
4

I know this is an old thread, but I've got this exact problem today when converting dates from America/Sao_Paulo timezone to UTC forward and backward. But for my case it is off by 6 minutes.

Merging the solution offered by @narendra-choudhary I got this:

import pytz
from datetime import datetime
# Simulating unaware date created by user in front-end
in_date = datetime.now()
z = pytz.timezone("America/Sao_Paulo")
aware_localized_date = z.localize(in_date)
# Now converting to UTC
utc_date = aware_localized_date.astimezone(pytz.UTC)
print(utc_date.strftime("%d/%m/%Y %H:%M:%S"))
print(in_date.strftime("%d/%m/%Y %H:%M:%S"))

The output is:

>>> print(utc_date.strftime("%d/%m/%Y %H:%M:%S"))
11/06/2021 21:56:02
>>> print(in_date.strftime("%d/%m/%Y %H:%M:%S"))
11/06/2021 18:56:02
>>>

My code it's working now thanks to @narendra-choudhary! I hope it helps for those who got confused.

Renato Aloi
  • 300
  • 2
  • 9
  • python datetime timezone conversion off by 6 minutes hoping google spy bots got this – Renato Aloi Jun 12 '21 at 02:49
  • 1
    it is safe and simpler to use `datetime.now(pytz.timezone("America/Sao_Paulo"))` - one of the occasions where you don't need to localize explicitly with pytz's timezone class. – FObersteiner Sep 20 '21 at 10:12