Work with timezone effectively

Timezone can bring a lot headaches when programming.
Luckly our peers have written many great libraries that we can use.
Here is a tutorial on how to deal with timezone in python and Django.

pytz package

pytc is a timezone definition package, and it is very powerful dealing with tiemzones.

you can install it through pip:

In [1]:
pip install pytz # in python environment
The following command must be run outside of the IPython shell:

    $ pip install pytz # in python environment

The Python package manager (pip) can only be used from outside of IPython.
Please reissue the `pip` command in a separate terminal or command prompt.

See the Python documentation for more informations on how to install packages:

    https://docs.python.org/3/installing/

Add time zone information to a naive datetime object

In [2]:
from datetime import datetime
from pytz import timezone

date_str = "2009-05-05 22:28:15"
datetime_obj = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
datetime_obj_utc = datetime_obj.replace(tzinfo=timezone('UTC'))
print(datetime_obj_utc.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
2009-05-05 22:28:15 UTC+0000

Add non-UTC time zone information to a naive datetime object

ATTENTION: datetime.replace() does not handle daylight savings time correctly.
The correct way is to use timezone.localize()

In [3]:
from datetime import datetime
from pytz import timezone

date_str = "2014-05-28 22:28:15"
datetime_obj_naive = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")

# Wrong way!
datetime_obj_pacific = datetime_obj_naive.replace(tzinfo=timezone('US/Pacific'))
print(datetime_obj_pacific.strftime("%Y-%m-%d %H:%M:%S %Z%z"))

# Right way!
datetime_obj_pacific = timezone('US/Pacific').localize(datetime_obj_naive)
print(datetime_obj_pacific.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
2014-05-28 22:28:15 LMT-0753
2014-05-28 22:28:15 PDT-0700

Convert time zones

Use datetime.astimezone(tz) to convert timezone

In [4]:
from datetime import datetime
from pytz import timezone

fmt = "%Y-%m-%d %H:%M:%S %Z%z"

# Current time in UTC
now_utc = datetime.now(timezone('UTC'))
print(now_utc.strftime(fmt))

# Convert to US/Pacific time zone
now_pacific = now_utc.astimezone(timezone('US/Pacific'))
print(now_pacific.strftime(fmt))

# Convert to Europe/Berlin time zone
now_berlin = now_pacific.astimezone(timezone('Europe/Berlin'))
print(now_berlin.strftime(fmt))
2018-11-12 06:02:09 UTC+0000
2018-11-11 22:02:09 PST-0800
2018-11-12 07:02:09 CET+0100

List timezones

There are 593 time zones included in the current pytz version.

In [5]:
# Check pytz version
import pytz

print(pytz.__version__)
2017.2
In [6]:
# Check how many timezonmes
from pytz import all_timezones

print(len(all_timezones))
593
In [7]:
# Print all the timezone in US
for zone in all_timezones:
    if 'US' in zone:
        print(zone)
US/Alaska
US/Aleutian
US/Arizona
US/Central
US/East-Indiana
US/Eastern
US/Hawaii
US/Indiana-Starke
US/Michigan
US/Mountain
US/Pacific
US/Pacific-New
US/Samoa

NOTE: all the above content is referenced from Converting time zones for datetime objects in Python - SaltyCrane Blog with slight modification

Deal with teimzone format in windows registry

In [8]:
# A big dictionary mapping windows registry timezone format 
# to the format readable by python
TIMEZONES = {
    "AUSCENTRALSTANDARDTIME": "Australia/Darwin",
    "AUSEASTERNSTANDARDTIME": "Australia/Sydney",
    "AFGHANISTANSTANDARDTIME": "Asia/Kabul",
    "ALASKANSTANDARDTIME": "America/Anchorage",
    "ARABSTANDARDTIME": "Asia/Riyadh",
    "ARABIANSTANDARDTIME": "Asia/Dubai",
    "ARABICSTANDARDTIME": "Asia/Baghdad",
    "ARGENTINASTANDARDTIME": "America/Buenos_Aires",
    "ATLANTICSTANDARDTIME": "America/Halifax",
    "AZERBAIJANSTANDARDTIME": "Asia/Baku",
    "AZORESSTANDARDTIME": "Atlantic/Azores",
    "BAHIASTANDARDTIME": "America/Bahia",
    "BANGLADESHSTANDARDTIME": "Asia/Dhaka",
    "BELARUSSTANDARDTIME": "Europe/Minsk",
    "CANADACENTRALSTANDARDTIME": "America/Regina",
    "CAPEVERDESTANDARDTIME": "Atlantic/Cape_Verde",
    "CAUCASUSSTANDARDTIME": "Asia/Yerevan",
    "CENAUSTRALIASTANDARDTIME": "Australia/Adelaide",
    "CENTRALAMERICASTANDARDTIME": "America/Guatemala",
    "CENTRALASIASTANDARDTIME": "Asia/Almaty",
    "CENTRALBRAZILIANSTANDARDTIME": "America/Cuiaba",
    "CENTRALEUROPESTANDARDTIME": "Europe/Budapest",
    "CENTRALEUROPEANSTANDARDTIME": "Europe/Warsaw",
    "CENTRALPACIFICSTANDARDTIME": "Pacific/Guadalcanal",
    "CENTRALSTANDARDTIME": "America/Chicago",
    "CENTRALSTANDARDTIME(MEXICO)": "America/Mexico_City",
    "CHINASTANDARDTIME": "Asia/Shanghai",
    "DATELINESTANDARDTIME": "Etc/GMT+12",
    "EAFRICASTANDARDTIME": "Africa/Nairobi",
    "EAUSTRALIASTANDARDTIME": "Australia/Brisbane",
    "EEUROPESTANDARDTIME": "Europe/Chisinau",
    "ESOUTHAMERICASTANDARDTIME": "America/Sao_Paulo",
    "EASTERNSTANDARDTIME": "America/New_York",
    "EGYPTSTANDARDTIME": "Africa/Cairo",
    "EKATERINBURGSTANDARDTIME": "Asia/Yekaterinburg",
    "FLESTANDARDTIME": "Europe/Kiev",
    "FIJISTANDARDTIME": "Pacific/Fiji",
    "GMTSTANDARDTIME": "Europe/London",
    "GTBSTANDARDTIME": "Europe/Bucharest",
    "GEORGIANSTANDARDTIME": "Asia/Tbilisi",
    "GREENLANDSTANDARDTIME": "America/Godthab",
    "GREENWICHSTANDARDTIME": "Atlantic/Reykjavik",
    "HAWAIIANSTANDARDTIME": "Pacific/Honolulu",
    "INDIASTANDARDTIME": "Asia/Calcutta",
    "IRANSTANDARDTIME": "Asia/Tehran",
    "ISRAELSTANDARDTIME": "Asia/Jerusalem",
    "JORDANSTANDARDTIME": "Asia/Amman",
    "KALININGRADSTANDARDTIME": "Europe/Kaliningrad",
    "KAMCHATKASTANDARDTIME": "Pacific/Fiji",  # this timezone is UTC+12
    "KOREASTANDARDTIME": "Asia/Seoul",
    "LIBYASTANDARDTIME": "Africa/Tripoli",
    "LINEISLANDSSTANDARDTIME": "Pacific/Kiritimati",
    "MAGADANSTANDARDTIME": "Asia/Magadan",
    "MAURITIUSSTANDARDTIME": "Indian/Mauritius",
    "MIDATLANTICSTANDARDTIME": "Etc/GMT+2",  # this timezone is UTC-2
    "MIDDLEEASTSTANDARDTIME": "Asia/Beirut",
    "MONTEVIDEOSTANDARDTIME": "America/Montevideo",
    "MOROCCOSTANDARDTIME": "Africa/Casablanca",
    "MOUNTAINSTANDARDTIME": "America/Denver",
    "MOUNTAINSTANDARDTIME(MEXICO)": "America/Chihuahua",
    "MYANMARSTANDARDTIME": "Asia/Rangoon",
    "NCENTRALASIASTANDARDTIME": "Asia/Novosibirsk",
    "NAMIBIASTANDARDTIME": "Africa/Windhoek",
    "NEPALSTANDARDTIME": "Asia/Katmandu",
    "NEWZEALANDSTANDARDTIME": "Pacific/Auckland",
    "NEWFOUNDLANDSTANDARDTIME": "America/St_Johns",
    "NORTHASIAEASTSTANDARDTIME": "Asia/Irkutsk",
    "NORTHASIASTANDARDTIME": "Asia/Krasnoyarsk",
    "PACIFICSASTANDARDTIME": "America/Santiago",
    "PACIFICSTANDARDTIME": "America/Los_Angeles",
    "PACIFICSTANDARDTIME(MEXICO)": "America/Tijuana",
    "PAKISTANSTANDARDTIME": "Asia/Karachi",
    "PARAGUAYSTANDARDTIME": "America/Asuncion",
    "ROMANCESTANDARDTIME": "Europe/Paris",
    "RUSSIATIMEZONE10": "Asia/Srednekolymsk",
    "RUSSIATIMEZONE11": "Asia/Kamchatka",
    "RUSSIATIMEZONE3": "Europe/Samara",
    "RUSSIANSTANDARDTIME": "Europe/Moscow",
    "SAEASTERNSTANDARDTIME": "America/Cayenne",
    "SAPACIFICSTANDARDTIME": "America/Bogota",
    "SAWESTERNSTANDARDTIME": "America/La_Paz",
    "SEASIASTANDARDTIME": "Asia/Bangkok",
    "SAMOASTANDARDTIME": "Pacific/Apia",
    "SINGAPORESTANDARDTIME": "Asia/Singapore",
    "SOUTHAFRICASTANDARDTIME": "Africa/Johannesburg",
    "SRILANKASTANDARDTIME": "Asia/Colombo",
    "SYRIASTANDARDTIME": "Asia/Damascus",
    "TAIPEISTANDARDTIME": "Asia/Taipei",
    "TASMANIASTANDARDTIME": "Australia/Hobart",
    "TOKYOSTANDARDTIME": "Asia/Tokyo",
    "TONGASTANDARDTIME": "Pacific/Tongatapu",
    "TURKEYSTANDARDTIME": "Europe/Istanbul",
    "USEASTERNSTANDARDTIME": "America/Indianapolis",
    "USMOUNTAINSTANDARDTIME": "America/Phoenix",
    "UTC": "Etc/GMT",
    "UTC+12": "Etc/GMT-12",
    "UTC02": "Etc/GMT+2",
    "UTC11": "Etc/GMT+11",
    "ULAANBAATARSTANDARDTIME": "Asia/Ulaanbaatar",
    "VENEZUELASTANDARDTIME": "America/Caracas",
    "VLADIVOSTOKSTANDARDTIME": "Asia/Vladivostok",
    "WAUSTRALIASTANDARDTIME": "Australia/Perth",
    "WCENTRALAFRICASTANDARDTIME": "Africa/Lagos",
    "WEUROPESTANDARDTIME": "Europe/Berlin",
    "WESTASIASTANDARDTIME": "Asia/Tashkent",
    "WESTPACIFICSTANDARDTIME": "Pacific/Port_Moresby",
    "YAKUTSKSTANDARDTIME": "Asia/Yakutsk",
}
In [9]:
from datetime import timedelta
from datetime import timezone

from pytz import timezone as tz
from pytz import all_timezones_set

raw_date = "2018-09-17+06:30" # test timezone

# Parse the raw date string
utc_offset = timedelta(
    hours=int(raw_date[-5:-3]), minutes=int(raw_date[-2:])
)

# Use the first timezone info returned
_timezone_info = next(
    _tz.zone
    for _tz in map(tz, all_timezones_set)
    if datetime.now().astimezone(_tz).utcoffset() == utc_offset
)
print("Processed timezone is:", _timezone_info)

# Get the correct timezone with raw date
date_without_timezone = datetime.strptime(raw_date[:10], "%Y-%m-%d")
print(date_without_timezone)

date_with_timezone = tz(_timezone_info).localize(date_without_timezone)
print(date_with_timezone, type(date_with_timezone))
Processed timezone is: Indian/Cocos
2018-09-17 00:00:00
2018-09-17 00:00:00+06:30 <class 'datetime.datetime'>

Lets look closer at these two dates

In [10]:
date_without_timezone
Out[10]:
datetime.datetime(2018, 9, 17, 0, 0)
In [11]:
date_with_timezone
Out[11]:
datetime.datetime(2018, 9, 17, 0, 0, tzinfo=<DstTzInfo 'Indian/Cocos' +0630+6:30:00 STD>)