Datetime Timezone Conversion Using Pytz

pytz - Converting UTC and timezone to local time

I think I got it:

pytz.utc.localize(utc_time, is_dst=None).astimezone(tz)

This line first converts the naive (time zone unaware) utc_time datetime object to a datetime object that contains a timezone (UTC). Then it uses the astimezone function to adjust the time according to the requested time zone.

Converting datetime from one time zone to another using pytz

You can convert from New York time to Frankfurt time by using localize() to create your naive datetime objects in New York time and astimezone() to then convert them to Frankfurt (Berlin) time. Using the timezone name (rather than a specific timezone abbreviation that only applies during part of the year) will handle the daylight savings difference for you.

For example:

from datetime import datetime
from pytz import timezone

newyork_tz = timezone('America/New_York')
berlin_tz = timezone('Europe/Berlin')

newyork = newyork_tz.localize(datetime(2018, 5, 1, 8, 0, 0))
berlin = newyork.astimezone(berlin_tz)
print(newyork)
print(berlin)
# OUTPUT
# 2018-05-01 08:00:00-04:00
# 2018-05-01 14:00:00+02:00

Datetime Timezone conversion using pytz

I assume you have these questions:

  • why does the first function work for UTC timezone?
  • why does it fail for 'US/Eastern' timezone (DstTzInfo instance)?
  • why does the second function work for all provided examples?

The first function is incorrect because it uses d.replace(tzinfo=dsttzinfo_instance) instead of dsttzinfo_instance.localize(d) .

The second function is correct most of the time except during ambiguous or non-existing times e.g., during DST transitions -- you can change the behaviour by passing is_dst parameter to .localize(): False(default)/True/None(raise an exception).

The first function works for UTC timezone because it has a fixed utc offset (zero) for any date. Other timezones such as America/New_York may have different utc offsets at different times (Daylight saving time, war time, any time that some local politician might think is a good idea -- it can be anything -- the tz database works in most cases). To implement tzinfo.utcoffset(dt), tzinfo.tzname(dt), tzinfo.dst(dt) methods pytz uses a collection of DstTzInfo instances each with a different set of (_tzname, _utcoffset, _dst) attributes. Given dt (date/time) and is_dst, .localize() method chooses an appropriate (in most cases but not always) DstTzInfo instance from the collection. pytz.timezone('America/New_York') returns a DstTzInfo instance with (_tzname, _utcoffset, _dst) attributes that correspond to some undocumented moment in time (different pytz versions may return different values -- the current version may return tzinfo instance that corresponds to the earliest date for which zoneinfo is available -- you don't want this value most of the time: I think the motivation behind the choice of the default value is to highlight the error (passing pytz.timezone to datetime constructor or .replace() method).

To summarize: .localize() selects appropriate utcoffset, tzname, dst values, .replace() uses the default (inappropriate) value. UTC has only one set of utcoffset, tzname, dst therefore the default value may be used and .replace() method works with UTC timezone. You need to pass a datetime object and is_dst parameter to select appropriate values for other timezones such as 'America/New_York'.

In principle, pytz could have called localize() method to implement utcoffset(), tzname(), dst() methods even if dt.tzinfo == self: it would make these methods O(log n) in time where n is number of intervals with different (utcoffset, tzname, dst) values but datetime constructor and .replace() would work as is i.e., the explicit localize() call would be necessary only to pass is_dst.

Python (datetime) timezone conversion off by 4 minutes

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.

datetime and timezone conversion with pytz - mind blowing behaviour

The documentation http://pytz.sourceforge.net/ states "Unfortunately using the tzinfo argument of the standard datetime constructors 'does not work' with pytz for many timezones." The code:

t = datetime(
2013, 5, 11, hour=11, minute=0,
tzinfo=pytz.timezone('Europe/Warsaw')
)

doesn't work according to this, instead you should use the localize method:

t = pytz.timezone('Europe/Warsaw').localize(
datetime(2013, 5, 11, hour=11, minute=0))

How to convert a UTC datetime to a local datetime using only standard library?

I think I figured it out: computes number of seconds since epoch, then converts to a local timzeone using time.localtime, and then converts the time struct back into a datetime...

EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60

def utc_to_local_datetime( utc_datetime ):
delta = utc_datetime - EPOCH_DATETIME
utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
time_struct = time.localtime( utc_epoch )
dt_args = time_struct[:6] + (delta.microseconds,)
return datetime.datetime( *dt_args )

It applies the summer/winter DST correctly:

>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)

