How to make a timezone aware datetime object in Python?

510,084

Solution 1

In general, to make a naive datetime timezone-aware, use the localize method:

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

For the UTC timezone, it is not really necessary to use localize since there is no daylight savings time calculation to handle:

now_aware = unaware.replace(tzinfo=pytz.UTC)

works. (.replace returns a new datetime; it does not modify unaware.)

Solution 2

All of these examples use an external module, but you can achieve the same result using just the datetime module, as also presented in this SO answer:

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

Fewer dependencies and no pytz issues.

NOTE: If you wish to use this with python3 and python2, you can use this as well for the timezone import (hardcoded for UTC):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()

Solution 3

I wrote this Python 2 script in 2011, but never checked if it works on Python 3.

I had moved from dt_aware to dt_unaware:

dt_unaware = dt_aware.replace(tzinfo=None)

and dt_unware to dt_aware:

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

Solution 4

I use this statement in Django to convert an unaware time to an aware:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

Solution 5

Python 3.9 adds the zoneinfo module so now only the standard library is needed!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Attach a timezone:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Attach the system's local timezone:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Subsequently it is properly converted to other timezones:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Wikipedia list of available time zones


Windows has no system time zone database, so here an extra package is needed:

pip install tzdata  

There is a backport to allow use of zoneinfo in Python 3.6 to 3.8:

pip install backports.zoneinfo

Then:

from backports.zoneinfo import ZoneInfo
Share:
510,084
Mark Tozzi
Author by

Mark Tozzi

Updated on January 28, 2022

