How to convert a UTC datetime to a local datetime using only standard library?
I have a python datetime
instance that was created using datetime.utcnow()
and persisted in database.
For display, I would like to convert the datetime
instance retrieved from the database to local datetime
using the default local timezone (i.e., as if the datetime
was created using datetime.now()
).
How can I convert the UTC datetime
to a local datetime
using only python standard library (e.g., no pytz
dependency)?
It seems one solution 开发者_如何学Pythonwould be to use datetime.astimezone(tz)
, but how would you get the default local timezone?
In Python 3.3+:
from datetime import datetime, timezone
def utc_to_local(utc_dt):
return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
In Python 2/3:
import calendar
from datetime import datetime, timedelta
def utc_to_local(utc_dt):
# get integer timestamp to avoid precision lost
timestamp = calendar.timegm(utc_dt.timetuple())
local_dt = datetime.fromtimestamp(timestamp)
assert utc_dt.resolution >= timedelta(microseconds=1)
return local_dt.replace(microsecond=utc_dt.microsecond)
Using pytz
(both Python 2/3):
import pytz
local_tz = pytz.timezone('Europe/Moscow') # use your local timezone name here
# NOTE: pytz.reference.LocalTimezone() would produce wrong result here
## You could use `tzlocal` module to get local timezone on Unix and Win32
# from tzlocal import get_localzone # $ pip install tzlocal
# # get local timezone
# local_tz = get_localzone()
def utc_to_local(utc_dt):
local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
return local_tz.normalize(local_dt) # .normalize might be unnecessary
Example
def aslocaltimestr(utc_dt):
return utc_to_local(utc_dt).strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')
print(aslocaltimestr(datetime(2010, 6, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime(2010, 12, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime.utcnow()))
Output
Python 3.32010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.093745 MSK+0400
Python 2
2010-06-06 21:29:07.730000
2010-12-06 20:29:07.730000
2012-11-08 14:19:50.093911
pytz
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.146917 MSK+0400
Note: it takes into account DST and the recent change of utc offset for MSK timezone.
I don't know whether non-pytz solutions work on Windows.
Since Python 3.9 you can use the zoneinfo
module.
First lets get that time with utcnow()
:
>>> from datetime import datetime
>>> database_time = datetime.utcnow()
>>> database_time
datetime.datetime(2021, 9, 24, 4, 18, 27, 706532)
Then create the time zones:
>>> from zoneinfo import ZoneInfo
>>> utc = ZoneInfo('UTC')
>>> localtz = ZoneInfo('localtime')
Then convert. To convert between timezones, the datetime must know what timezone it is in, then we just use astimezone()
:
>>> utctime = database_time.replace(tzinfo=utc)
>>> localtime = utctime.astimezone(localtz)
>>> localtime
datetime.datetime(2021, 9, 24, 6, 18, 27, 706532, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
For Python 3.6 to 3.8 you need the backports.zoneinfo module:
>>> try:
>>> from zoneinfo import ZoneInfo
>>> except ImportError:
>>> from backports.zoneinfo import ZoneInfo
The rest is the same.
For versions earlier than that need pytz
or dateutil
. datutil works similar to zoneinfo:
>>> from dateutil import tz
>>> utc = tz.gettz('UTC')
>>> localtz = tz.tzlocal()
The Conversion:
>>> utctime = now.replace(tzinfo=UTC)
>>> localtime = utctime.astimezone(localtz)
>>> localtime
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())
pytz
has a different interface which is a result of Python's time zone handling not handling ambigous times:
>>> import pytz
>>> utc = pytz.timezone('UTC')
# There is no local timezone support, you need to know your timezone
>>> localtz = pytz.timezone('Europe/Paris')
>>> utctime = utc.localize(database_time)
>>> localtime = localtz.normalize(utctime.astimezone(localtz))
>>> localtime
Python 3.9 adds the zoneinfo
module so now it can be done as follows (stdlib only):
from zoneinfo import ZoneInfo
from datetime import datetime
utc_unaware = datetime(2020, 10, 31, 12) # loaded from database
utc_aware = utc_unaware.replace(tzinfo=ZoneInfo('UTC')) # make aware
local_aware = utc_aware.astimezone(ZoneInfo('localtime')) # convert
Central Europe is 1 or 2 hours ahead of UTC, so local_aware
is:
datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))
as str
:
2020-10-31 13:00:00+01:00
Windows has no system time zone database, so here an extra package is needed:
pip install tzdata
There is a backport to allow use in Python 3.6 to 3.8:
sudo pip install backports.zoneinfo
Then:
from backports.zoneinfo import ZoneInfo
You can't do it with standard library. Using pytz module you can convert any naive/aware datetime object to any other time zone. Lets see some examples using Python 3.
Naive objects created through class method
utcnow()
To convert a naive object to any other time zone, first you have to convert it into aware datetime object. You can use the replace
method for converting a naive datetime object to an aware datetime object. Then to convert an aware datetime object to any other timezone you can use astimezone
method.
The variable pytz.all_timezones
gives you the list of all available time zones in pytz module.
import datetime,pytz
dtobj1=datetime.datetime.utcnow() #utcnow class method
print(dtobj1)
dtobj3=dtobj1.replace(tzinfo=pytz.UTC) #replace method
dtobj_hongkong=dtobj3.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
Naive objects created through class method
now()
Because now
method returns current date and time, so you have to make the datetime object timezone aware first. The localize
function converts a naive datetime object into a timezone-aware datetime object. Then you can use the astimezone
method to convert it into another timezone.
dtobj2=datetime.datetime.now()
mytimezone=pytz.timezone("Europe/Vienna") #my current timezone
dtobj4=mytimezone.localize(dtobj2) #localize function
dtobj_hongkong=dtobj4.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
Building on Alexei's comment. This should work for DST too.
import time
import datetime
def utc_to_local(dt):
if time.localtime().tm_isdst:
return dt - datetime.timedelta(seconds = time.altzone)
else:
return dt - datetime.timedelta(seconds = time.timezone)
I think I figured it out: computes number of seconds since epoch, then converts to a local timzeone using time.localtime, and then converts the time struct back into a datetime...
EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60
def utc_to_local_datetime( utc_datetime ):
delta = utc_datetime - EPOCH_DATETIME
utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
time_struct = time.localtime( utc_epoch )
dt_args = time_struct[:6] + (delta.microseconds,)
return datetime.datetime( *dt_args )
It applies the summer/winter DST correctly:
>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)
The standard Python library does not come with any tzinfo
implementations at all. I've always considered this a surprising shortcoming of the datetime module.
The documentation for the tzinfo class does come with some useful examples. Look for the large code block at the end of the section.
Use time.timezone
, it gives an integer in "seconds west of UTC".
For example:
from datetime import datetime, timedelta, timezone
import time
# make datetime from timestamp, thus no timezone info is attached
now = datetime.fromtimestamp(time.time())
# make local timezone with time.timezone
local_tz = timezone(timedelta(seconds=-time.timezone))
# attach different timezones as you wish
utc_time = now.astimezone(timezone.utc)
local_time = now.astimezone(local_tz)
print(utc_time.isoformat(timespec='seconds'))
print(local_time.isoformat(timespec='seconds'))
On my PC (Python 3.7.3), it gives:
2021-05-07T12:50:46+00:00
2021-05-07T20:50:46+08:00
Pretty simple and uses only standard libraries~
The easiest way I have found is to get the time offset of where you are, then subtract that from the hour.
def format_time(ts,offset):
if not ts.hour >= offset:
ts = ts.replace(day=ts.day-1)
ts = ts.replace(hour=ts.hour-offset)
else:
ts = ts.replace(hour=ts.hour-offset)
return ts
This works for me, in Python 3.5.2.
A simple (but maybe flawed) way that works in Python 2 and 3:
import time
import datetime
def utc_to_local(dt):
return dt - datetime.timedelta(seconds = time.timezone)
Its advantage is that it's trivial to write an inverse function
Here is another way to change timezone in datetime format (I know I wasted my energy on this but I didn't see this page so I don't know how) without min. and sec. cause I don't need it for my project:
def change_time_zone(year, month, day, hour):
hour = hour + 7 #<-- difference
if hour >= 24:
difference = hour - 24
hour = difference
day += 1
long_months = [1, 3, 5, 7, 8, 10, 12]
short_months = [4, 6, 9, 11]
if month in short_months:
if day >= 30:
day = 1
month += 1
if month > 12:
year += 1
elif month in long_months:
if day >= 31:
day = 1
month += 1
if month > 12:
year += 1
elif month == 2:
if not year%4==0:
if day >= 29:
day = 1
month += 1
if month > 12:
year += 1
else:
if day >= 28:
day = 1
month += 1
if month > 12:
year += 1
return datetime(int(year), int(month), int(day), int(hour), 00)
for a specific situation:
input utc datetime string. // usually from log
output locale datetime string.
def utc_to_locale(utc_str):
# from utc to locale
d1=datetime.fromisoformat(utc_str+'-00:00')
return d1.astimezone().strftime('%F %T.%f')[:-3]
tests:
>>> utc_to_locale('2022-02-14 00:49:06')
'2022-02-14 08:49:06.000'
>>> utc_to_locale('2022-02-14 00:49:06.123')
'2022-02-14 08:49:06.123'
>>> utc_to_locale('2022-02-14T00:49:06.123')
'2022-02-14 08:49:06.123'
This is a terrible way to do it but it avoids creating a definition. It fulfills the requirement to stick with the basic Python3 library.
# Adjust from UST to Eastern Standard Time (dynamic)
# df.my_localtime should already be in datetime format, so just in case
df['my_localtime'] = pd.to_datetime.df['my_localtime']
df['my_localtime'] = df['my_localtime'].dt.tz_localize('UTC').dt.tz_convert('America/New_York').astype(str)
df['my_localtime'] = pd.to_datetime(df.my_localtime.str[:-6])
Use timedelta to switch between timezones. All you need is the offset in hours between timezones. Don't have to fiddle with boundaries for all 6 elements of a datetime object. timedelta handles leap years, leap centuries, etc., too, with ease. You must first
from datetime import datetime, timedelta
Then if offset
is the timezone delta in hours:
timeout = timein + timedelta(hours = offset)
where timein and timeout are datetime objects. e.g.
timein + timedelta(hours = -8)
converts from GMT to PST.
So, how to determine offset
? Here is a simple function provided you only have a few possibilities for conversion without using datetime objects that are timezone "aware" which some other answers nicely do. A bit manual, but sometimes clarity is best.
def change_timezone(timein, timezone, timezone_out):
'''
changes timezone between predefined timezone offsets to GMT
timein - datetime object
timezone - 'PST', 'PDT', 'GMT' (can add more as needed)
timezone_out - 'PST', 'PDT', 'GMT' (can add more as needed)
'''
# simple table lookup
tz_offset = {'PST': {'GMT': 8, 'PDT': 1, 'PST': 0}, \
'GMT': {'PST': -8, 'PDT': -7, 'GMT': 0}, \
'PDT': {'GMT': 7, 'PST': -1, 'PDT': 0}}
try:
offset = tz_offset[timezone][timezone_out]
except:
msg = 'Input timezone=' + timezone + ' OR output time zone=' + \
timezone_out + ' not recognized'
raise DateTimeError(msg)
return timein + timedelta(hours = offset)
After looking at the numerous answers and playing around with the tightest code I can think of (for now) it seems best that all applications, where time is important and mixed timezones must be accounted for, should make a real effort to make all datetime objects "aware". Then it would seem the simplest answer is:
timeout = timein.astimezone(pytz.timezone("GMT"))
to convert to GMT for example. Of course, to convert to/from any other timezone you wish, local or otherwise, just use the appropriate timezone string that pytz understands (from pytz.all_timezones). Daylight savings time is then also taken into account.
精彩评论