在 Python/Django 中从城市获取时区

Posted

技术标签:

【中文标题】在 Python/Django 中从城市获取时区【英文标题】:Get Timezone from City in Python/Django 【发布时间】:2013-05-06 12:14:23 【问题描述】:

使用pytz,我可以获得如下时区列表:

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

鉴于我从用户那里获得 Country 和 City 字段,我该如何确定城市的时区?

【问题讨论】:

您的意思是要搜索特定城市的时区数据库并获取其时区?还是您只想要(在这种情况下)Europe/Zurich 的时区? 我想搜索特定城市的时区数据库并获取其时区 相关:How to get a time zone from a location using latitude and longitude coordinates? 【参考方案1】:

pytz 是 IANA 时区数据库(Olson 数据库)的封装。它不包含将世界上任意城市映射到其所在时区的数据。

您可能需要一个地理编码器,例如 geopy,它可以使用各种网络服务将一个地方(例如,城市名称)转换为其坐标(纬度、经度):

from geopy import geocoders # pip install geopy

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

鉴于城市的纬度、经度,可以使用tz_world, an efele.net/tz map / a shapefile of the TZ timezones of the world 例如通过postgis timezone db 或pytzwhere 找到其时区:

import tzwhere

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

还有一些网络服务允许将(纬度、经度)转换为时区,例如askgeo、geonames,请参阅Timezone lookup from latitude longitude。

作为@dashesy pointed out in the comment,geopy也可以找到时区(从1.2开始):

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

GeoNames also provides offline data 允许直接从其名称获取城市的时区,例如:

#!/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)))

输出

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

【讨论】:

我想已经过去了一段时间,现在看来geopy本身可以找到时区:g.timezone(g.geocode('Singapore').point) @dashesy 是的,它现在就像答案中已经提到的其他在线服务一样。 .timezone() method is available for 6 months now 离线数据的唯一缺点是需要大量存储空间,对于那些使用 hobby-dev 或亚马逊免费层的人来说,这可能不起作用,即使免费的最大 redis 内存是 30MB redis 云 这是 colab 上的完整 python 3 版本colab.research.google.com/drive/…【参考方案2】:

这里提出了很多可能的解决方案,但设置起来都有些乏味。

为了让下一个遇到这个问题的人更快,我从 Will Charlton 那里拿了一个,并用它制作了一个快速的 Python 库:https://pypi.python.org/pypi/whenareyou

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

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

这将为您提供一个 pytz 对象(示例中为tz),因此您可以以 Python 方式使用它。

它使用谷歌 API 将夏令时计算留给 pytz,每个城市只调用一次,其余时间离线进行 LRU 会缓存请求,因此您不应轻易达到 API 限制 也应该适用于任何地址或任何 google 地图可以理解的内容

【讨论】:

这看起来是一个非常有趣的模块,但是当我今天尝试它时它返回错误。我只是使用自述文件中的示例。 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 看来我需要购买 google 高级计划才能访问 gmap 数据! @LasseSchuirmann 即使你的例子给了我错误ModuleNotFoundError: No module named 'dateutil'如果我安装python-dateutil它给了我UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 2098: character maps to &lt;undefined&gt;我真的很喜欢你的程序的想法,但遗憾的是它似乎不起作用。跨度> @BenjaminK 嘿,对不起,这是几年前的事了:/ 我没有收到您的错误,但现在看来 google API 不再允许匿名请求了。如果您想修复它,请查看github.com/aerupt/whenareyou,我会检查任何更改并在需要时执行发布。 @BenjaminK 尝试 git 的最新版本,我刚刚合并了一些可以帮助你的东西。 (我还没有彻底测试过,这些天我没有太多时间做开源的东西:/)【参考方案3】:

我认为您需要手动搜索您要查找的城市的时区数据库:

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)

(这只是一个快速而肮脏的解决方案,例如它不会尝试仅匹配时区的城市部分 - 尝试搜索 Europe,它会匹配子字符串,不搜索大小写 -不敏感等)

【讨论】:

它不适用于大多数城市。城市数量远大于时区数量。 @JFSebastian OP 写道“我想在时区数据库中搜索特定城市”,因此我将解决方案限制在pytz 提供的时区数据库中的城市. OP 写道 “鉴于我从用户那里获得 Country 和 City 字段,我该如何确定城市的时区?” 您认为可能性有多大用户给出的城市名称也是时区名称的一部分? @J.F.Sebastian 因此我在 cmets 中的问题是:“您的意思是要搜索时区数据库...?”【参考方案4】:

这个想法是使用geopy 的地理编码器之一找到给定城市(或州)的纬度/经度坐标,并从地理编码器中获取适当的时区。例如:

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

如果地理编码器没有生成时区,您可以使用timezonefinder 将时区归因于给定的纬度/经度坐标。

【讨论】:

据我所知,timezonefinder 仅适用于经纬度。并且 geopy 已关闭(如 IST 7 月 1 日下午 2 点),因此无法注册用户名。有人有替代方案吗?谢谢。 @NaveenKumar: GeoNames 据我所知,网络服务正在工作;不过不确定其他地理编码器。 是的,它可能正在工作,但我没有要使用的用户名。当我尝试一些虚拟用户名时出现 HTTP 错误,并且 geopy 的网站已关闭,我无法创建用户名。 @NaveenKumar:geopy 只是一个包;见Geopy Is Not a Service。您需要一个特定地理编码服务的帐户。【参考方案5】:

没有简单的方法可以做到这一点,这是不幸的。 Geonames 记录了每个城市的列表及其时区名称。 这将是一个很好的选择,但您必须围绕它解析和构建自己的数据库,这样您就可以随时轻松地从国家/城市对中找到时区。

【讨论】:

【参考方案6】:

我最终通过使用 requests 的几个 Google API 调用并通过 JSON 解析解决了这个问题。我能够在没有 API 密钥的情况下做到这一点,因为我永远不会达到他们的使用限制。

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

我希望这会有所帮助。

【讨论】:

【参考方案7】:

@robertklep 的方法可能有效。

但这是使用 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

【讨论】:

它应该可以通过支持的城市列表非常小。 那里找不到很多城市...伦敦很容易,但其他城市根本没有列出...

以上是关于在 Python/Django 中从城市获取时区的主要内容,如果未能解决你的问题,请参考以下文章

获取iOS时区中的城市和国家/地区列表

如何在 Flutter 中使用 OpenWeather API 的时区属性获取任何城市/国家的当前时间?

“美国/太平洋”时区的 Python Django 时区转换时间不正确

在 php 中从 EDT 转换为 GMT+05.30 时区以在 mysql 数据库中输入时间戳

使用时区城市坐标和当地时间进行时区转换[重复]

24个时区的代表城市