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.

Leave a Comment