Python Timezone conversion

Solution 1:

I have found that the best approach is to convert the "moment" of interest to a utc-timezone-aware datetime object (in python, the timezone component is not required for datetime objects).

Then you can use astimezone to convert to the timezone of interest (reference).

from datetime import datetime
import pytz

utcmoment_naive = datetime.utcnow()
utcmoment = utcmoment_naive.replace(tzinfo=pytz.utc)

# print "utcmoment_naive: {0}".format(utcmoment_naive) # python 2
print("utcmoment_naive: {0}".format(utcmoment_naive))
print("utcmoment:       {0}".format(utcmoment))

localFormat = "%Y-%m-%d %H:%M:%S"

timezones = ['America/Los_Angeles', 'Europe/Madrid', 'America/Puerto_Rico']

for tz in timezones:
    localDatetime = utcmoment.astimezone(pytz.timezone(tz))
    print(localDatetime.strftime(localFormat))

# utcmoment_naive: 2017-05-11 17:43:30.802644
# utcmoment:       2017-05-11 17:43:30.802644+00:00
# 2017-05-11 10:43:30
# 2017-05-11 19:43:30
# 2017-05-11 13:43:30

So, with the moment of interest in the local timezone (a time that exists), you convert it to utc like this (reference).

localmoment_naive = datetime.strptime('2013-09-06 14:05:10', localFormat)

localtimezone = pytz.timezone('Australia/Adelaide')

try:
    localmoment = localtimezone.localize(localmoment_naive, is_dst=None)
    print("Time exists")

    utcmoment = localmoment.astimezone(pytz.utc)

except pytz.exceptions.NonExistentTimeError as e:
    print("NonExistentTimeError")

Solution 2:

Using pytz

from datetime import datetime
from pytz import timezone

fmt = "%Y-%m-%d %H:%M:%S %Z%z"
timezonelist = ['UTC','US/Pacific','Europe/Berlin']
for zone in timezonelist:

    now_time = datetime.now(timezone(zone))
    print now_time.strftime(fmt)

Solution 3:

import datetime
import pytz

def convert_datetime_timezone(dt, tz1, tz2):
    tz1 = pytz.timezone(tz1)
    tz2 = pytz.timezone(tz2)

    dt = datetime.datetime.strptime(dt,"%Y-%m-%d %H:%M:%S")
    dt = tz1.localize(dt)
    dt = dt.astimezone(tz2)
    dt = dt.strftime("%Y-%m-%d %H:%M:%S")

    return dt

-

  • dt: date time string
  • tz1: initial time zone
  • tz2: target time zone

-

> convert_datetime_timezone("2017-05-13 14:56:32", "Europe/Berlin", "PST8PDT")
'2017-05-13 05:56:32'

> convert_datetime_timezone("2017-05-13 14:56:32", "Europe/Berlin", "UTC")
'2017-05-13 12:56:32'

-

> pytz.all_timezones[0:10]
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 'Africa/Algiers',
 'Africa/Asmara',
 'Africa/Asmera',
 'Africa/Bamako',
 'Africa/Bangui',
 'Africa/Banjul',
 'Africa/Bissau']

Solution 4:

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

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime

>>> d = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo('America/Los_Angeles'))
>>> d.astimezone(ZoneInfo('Europe/Berlin'))  # 12:00 in Cali will be 20:00 in Berlin
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))

Wikipedia list of available time zones


Some functions such as now() and utcnow() return timezone-unaware datetimes, meaning they contain no timezone information. I recommend only requesting timezone-aware values from them using the keyword tz=ZoneInfo('localtime').

If astimezone gets a timezone-unaware input, it will assume it is local time, which can lead to errors:

>>> datetime.utcnow()  # UTC -- NOT timezone-aware!!
datetime.datetime(2020, 6, 1, 22, 39, 57, 376479)
>>> datetime.now()     # Local time -- NOT timezone-aware!!
datetime.datetime(2020, 6, 2, 0, 39, 57, 376675)

>>> datetime.now(tz=ZoneInfo('localtime'))  # timezone-aware
datetime.datetime(2020, 6, 2, 0, 39, 57, 376806, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> datetime.now(tz=ZoneInfo('Europe/Berlin'))  # timezone-aware
datetime.datetime(2020, 6, 2, 0, 39, 57, 376937, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))
>>> datetime.utcnow().astimezone(ZoneInfo('Europe/Berlin'))  # WRONG!!
datetime.datetime(2020, 6, 1, 22, 39, 57, 377562, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))

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

Solution 5:

To convert a time in one timezone to another timezone in Python, you could use datetime.astimezone():

time_in_new_timezone = time_in_old_timezone.astimezone(new_timezone)

Given aware_dt (a datetime object in some timezone), to convert it to other timezones and to print the times in a given time format:

#!/usr/bin/env python3
import pytz  # $ pip install pytz

time_format = "%Y-%m-%d %H:%M:%S%z"
tzids = ['Asia/Shanghai', 'Europe/London', 'America/New_York']
for tz in map(pytz.timezone, tzids):
    time_in_tz = aware_dt.astimezone(tz)
    print(f"{time_in_tz:{time_format}}")

If f"" syntax is unavailable, you could replace it with "".format(**vars())

where you could set aware_dt from the current time in the local timezone:

from datetime import datetime
import tzlocal  # $ pip install tzlocal

local_timezone = tzlocal.get_localzone()
aware_dt = datetime.now(local_timezone) # the current time

Or from the input time string in the local timezone:

naive_dt = datetime.strptime(time_string, time_format)
aware_dt = local_timezone.localize(naive_dt, is_dst=None)

where time_string could look like: '2016-11-19 02:21:42'. It corresponds to time_format = '%Y-%m-%d %H:%M:%S'.

is_dst=None forces an exception if the input time string corresponds to a non-existing or ambiguous local time such as during a DST transition. You could also pass is_dst=False, is_dst=True. See links with more details at Python: How do you convert datetime/timestamp from one timezone to another timezone?