Pytz Localize VS Datetime Replace

pytz localize vs datetime replace

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).

Why python datetime replace timezone is returning different timezone?

As noted in the comments, you appear to have two problems. First is that the datetimepicker is saving a date and time with a UTC timezone offset, when it should be applying a different timezone or leaving off the timezone offset entirely. Second is that pytz is using the wrong offset.

I don't know how to fix the first problem, but I can help you with the second. You need to use pytz's localize function. This only works when the datetime doesn't have a timezone attached, but since you know the timezone is incorrect anyway you can delete it first.

tz = pytz.timezone('America/Chicago')
dt = tz.localize(dt.replace(tzinfo=None))

Is there every a benefit to use localize instead of astimezone in pytz when going from unaware to aware

To clarify my comment,

  • localize sets the time zone for a naive datetime object. It does not change the object's attributes (date/time)
  • astimezone takes a given datetime object, naive or aware, and converts the date/time to the desired time zone. If the datetime object is naive, it is assumed to be local time.

EX:

from datetime import datetime
import pytz

# just localize to a certain time zone.
pytz.timezone("America/Denver").localize(datetime(2022, 5, 15))
Out[3]: datetime.datetime(2022, 5, 15, 0, 0, tzinfo=<DstTzInfo 'America/Denver' MDT-1 day, 18:00:00 DST>)

# convert to the desired time zone; note that my local time zone is UTC+2
# US/Denver is at UTC-6 on May 15th 2022, so total shift is 8 hours:
datetime(2022, 5, 15).astimezone(pytz.timezone("America/Denver"))
Out[4]: datetime.datetime(2022, 5, 14, 16, 0, tzinfo=<DstTzInfo 'America/Denver' MDT-1 day, 18:00:00 DST>)

To answer the question: no, there is no benefit. You're dealing with methods that have different purposes.


As a side note, the Python 3.9+ standard library actually offers a benefit:

from zoneinfo import ZoneInfo

%timeit pytz.timezone("America/Denver").localize(datetime(2022, 5, 15))
16.8 µs ± 30.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit datetime(2022, 5, 15).replace(tzinfo=ZoneInfo("America/Denver"))
1.19 µs ± 5.83 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

datetime.combine with timezone is different from datetime.now with timezone

datetime.now effectively converts (localizes) your datetime with the pytz timezone object - from the docs:

If tz is not None, it must be an instance of a tzinfo subclass, and
the current date and time are converted to tz’s time zone.

datetime.combine does not do that. It is as if you would write something like datetime(2020,1,1, tzinfo=pytz.timezone('US/Eastern')) - effectively not adjusting the time zone to the provided date/time. See also e.g. pytz localize vs datetime replace and pytz: The Fastest Footgun in the West for more background info.

The correct way to get d2 with pytz would be

d2 = EDT.localize(datetime.combine(d1.date(), d1.time()))

No such issues if using timezone objects from dateutil or zoneinfo (Python 3.9+):

from datetime import datetime
from zoneinfo import ZoneInfo

EDT = ZoneInfo('US/Eastern')
d1 = datetime.now(tz=EDT)
d2 = datetime.combine(d1.date(), d1.time(), tzinfo=EDT)

# d1
# Out[75]: datetime.datetime(2021, 4, 8, 7, 57, 18, 309209, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))

# d2
# Out[76]: datetime.datetime(2021, 4, 8, 7, 57, 18, 309209, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))

# d1 == d2
# Out[77]: True

Python datetime difference between .localize and tzinfo

When you create d2 = datetime(2015, 8, 1, 0, 0, 0, 0, local_tz), it does not handle daylight saving time (DST) correctly. local_tz.localize() does.

d1 is

>>> local_tz.localize(datetime(2015, 8, 1, 0, 0, 0, 0))
datetime.datetime(
2015, 8, 1, 0, 0,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>
)

d2 is

>>> datetime(2015, 8, 1, 0, 0, 0, 0, local_tz)
datetime.datetime(
2015, 8, 1, 0, 0,
tzinfo=<DstTzInfo 'America/Los_Angeles' LMT-1 day, 16:07:00 STD>
)

You can see that they are not representing the same time.

d2 way is fine if you are going to work with UTC. UTC does not have daylight saving time (DST) transitions to deal with.

The correct way to handle timezone is to use local_tz.localize() to support daylight saving time (DST)

More information and additional examples can be found here:

http://pytz.sourceforge.net/#localized-times-and-date-arithmetic

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.

adding tzinfo for naive datetime cause strange offset?

Ok, i dont know whats wrong with this, but i found solution using django make_aware:

from django.utils.timezone import make_aware

def _converttime2(dt_str, tz_str):
dt = datetime.fromisoformat(dt_str)
tz = pytz.timezone(tz_str)
dt = make_aware(dt, tz)
return dt

Why doesn't pytz localize() produce a datetime object with tzinfo matching the tz object that localized it?

They are the same time zone - "Europe/Berlin".

When you are printing them, the output includes the abbreviation and offset that applies at that particular point in time.

If you examine the tz data sources, you'll see:

# Zone  NAME            GMTOFF   RULES       FORMAT   [UNTIL]
Zone Europe/Berlin 0:53:28 - LMT 1893 Apr
1:00 C-Eur CE%sT 1945 May 24 2:00
1:00 SovietZone CE%sT 1946
1:00 Germany CE%sT 1980
1:00 EU CE%sT

So it would appear that when the time zone has not localized a datetime, then it just uses the first entry.

It would also appear that pytz doesn't retain the extra 28 seconds from the original local mean time deviation - but that doesn't matter unless you are working with dates in Berlin before April 1893.



Related Topics



Leave a reply



Submit