65

I've got a datetime which has no timezone information. I'm now getting the timezone info and would like to add the timezone into the existed datetime instance, how can I do?

d = datetime.datetime.now()
tz = pytz.timezone('Asia/Taipei')

How to add the timezone info tz into datetime a

waitingkuo
  • 89,478
  • 28
  • 112
  • 118

2 Answers2

94

Use tz.localize(d) to localize the instance. From the documentation:

The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):

>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500

If you don't use tz.localize(), but use datetime.replace(), chances are that a historical offset is used instead; tz.localize() will pick the right offset in effect for the given date. The US Eastern timezone DST start and end dates have changed over time, for example.

When you try to localize a datetime value that is ambiguous because it straddles the transition period from summer to winter time or vice-versa, the timezone will be consulted to see if the resulting datetime object should have .dst() return True or False. You can override the default for the timezone with the is_dst keyword argument for .localize():

dt = tz.localize(naive, is_dst=True)

or even switch off the choice altogether by setting is_dst=None. In that case, or in the rare cases there is no default set for a timezone, an ambiguous datetime value would lead to a AmbiguousTimeError exception being raised. The is_dst flag is only consulted for datetime values that are ambiguous and is ignored otherwise.

To go back the other way, turn a timezone-aware object back to a naive object, use .replace(tzinfo=None):

naivedt = awaredt.replace(tzinfo=None)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Is there any convenient way to get the naive `datetime` from `datetime` which has tzinfo? – waitingkuo Dec 21 '12 at 17:37
  • 6
    @waitingkuo: call `.replace(tzinfo=None)` on the `datetime` object. The return value is a naive `datetime` instance. – Martijn Pieters Dec 21 '12 at 17:39
  • `is_dst` parameter is worth mentioning to resolve ambiguous times or to assert that there is no DST transition at the time. – jfs Dec 21 '12 at 18:36
  • @J.F.Sebastian: There, a short paragraph on `is_dst` added. – Martijn Pieters Dec 21 '12 at 19:20
  • also, `local_dt = tz.localize(dt)` may produce a non-existing time (during "sprint forward" DST transition). To fix it, call `local_dt = tz.normalize(local_dt)`. Or use `is_dst=None` that prevents `tz.localize()` from returning ambiguous or non-existing times. – jfs Sep 13 '14 at 17:05
  • `pytz.timezone.localize(datetime.time, pyzt)` will fail for timzones other than GMT/UTC (`pytz.timezone.tzinfo.timedelta` is nonzero). The `localize` method works great for `datetime`s but not for `time`s (because it might have to wrap around to a new day). For a `time` just use `new_time = old_time.replace(tzinfo=pytz.timezone(timezone_name))`. This will NOT adjust the time to account for the new time zone, just presume it was already "measured" in local time. Incidentally, if you don't want to "shift" a datetime it `.replace` works for datetimes as well. – hobs Mar 25 '15 at 17:47
  • 1
    @hobs: I'd not expect localize to work for `time` objects, no, because the localisation requires a *date* to do its job correctly. Timezone offsets make no sense for *just* a time component, you are missing the information for DST and historical context. – Martijn Pieters Mar 25 '15 at 18:01
  • @MartjinPieters Exactly. But if you *need* a tz-aware time (which is only useful with other aware times in the same time zone) you can use `.replace()`. I agree that doesn't normally make sense... but if you inform a `time` of a time zone that tz can be used later when forming a datetime. – hobs Mar 25 '15 at 18:15
  • 3
    @hobs: it is wrong to use `.replace()` with a `pytz` timezone that may have multiple utc offsets (many timezones do). The default tzinfo object usually corresponds to LMT (solar time) that is not what you want in most cases (I think the reasoning behind the default is to help reveal the incorrect `.replace()` usage). – jfs Mar 25 '15 at 18:43
  • @J.F.Sebastian Makes sense that it is 'wrong'. But it might be OK for someone in my situation (with `datetime.time` instances without date info yet). This happened to me processing a CSV from wunderground.com. The TZ info is available in the column header (and sometimes in the field string), but date is available elsewhere. So I just tagged the time with the tzinfo and used that tzinfo to localize with the date later. I'm sure it's wrong, a hack, but it worked for me. The downvote on my misguided answer will hopefully encourage others to think hard about how to solve this edge case properly. – hobs Mar 25 '15 at 18:52
  • @hobs: why not just store the timezone and time separately and when you combine that with the date *then* apply the timezone? – Martijn Pieters Mar 25 '15 at 19:27
  • @MartjinPieters Agree that's probably safer from a timezone accuracy/confusion/ambiguity perspective, but adds complication and might introduce bookkeeping (indexing) errors... I prefer to create one object per CSV "cell" and pass a list of lists of objects around rather than a list of lists of 2-tuples of objects. – hobs Mar 25 '15 at 22:22
27

If you know that your original datetime was "measured" in the time zone you are trying to add to it, you could (but probably shouldn't) use replace rather than localize.

# d = datetime.datetime.now()
# tz = pytz.timezone('Asia/Taipei')
d = d.replace(tzinfo=tz)

I can imagine 2 times when this might make sense (the second one happened to me):

  1. Your server locale is set to the incorrect time zone and you are trying to correct a datetime instance by making it aware of this incorrect timezone (and presumably later localizing it to the "correct" time zone so the values of now() match up to other times you are comparing it to (your watch, perhaps)
  2. You want to "tag" a time instance (NOT a datetime) with a time zone (tzinfo) attribute so that attribute can be used later to form a full datetime instance.
hobs
  • 18,473
  • 10
  • 83
  • 106
  • 5
    3. you've retrieved a datetime from a DB where the column is naive, but you know the TZ it was stored in, and you want to manipulate it after retrieval. – Marc Jul 21 '20 at 21:45
  • 3
    4. you've generated a utc time using utcnow, which is tz-naive, and you need to compare it against a tx-aware time. – Marc May 14 '21 at 03:36
  • 1
    As pointed out in the answer above, this here can run into problems. Quick example: `parse('2022-09-03 10:00').replace(tzinfo=pytz.timezone('Europe/Berlin')) - parse('2022-09-03 10:00:00+02:00')` -> `datetime.timedelta(seconds=4020)`, `pytz.timezone('Europe/Berlin').localize(parse('2022-09-03 10:00')) - parse('2022-09-03 10:00:00+02:00')` -> `datetime.timedelta(0)` – nuts Sep 07 '22 at 16:28