Nitro Zark
Nitro Zark

Reputation: 3567

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 would be to use datetime.astimezone(tz), but how would you get the default local timezone?

Upvotes: 345

Views: 518976

Answers (14)

yurenchen
yurenchen

Reputation: 2483

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'

Upvotes: 0

Lennart Regebro
Lennart Regebro

Reputation: 172249

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

Upvotes: 99

henry zhu
henry zhu

Reputation: 651

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~

Upvotes: 4

xjcl
xjcl

Reputation: 15309

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

Upvotes: 28

Karl Rudnick
Karl Rudnick

Reputation: 115

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.

Upvotes: -1

Arthur D. Howland
Arthur D. Howland

Reputation: 4557

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])

Upvotes: -1

jfs
jfs

Reputation: 414215

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.3
2010-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.

Upvotes: 452

Guest
Guest

Reputation: 1

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)

Upvotes: 0

John Kerns
John Kerns

Reputation: 89

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)

Upvotes: 8

Zld Productions
Zld Productions

Reputation: 349

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.

Upvotes: 1

N Randhawa
N Randhawa

Reputation: 9511

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)

Upvotes: 17

Aleksei Averchenko
Aleksei Averchenko

Reputation: 1776

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

Upvotes: 0

Mark Ransom
Mark Ransom

Reputation: 308186

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.

Upvotes: 4

Nitro Zark
Nitro Zark

Reputation: 3567

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)

Upvotes: 6

Related Questions