How to make a timezone aware datetime object in Python?
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
Mark Tozzi
Updated on January 28, 2022Comments
-
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 almost 13 yearsWell, 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 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 over 12 yearsthanks, I had use from dt_aware to unware dt_unware = datetime.datetime(*(dt_aware.timetuple()[:6])),
-
jfs about 10 yearsif the timezone is not UTC then don't use the constructor directly:
aware = datetime(..., tz)
, use.localize()
instead. -
jfs about 10 yearsIt is worth mentioning that local time may be ambiguous.
tz.localize(..., is_dst=None)
asserts that it is not . -
jfs about 10 yearsyou could use
localtz.localize(dt_unware, is_dst=None)
to raise an exception ifdt_unware
represents non-existing or ambiguous local time (note: there were no such issue in the previous revision of your answer wherelocaltz
was UTC because UTC has no DST transitions -
Sérgio about 10 years@J.F. Sebastian , first comment applied
-
mkoistinen over 8 yearsI do like this solution (+1), but it is dependent on Django, which is not what they were looking for (-1). =)
-
Tim Tisdall over 8 yearslooking at pytz source,
localize
simply calls.replace(tzinfo= ... )
provided you called it on an unawaredatetime
; anything else throws an exception. I don't see anything about it doing calculations. -
Tim Tisdall over 8 yearsah... I found it. :) It does that for
pytz.utc.localize
, but some of the other timezones do calculations. -
unutbu over 8 years
-
Christian Long almost 8 yearsI appreciate you showing both directions of the conversion.
-
Oli over 7 yearsYou 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 over 7 yearsVery good answer for preventing the
pytz
issues, I'm glad I scrolled down a bit! Didn't want to tackle withpytz
on my remote servers indeed :) -
7yl4r over 7 yearsNote that
from datetime import timezone
works in py3 but not py2.7. -
Blairg23 about 7 yearsYou should note that
dt.replace(tzinfo=timezone.utc)
returns a new datetime, it does not modifydt
in place. (I will edit to show this). -
iurii about 7 yearsA 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 almost 7 yearsThis 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 almost 7 yearsWhat is the difference between using
replace()
and just settingunaware.tzinfo = datetime.timezone.utc
? -
unutbu almost 7 years@Jens: There isn't much difference, except that one is a function call and the other is an assignment.
-
Blairg23 almost 7 yearsThis 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 over 6 yearsHow might you, instead of using timezone.utc, provide a different timezone as a string (eg "America/Chicago")?
-
zk82 over 5 years@rapt For python2 you may miss the
from datetime import tzinfo
when definingutc
in the except path. -
Florian over 5 yearsi 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 over 5 years@bumpkin better late than never, i guess:
tz = pytz.timezone('America/Chicago')
-
JoeyC almost 5 years@bumpkin I couldn't find a way to do it without using pytz.
-
Martin Thoma almost 5 yearsIf you want to make it UTC:
.replace(tzinfo=dateutil.tz.UTC)
-
Nikhil VJ over 4 yearsThanks, 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 over 4 yearsputz has a bug that sets Amsterdam timezone to + 20 minutes to UTC, some archaic timezone from 1937. You had one job pytz.
-
kubanczyk about 4 yearsOne import less and just:
.replace(tzinfo=datetime.timezone.utc)
-
xjcl almost 4 yearsCan you give a reproducible example of
replace(tzinfo=...)
having unexpected behavior? -
FObersteiner almost 4 yearson Windows, you also need to
pip install tzdata
-
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 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 almost 4 yearsHow is this different to the main answer?
-
Harry Moreno almost 4 yearsI 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 almost 4 yearsThe 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 almost 4 yearsthe 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 almost 4 yearsSo the only difference is the using the named parameter?
-
Harry Moreno almost 4 yearsand 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 over 3 yearstzinfo named parameter is not mentioned in the accepted answer.
-
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 installtzdata
unconditionally (since the system data is prioritized overtzdata
) if your application needs time zone data. -
xjcl over 3 years@Paul That's unfortunate since I was really hoping for a standard library solution
-
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. about 3 yearsYou can save a line and do
datetime.now(timezone.utc)
, as in ufficial doc docs.python.org/3/library/… -
FObersteiner almost 3 yearsyou don't even need
pytz
for this; usedatetime.timezone.utc
instead. -
Eduardo Tolmasquim almost 3 yearsIf you want to convert to UTC:
dt_aware = timezone.make_aware(dt_unaware, timezone.utc)
-
étale-cohomology over 2 yearsWhat if you want a timezone other than UTC?
-
Harry Moreno over 2 yearswith pytz, ``` from pytz import timezone eastern = timezone('US/Eastern') datetime.datetime(2019, 12, 7, tzinfo=eastern) ```
-
00schneider over 2 yearsjust be aware that
datetime.utcnow()
does not return a timezone aware datetime (in contrast to its name) -
FrackeR011 over 2 yearsThanks a lot. I wasted a lot of time trying to use
replace()
but it didn't work. -
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 almost 2 yearsI 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 almost 2 yearsValueError: Not naive datetime (tzinfo is already set)