18

Using pytz, I am able to get a list of timezones like so:

>>> from pytz import country_timezones
>>> print(' '.join(country_timezones('ch')))
Europe/Zurich
>>> print(' '.join(country_timezones('CH')))
Europe/Zurich

Given that I am getting both Country and City fields from the user, how can I go about determining the timezone for the city?

MrFuppes
  • 11,829
  • 3
  • 17
  • 46
super9
  • 26,033
  • 36
  • 110
  • 168
  • You mean you want to search the timezone database for a specific city and get its timezone? Or do you just want the timezone of (in this case) `Europe/Zurich`? – robertklep May 12 '13 at 08:58
  • I want to search the timezone database for a specific city and get its timezone – super9 May 12 '13 at 09:43
  • related: [How to get a time zone from a location using latitude and longitude coordinates?](http://stackoverflow.com/q/16086962/4279) – jfs Dec 29 '15 at 01:11

7 Answers7

25

pytz is a wrapper around IANA Time Zone Database (Olson database). It does not contain data to map an arbitrary city in the world to the timezone it is in.

You might need a geocoder such as geopy that can translate a place (e.g., a city name) to its coordinates (latitude, longitude) using various web-services:

from geopy import geocoders # pip install geopy

g = geocoders.GoogleV3()
place, (lat, lng) = g.geocode('Singapore')
# -> (u'Singapore', (1.352083, 103.819836))

Given city's latitude, longitude, it is possible to find its timezone using tz_world, an efele.net/tz map / a shapefile of the TZ timezones of the world e.g., via postgis timezone db or pytzwhere:

import tzwhere

w = tzwhere()
print w.tzNameAt(1.352083, 103.819836)
# -> Asia/Singapore

There are also web-services that allow to convert (latitude, longitude) into a timezone e.g., askgeo, geonames, see Timezone lookup from latitude longitude.

As @dashesy pointed out in the comment, geopy also can find timezone (since 1.2):

timezone = g.timezone((lat, lng)) # return pytz timezone object
# -> <DstTzInfo 'Asia/Singapore' LMT+6:55:00 STD>

GeoNames also provides offline data that allows to get city's timezone directly from its name e.g.:

#!/usr/bin/env python
import os
from collections import defaultdict
from datetime import datetime
from urllib   import urlretrieve
from urlparse import urljoin
from zipfile  import ZipFile

import pytz # pip install pytz

geonames_url = 'http://download.geonames.org/export/dump/'
basename = 'cities15000' # all cities with a population > 15000 or capitals
filename = basename + '.zip'

# get file
if not os.path.exists(filename):
    urlretrieve(urljoin(geonames_url, filename), filename)

# parse it
city2tz = defaultdict(set)
with ZipFile(filename) as zf, zf.open(basename + '.txt') as file:
    for line in file:
        fields = line.split(b'\t')
        if fields: # geoname table http://download.geonames.org/export/dump/
            name, asciiname, alternatenames = fields[1:4]
            timezone = fields[-2].decode('utf-8').strip()
            if timezone:
                for city in [name, asciiname] + alternatenames.split(b','):
                    city = city.decode('utf-8').strip()
                    if city:
                        city2tz[city].add(timezone)

print("Number of available city names (with aliases): %d" % len(city2tz))

#
n = sum((len(timezones) > 1) for city, timezones in city2tz.iteritems())
print("")
print("Find number of ambigious city names\n "
      "(that have more than one associated timezone): %d" % n)

#
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
city = "Zurich"
for tzname in city2tz[city]:
    now = datetime.now(pytz.timezone(tzname))
    print("")
    print("%s is in %s timezone" % (city, tzname))
    print("Current time in %s is %s" % (city, now.strftime(fmt)))

Output

Number of available city names (with aliases): 112682

Find number of ambigious city names
 (that have more than one associated timezone): 2318

Zurich is in Europe/Zurich timezone
Current time in Zurich is 2013-05-13 11:36:33 CEST+0200
Community
  • 1
  • 1
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • 6
    I think some time has passed, it seems now `geopy` itself can find timezone: `g.timezone(g.geocode('Singapore').point)` – dashesy Mar 11 '15 at 00:06
  • @dashesy yes, it is now like other already mentioned online services in the answer. [`.timezone()` method is available for 6 months now](https://github.com/geopy/geopy/commit/b524a7a91cd81d64402829a1332e68154267c91a) – jfs Mar 11 '15 at 00:28
  • the only downside of offline data it takes good amount of storage space , and for those using hobby-dev or free tier of amazon this might not work, even the max redis memory available for free is 30MB on redis cloud – Ciasto piekarz Sep 22 '17 at 17:50
  • here's a fully python 3 version on colab https://colab.research.google.com/drive/1-QL_qQVfFGGZ4_0xk6912VusOvgqw4_c – Harry Moreno Oct 18 '18 at 22:58
2

I think you're going to need to manually search the timezone database for the city you're looking for:

from pytz import country_timezones, timezone

def find_city(query):
    for country, cities in country_timezones.items():
        for city in cities:
            if query in city:
                yield timezone(city)

for tz in find_city('Zurich'):
    print(tz)

(that's just a quick-and-dirty solution, it for instance doesn't try to match only the city-part of a timezone – try searching for Europe, it does substring matches, doesn't search case-insensitive, etc.)

robertklep
  • 174,329
  • 29
  • 336
  • 330
  • 1
    it doesn't work for most cities. Number of cities is much larger than number of timezones. – jfs May 14 '13 at 08:42
  • @J.F.Sebastian OP wrote *"I want to search the timezone database for a specific city"*, so I limited my solution to cities in the timezone database provided by `pytz`. – robertklep May 14 '13 at 08:49
  • OP wrote *"Given that I am getting both Country and City fields from the user, how can I go about determining the timezone for the city?"* How likely do you think a user gives the city name that is also a part of a timezone name? – jfs May 14 '13 at 08:59
  • @J.F.Sebastian hence my question in the comments: *"You mean you want to search the timezone database...?"*. – robertklep May 14 '13 at 09:38
2

there have been a lot of possible solutions proposed here and they're all a bit tedious to set up.

To make things quicker for the next person with this problem, I took the one from Will Charlton and made a quick python library out of it: https://pypi.python.org/pypi/whenareyou

from whenareyou import whenareyou
tz = whenareyou('Hamburg')
tz.localize(datetime(2002, 10, 27, 6, 0, 0))

Gets you datetime.datetime(2002, 10, 27, 6, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>) .

This gets you a pytz object (tz in the example) so you can use it pythonicly.

  • It uses the google API
  • Leaves daylight savings calculation to pytz, only one call per city, rest happens offline
  • LRU caches the requests so you shouldn't hit the API limit easily
  • Should also work with any address or anything google maps understands
  • 1
    This looks like a quite interesting module, but when I try it today it returns error. I was just using your example from README file. 50 latlong = cached_json_get( 51 LONG_LAT_URL.format(quote_plus(address)) ---> 52 )['results'][0]['geometry']['location'] 53 54 return get_tz(latlong['lat'], latlong['lng']) IndexError: list index out of range – Ruxi Zhang Jun 28 '19 at 18:59
  • Looks like I need to purchase google premium plan to have access to gmap data! – Ruxi Zhang Jun 28 '19 at 19:28
  • @LasseSchuirmann Even your example gave me the error `ModuleNotFoundError: No module named 'dateutil'` if I install `python-dateutil` it gives me `UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 2098: character maps to `I really liked the idea of your program but sadly it seems to not work. – BenjaminK Jun 16 '20 at 17:46
  • @BenjaminK hey, sorry this was several years ago :/ I don't get your error, however it seems that the google API by now doesn't allow anonymous requests anymore. If you're looking to fix it, please check out https://github.com/aerupt/whenareyou , I'll be available to review any changes and perform a release if needed. – Lasse Schuirmann Jun 17 '20 at 20:10
  • @BenjaminK try the latest version from git, I just merged something that could help you. (I haven't tested it thoroughly, I don't have much time for open source stuff these days :/) – Lasse Schuirmann Jun 19 '20 at 07:06
  • I received error: `ImportError: cannot import name '_normalize_host' from 'requests.packages.urllib3.util.url'` – Ricky Levi Jul 05 '20 at 07:45
1

There is not trivial way of doing this, which is unfortunate. Geonames records a list of every city, along with its time zone name. This would be a god pick, but you will have to parse and build your own database around this, so you can easily find at any moment the time zone from a country/city pair.

Steve K
  • 9,731
  • 4
  • 36
  • 38
1

I ended up solving this with a couple Google API calls using requests and parsing through the JSON. I was able to do it without API keys because I'm never going to hit their usage limits.

https://gist.github.com/willcharlton/b055885e249a902402fc

I hope this helps.

Will Charlton
  • 652
  • 7
  • 9
1

The idea is to find lat/long coordinates for given city (or state) with one of geopy's geocoders, and get the appropriate time zone from the geocoder. Ex:

from datetime import datetime, timezone
from geopy import geocoders

# get the location by using one of the geocoders.
# GeoNames has a free option.
gn = geopy.geocoders.GeoNames(username='your-account-name')    
loc = gn.geocode("California, USA")

# some geocoders can obtain the time zone directly.
# note: the geopy.timezone object contains a pytz timezone.
loc_tz = gn.reverse_timezone(loc.point)

# EXAMPLE: localize a datetime object
dt_UTC = datetime(2020, 11, 27, 12, 0, 0, tzinfo=timezone.utc)
dt_tz = dt_UTC.astimezone(loc_tz.pytz_timezone)
print(dt_tz, repr(dt_tz))   
# 2020-11-27 04:00:00-08:00 
# datetime.datetime(2020, 11, 27, 4, 0, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)

If the geocoder doesn't yield a time zone, you can use timezonefinder to attribute a time zone to given lat/long coordinates.

MrFuppes
  • 11,829
  • 3
  • 17
  • 46
-1

@robertklep's approach probably works.

But here's another possible approach using astral - https://pypi.python.org/pypi/astral/0.5

>>> import datetime
>>> from astral import Astral

>>> city_name = 'London'  # assuming we retrieve this from user input

>>> a = Astral()
>>> a.solar_depression = 'civil'

>>> city = a[city_name]   # creates the city object given the city name above

>>> print('Information for %s/%s\n' % (city_name, city.country))
Information for London/England

>>> timezone = city.timezone
>>> print('Timezone: %s' % timezone)
Timezone: Europe/London
Calvin Cheng
  • 32,676
  • 29
  • 109
  • 162