TL;DR 🎍

タイムゾーンを扱う Python パッケージを開発した際に調べた情報をまとめておきます。 そもそも Python におけるタイムゾーンは、tzinfo と呼ばれるオブジェクトによって表現されます。 これは、標準ライブラリの datetime.tzinfo クラスのサブクラスとして、標準ライブラリの datetime.timezone や外部ライブラリ pytz によって提供されています。

以下では、これらを使ってどのように tzinfo を取得・使用できるのかを紹介します。 その際、Python スクリプトでは以下のようなインポートを仮定しています。

>>> import pytz
>>> import geocoder
>>> from timezonefinder import TimezoneFinder
>>> from datetime import datetime, timedelta, timezone, tzinfo

このうち、pytz・geocoder・timezonefinder は外部ライブラリです。 ただし、pytz は Python documentation でも紹介されているように、ほとんど標準ライブラリ的な扱いをされていると思われます。

How to get tzinfo

tzinfo を様々な方法で取得する方法をまとめます。 ここでは datetime.tzinfo クラスと区別するため、取得したオブジェクトの変数名は tz として書いています。

タイムゾーン ID から指定する

まずはベーシックな方法から。 例えば、日本のタイムゾーン ID は Asia/Tokyo なので、以下のように tzinfo を取得します。

>>> query = 'Asia/Tokyo'
>>> tz = pytz.timezone(query)
>>> tz
<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>

ちなみに、全てのタイムゾーン ID は以下のように確認することができます。

>>> pytz.all_timezones
['Africa/Abidjan',
 'Africa/Accra',
 'Africa/Addis_Ababa',
 ...,
 'Zulu']

UTC からのオフセット値で指定する

UTC (協定世界時) からの任意のオフセット値を持つ tzinfo を取得したいときは、以下のようにします。

>>> query = 9.0
>>> tz = timezone(timedelta(hours=query))
>>> tz
datetime.timezone(datetime.timedelta(0, 32400))

以下のように文字列に変換することで読みやすい感じになります。

>>> str(tz)
'UTC+09:00'

経度緯度から求める

実際の開発では、ある場所の経度緯度からタイムゾーンを求めたい場合も多いと思います。 その際は、外部ライブラリ timezonefinder を使うことで簡単に取得できます。

>>> query = {'lng': 135, 'lat': 35}
>>> tf = TimezoneFinder()
>>> tz = pytz.timezone(tf.timezone_at(**query))
>>> tz
<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>

ちなみに、検索すると pytzwhere を使用している例を見かけますが、timezonefinder の方が省メモリかつ高速だそうです。 最近の開発もこちらの方が活発っぽいので、これを使っておくのが良さそうです。

場所名から求める

さらに、ある場所名から直接タイムゾーンを求めたい場合、場所名→経度緯度への変換を何かしらの方法で行う必要があります。 ここでは、OpenStreetMap からジオコーディングが可能な geocoder を使った例を紹介します。

>>> query = '名古屋市'
>>> g = geocoder.osm(query)
>>> lnglat = {'lng': g.lng, 'lat': g.lat}
>>> tf = TimezoneFinder()
>>> tz = pytz.timezone(tf.timezone_at(**lnglat))
>>> tz
<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>

他にも、Google Maps API で API キーを取得して、Time Zone API を叩くといった方法もあります。

>>> import googlemaps
>>> client = googlemaps.Client(key)
>>> result = client.timezone((lat, lng))
>>> tz = result['timeZoneId']

UTC を指定する

最後に、UTC は各ライブラリで特別な方法で指定します。

>>> tz = pytz.utc # also: pytz.UTC or timezone.utc
>>> tz
<UTC>

How to use tzinfo

取得した tzinfo (tz) の実際の使い方をまとめます。 まず、上でどの手法で取得した tzdatetime.tzinfo のサブクラスであることを確認しておきましょう。

>>> isinstance(tz, tzinfo)
True

日付に応じたタイムゾーンを求める

夏時間を採用する地域では、日付によってタイムゾーン名と UTC からのオフセット値が変わります。 tzinfo では以下のメソッドで datetime.datetime オブジェクトを与えることで、日付に応じた値の取得が可能です。 まずはタイムゾーン名から。

>>> tz = pytz.timezone('Europe/Amsterdam')
>>> tz.tzname(datetime(2019, 1, 1))
'CET'

>>> tz.tzname(datetime(2019, 8, 1))
'CEST'

次に UTC オフセット値。 こちらは、datetime.timedelta オブジェクトによって表現されます。

>>> tz.utcoffset(datetime(2019, 1, 1))
datetime.timedelta(0, 3600) # UTC+01:00

>>> tz.utcoffset(datetime(2019, 8, 1))
datetime.timedelta(0, 7200) # UTC+02:00

タイムゾーンを持つ日付を扱う

Python の日付は、datetime.datetime に tzinfo を指定しない場合はタイムゾーンを持たない (timezone-naive な) オブジェクト、指定した場合はタイムゾーンを持つ (timezone-aware な) オブジェクトになります。 例えば、以下のように datetime オブジェクトを作成した場合は timezone-naive となります。

>>> dt_naive = datetime(2019, 1, 1)
datetime.datetime(2019, 1, 1, 0, 0) # timezone-naive

timezone-aware なオブジェクトは、以下のように localize メソッドに timezone-naive なオブジェクトを与えてあげることで作成します。 オブジェクトを表示すると、tzinfo が含まれていることが分かります。

>>> dt_aware = tz.localize(dt_naive)
>>> dt_aware
datetime.datetime(2019, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Amsterdam' CET+1:00:00STD>)

ちなみに、timezone-aware → timezone-naive の変換は、以下の通りです。

>>> dt_aware.replace(tzinfo=None)
datetime.datetime(2019, 1, 1, 0, 0)

タイムゾーンの変換

timezone-aware なオブジェクトは、astimezone メソッドで他のタイムゾーンを持つオブジェクトに変換することができます。 例えば、あるタイムゾーンでの時刻が UTC で何時なのかを調べたい場合は、以下のようにします。

>>> dt_aware.astimezone(pytz.utc)
datetime.datetime(2018, 12, 31, 23, 0, tzinfo=<UTC>)

ちなみに、引数に何も与えないと、マシンのローカルタイムゾーンに変換されるようです。

>>> dt_aware.astimezone()
datetime.datetime(2019, 1, 1, 8, 0, tzinfo=datetime.timezone(datetime.timedelta(0, 32400), 'JST'))

References