Python pytz timezone conversion returns values that differ from timezone offset for different dates

Assuming you are on Linux or a BSD, if you run 'zdump -v US/Eastern' you will see the complete list of transitions. It wasn't until 1918 that the US switched to whole hour offsets, probably because of the needs of railroads or telegraph.

US/Eastern  Sun Nov 18 16:59:59 1883 UT = Sun Nov 18 12:03:57 1883 LMT isdst=0 gmtoff=-17762
US/Eastern Sun Nov 18 17:00:00 1883 UT = Sun Nov 18 12:00:00 1883 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 31 06:59:59 1918 UT = Sun Mar 31 01:59:59 1918 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 31 07:00:00 1918 UT = Sun Mar 31 03:00:00 1918 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 27 05:59:59 1918 UT = Sun Oct 27 01:59:59 1918 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 27 06:00:00 1918 UT = Sun Oct 27 01:00:00 1918 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 30 06:59:59 1919 UT = Sun Mar 30 01:59:59 1919 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 30 07:00:00 1919 UT = Sun Mar 30 03:00:00 1919 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 26 05:59:59 1919 UT = Sun Oct 26 01:59:59 1919 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 26 06:00:00 1919 UT = Sun Oct 26 01:00:00 1919 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 28 06:59:59 1920 UT = Sun Mar 28 01:59:59 1920 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 28 07:00:00 1920 UT = Sun Mar 28 03:00:00 1920 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 31 05:59:59 1920 UT = Sun Oct 31 01:59:59 1920 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 31 06:00:00 1920 UT = Sun Oct 31 01:00:00 1920 EST isdst=0 gmtoff=-18000

[...]

US/Eastern Sun Mar 8 06:59:59 2015 UT = Sun Mar 8 01:59:59 2015 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 8 07:00:00 2015 UT = Sun Mar 8 03:00:00 2015 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 05:59:59 2015 UT = Sun Nov 1 01:59:59 2015 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 06:00:00 2015 UT = Sun Nov 1 01:00:00 2015 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 13 06:59:59 2016 UT = Sun Mar 13 01:59:59 2016 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 13 07:00:00 2016 UT = Sun Mar 13 03:00:00 2016 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 6 05:59:59 2016 UT = Sun Nov 6 01:59:59 2016 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 6 06:00:00 2016 UT = Sun Nov 6 01:00:00 2016 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 12 06:59:59 2017 UT = Sun Mar 12 01:59:59 2017 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 12 07:00:00 2017 UT = Sun Mar 12 03:00:00 2017 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 5 05:59:59 2017 UT = Sun Nov 5 01:59:59 2017 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 5 06:00:00 2017 UT = Sun Nov 5 01:00:00 2017 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 11 06:59:59 2018 UT = Sun Mar 11 01:59:59 2018 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 11 07:00:00 2018 UT = Sun Mar 11 03:00:00 2018 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 4 05:59:59 2018 UT = Sun Nov 4 01:59:59 2018 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 4 06:00:00 2018 UT = Sun Nov 4 01:00:00 2018 EST isdst=0 gmtoff=-18000

[...]

US/Eastern Sun Mar 9 06:59:59 2498 UT = Sun Mar 9 01:59:59 2498 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 9 07:00:00 2498 UT = Sun Mar 9 03:00:00 2498 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 2 05:59:59 2498 UT = Sun Nov 2 01:59:59 2498 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 2 06:00:00 2498 UT = Sun Nov 2 01:00:00 2498 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 8 06:59:59 2499 UT = Sun Mar 8 01:59:59 2499 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 8 07:00:00 2499 UT = Sun Mar 8 03:00:00 2499 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 05:59:59 2499 UT = Sun Nov 1 01:59:59 2499 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 06:00:00 2499 UT = Sun Nov 1 01:00:00 2499 EST isdst=0 gmtoff=-18000


Related Topics



Leave a reply



Submit