Comments

  • Mark Tozzi
    Mark Tozzi over 2 years

    What I need to do

    I have a timezone-unaware datetime object, to which I need to add a time zone in order to be able to compare it with other timezone-aware datetime objects. I do not want to convert my entire application to timezone unaware for this one legacy case.

    What I've Tried

    First, to demonstrate the problem:

    Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
    [GCC 4.2.1 (Apple Inc. build 5646)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import datetime
    >>> import pytz
    >>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
    >>> unaware
    datetime.datetime(2011, 8, 15, 8, 15, 12)
    >>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
    >>> aware
    datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
    >>> aware == unaware
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't compare offset-naive and offset-aware datetimes
    

    First, I tried astimezone:

    >>> unaware.astimezone(pytz.UTC)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: astimezone() cannot be applied to a naive datetime
    >>>
    

    It's not terribly surprising this failed, since it's actually trying to do a conversion. Replace seemed like a better choice (as per How do I get a value of datetime.today() in Python that is "timezone aware"?):

    >>> unaware.replace(tzinfo=pytz.UTC)
    datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
    >>> unaware == aware
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't compare offset-naive and offset-aware datetimes
    >>> 
    

    But as you can see, replace seems to set the tzinfo, but not make the object aware. I'm getting ready to fall back to doctoring the input string to have a timezone before parsing it (I'm using dateutil for parsing, if that matters), but that seems incredibly kludgy.

    Also, I've tried this in both Python 2.6 and Python 2.7, with the same results.

    Context

    I am writing a parser for some data files. There is an old format I need to support where the date string does not have a timezone indicator. I've already fixed the data source, but I still need to support the legacy data format. A one time conversion of the legacy data is not an option for various business BS reasons. While in general, I do not like the idea of hard-coding a default timezone, in this case it seems like the best option. I know with reasonable confidence that all the legacy data in question is in UTC, so I'm prepared to accept the risk of defaulting to that in this case.

  • Mark Tozzi
    Mark Tozzi almost 13 years
    Well, I feel silly. Replace returns a new datetime. It says that right there in the docs too, and I completely missed that. Thanks, that's exactly what I was looking for.
  • Karl Knechtel - away from home
    Karl Knechtel - away from home almost 13 years
    "Replace returns a new datetime." Yep. The hint that the REPL gives you is that it's showing you the returned value. :)
  • Sérgio
    Sérgio over 12 years
    thanks, I had use from dt_aware to unware dt_unware = datetime.datetime(*(dt_aware.timetuple()[:6])),
  • jfs
    jfs about 10 years
    if the timezone is not UTC then don't use the constructor directly: aware = datetime(..., tz), use .localize() instead.
  • jfs
    jfs about 10 years
    It is worth mentioning that local time may be ambiguous. tz.localize(..., is_dst=None) asserts that it is not .
  • jfs
    jfs about 10 years
    you could use localtz.localize(dt_unware, is_dst=None) to raise an exception if dt_unware represents non-existing or ambiguous local time (note: there were no such issue in the previous revision of your answer where localtz was UTC because UTC has no DST transitions
  • Sérgio
    Sérgio about 10 years
    @J.F. Sebastian , first comment applied
  • mkoistinen
    mkoistinen over 8 years
    I do like this solution (+1), but it is dependent on Django, which is not what they were looking for (-1). =)
  • Tim Tisdall
    Tim Tisdall over 8 years
    looking at pytz source, localize simply calls .replace(tzinfo= ... ) provided you called it on an unaware datetime; anything else throws an exception. I don't see anything about it doing calculations.
  • Tim Tisdall
    Tim Tisdall over 8 years
    ah... I found it. :) It does that for pytz.utc.localize, but some of the other timezones do calculations.
  • unutbu
    unutbu over 8 years
    @TimTisdall: Right. For timezones which are subclasses of DstTzInfo, localize is defined like this.
  • Christian Long
    Christian Long almost 8 years
    I appreciate you showing both directions of the conversion.
  • Oli
    Oli over 7 years
    You don't actually the second argument. The default argument (None) will mean the local timezone is used implicitly. Same with the DST (which is the third argument_
  • Tregoreg
    Tregoreg over 7 years
    Very good answer for preventing the pytz issues, I'm glad I scrolled down a bit! Didn't want to tackle with pytz on my remote servers indeed :)
  • 7yl4r
    7yl4r over 7 years
    Note that from datetime import timezone works in py3 but not py2.7.
  • Blairg23
    Blairg23 about 7 years
    You should note that dt.replace(tzinfo=timezone.utc) returns a new datetime, it does not modify dt in place. (I will edit to show this).
  • iurii
    iurii about 7 years
    A simple one-liner to copy the timezone info from the aware to the unaware object - independently of the timezone: unaware = aware.tzinfo.localize(unaware). This implies however that the aware object has pytz timezone object.
  • gloriphobia
    gloriphobia almost 7 years
    This didn't actually work for me. I found that it's actually just setting the timezone to UTC -- which is wrong if you are not on UTC time.
  • Jens
    Jens almost 7 years
    What is the difference between using replace() and just setting unaware.tzinfo = datetime.timezone.utc?
  • unutbu
    unutbu almost 7 years
    @Jens: There isn't much difference, except that one is a function call and the other is an assignment.
  • Blairg23
    Blairg23 almost 7 years
    This needs more upvotes, trying to do replace(tzinfo=...) on a timezone other than UTC will foul up your datetime. I got -07:53 instead of -08:00 for instance. See stackoverflow.com/a/13994611/1224827
  • bumpkin
    bumpkin over 6 years
    How might you, instead of using timezone.utc, provide a different timezone as a string (eg "America/Chicago")?
  • zk82
    zk82 over 5 years
    @rapt For python2 you may miss the from datetime import tzinfo when defining utc in the except path.
  • Florian
    Florian over 5 years
    i think this answer is wrong: timezones change over time! eg. "Europe/Vienna" once hat an offset of "01:05", so setting it with replace() would offset the datetime by that, and not the current offset of "01:00". resulting in weirdness like >>> tz.normalize(datetime.strptime('2019-02-12', '%Y-%m-%d').replace(tzinfo=tz)).isoformat() '2019-02-12T23:55:00+01:00' better use localize: >>> tz.localize(datetime.datetime.strptime('2019-02-12', '%Y-%m-%d')).isoformat() '2019-02-12T00:00:00+01:00'
  • Florian
    Florian over 5 years
    @bumpkin better late than never, i guess: tz = pytz.timezone('America/Chicago')
  • JoeyC
    JoeyC almost 5 years
    @bumpkin I couldn't find a way to do it without using pytz.
  • Martin Thoma
    Martin Thoma almost 5 years
    If you want to make it UTC: .replace(tzinfo=dateutil.tz.UTC)
  • Nikhil VJ
    Nikhil VJ over 4 years
    Thanks, this helped me "brand" a raw datetime object as "UTC" without the system first assuming it to be local time and then recalculating the values!
  • Boris
    Boris over 4 years
    putz has a bug that sets Amsterdam timezone to + 20 minutes to UTC, some archaic timezone from 1937. You had one job pytz.
  • kubanczyk
    kubanczyk about 4 years
    One import less and just: .replace(tzinfo=datetime.timezone.utc)
  • xjcl
    xjcl almost 4 years
    Can you give a reproducible example of replace(tzinfo=...) having unexpected behavior?
  • FObersteiner
    FObersteiner almost 4 years
    on Windows, you also need to pip install tzdata
  • xjcl
    xjcl almost 4 years
    @MrFuppes Thanks for the tip! I'll test this tomorrow and it to my answer. Do you know what the situation is on Macs?
  • Gustavo Rottgering
    Gustavo Rottgering almost 4 years
    @Sérgio and when you put that argument in .replace and get "TypeError: replace() got an unexpected keyword argument 'tzinfo'"? What can be done for this problem?
  • Jack
    Jack almost 4 years
    How is this different to the main answer?
  • Harry Moreno
    Harry Moreno almost 4 years
    I don't care to use the localize function. This answer is more succinct for those trying to solve their problem quickly (what I wish was the accepted answer).
  • Jack
    Jack almost 4 years
    The localize function is just there to test the assert method right? It's not actually required? aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC) is the same as you've written they have just named the parameter no?
  • Harry Moreno
    Harry Moreno almost 4 years
    the assert is there to demonstrate how to change a datetime between timezone aware and unaware. I actually provided the keyword argument for clarity. you can omit the keyword and rely on positional arguments if you prefer. kwargs are less error prone though.
  • Jack
    Jack almost 4 years
    So the only difference is the using the named parameter?
  • Harry Moreno
    Harry Moreno almost 4 years
    and the assert and the extra paragraphs. Again, the point of my answer is for a quick solution if someone reaches this question looking to make a timezone aware datetime.
  • Aaron
    Aaron over 3 years
    tzinfo named parameter is not mentioned in the accepted answer.
  • Paul
    Paul over 3 years
    @xjcl You need pip install tzdata on any platform where the operating system doesn't provide a time zone database. Macs should work out of the box. It will not hurt to install tzdata unconditionally (since the system data is prioritized over tzdata) if your application needs time zone data.
  • xjcl
    xjcl over 3 years
    @Paul That's unfortunate since I was really hoping for a standard library solution
  • Paul
    Paul over 3 years
    @xjcl tzdata is effectively part of the standard library (we call it a "first party package"), you just need to pip install it because it has a much faster release cadence than CPython.
  • Christian Pao.
    Christian Pao. about 3 years
    You can save a line and do datetime.now(timezone.utc) , as in ufficial doc docs.python.org/3/library/…
  • FObersteiner
    FObersteiner almost 3 years
    you don't even need pytz for this; use datetime.timezone.utc instead.
  • Eduardo Tolmasquim
    Eduardo Tolmasquim almost 3 years
    If you want to convert to UTC: dt_aware = timezone.make_aware(dt_unaware, timezone.utc)
  • étale-cohomology
    étale-cohomology over 2 years
    What if you want a timezone other than UTC?
  • Harry Moreno
    Harry Moreno over 2 years
    with pytz, ``` from pytz import timezone eastern = timezone('US/Eastern') datetime.datetime(2019, 12, 7, tzinfo=eastern) ```
  • 00schneider
    00schneider over 2 years
    just be aware that datetime.utcnow() does not return a timezone aware datetime (in contrast to its name)
  • FrackeR011
    FrackeR011 over 2 years
    Thanks a lot. I wasted a lot of time trying to use replace() but it didn't work.
  • akki
    akki about 2 years
    +1 for pure Python solution. BTW, another way of doing it via a one-liner is datetime.datetime.combine(datetime.datetime(2022, 4, 26), datetime.datetime.min.time(), datetime.timezone.utc)
  • Joe Sadoski
    Joe Sadoski almost 2 years
    I wanted to mention, it's very easy to add this to requirements.txt conditionally for Windows only: tzdata; sys_platform == "win32" (from: stackoverflow.com/a/35614580/705296)
  • CS QGB
    CS QGB almost 2 years
    ValueError: Not naive datetime (tzinfo is already set)