cover_python_cookbook

Python Cookbook [Manipulating Dates Involving Time Zones : タイムゾーンを考慮した日付操作] 投稿一覧へ戻る

Tags: Python , Cookbook , datetime , timedelta , Olson , pytz , timezone

Published 2020年4月23日22:37 by T.Tsuyoshi

Problem:

東京でテレワークをしている僕にニューヨークの同僚から電話。
「現地時間(2020年)4月27日午前9時30分から会議をするから、ちゃんと出席してね」。
さて、僕は何時にテレビ会議システムの前にいなきゃダメなんだろう?

Solution:

タイムゾーンが関係するほとんど全ての問題には pytz モジュールが使用できます。


Python3 からは、datetime モジュールで timezone クラスが提供されるようになりました。
また、PEP 431 で解説されているようにタイムゾーンサポートの改善に伴い pytz の使用は推奨されない可能性があります。
ただ、後半述べているような「タイムゾーンを扱う上での戦略」等の基本的な考え方は十分に通用するものです。



このパッケージは、多くの言語やオペレーティングシステムが標準のタイムゾーン情報として採用しているオルソンタイムゾーンデータベース ( Olson time zone database ) を利用しています。


pytz の最も一般的な使用場面は、datetime 日時からローカル日時を取得することでしょう。


ただ、誤解しないでください。


ここでいっている「ローカル日時を取得」とは、タイムゾーン情報を持っている日時オブジェクト (すなわち aware なオブジェクト) を取得する、という意味です。


タイムゾーン情報を持っていない日時オブジェクト (すなわち naive なオブジェクト) 同士で、「ここは12時だから、あそこは前日の23時だ」という意味合いではありません。

from datetime import datetime
from pytz import timezone

d = datetime(2020, 4, 27, 9, 30, 0)
print(d) # → 2020-04-27 09:30:00 (これは naive なオブジェクトでタイムゾーン情報を持っていません)



New York のローカル時間は?

eastern = timezone('America/New_York')
new_d = eastern.localize(d)
print(new_d) # → 2020-04-27 09:30:00-04:00 (これは aware なオブジェクトで、アメリカ東部夏時間2020-4-27 9:30:00、を表しています)



取得したローカル日時を元にして、他のタイムゾーンへ変換することができます。
日本時間を取得してみましょう。

tokyo_d = new_d.astimezone(timezone('Asia/Tokyo'))
print(tokyo_d) # → 2020-04-27 22:30:00+09:00



ここでは astimezone() を利用して、アメリカ東部夏時間から日本時間を取得しています。
ニューヨークで2020年4月27日午前9時30分から行われるテレビ会議に出席するためには、日本にいる私は2020年4月27日午後10時30分に机に向かっていなければいけないようです。


タイムゾーンを考慮した日時オブジェクトで計算を行う際は、サマータイム等の特殊事項も考慮する必要があります。


例えば、2020年のアメリカ東部時間における夏時間は 2020-3-8(Sun)2:00:00 から 2020-11-1(Sun)2:00:00 までとなっています。


タイムゾーンを考慮しない日時オブジェクトでこの時期を含む計算を行うと正確な日時を取得することができません。

from datetime import timedelta

d = datetime(2020, 3, 8, 1, 45)
new_d = eastern.localize(d)
print(new_d) # → 2020-03-08 01:45:00-05:00
sum_d = new_d + timedelta(minutes = 30)
print(sum_d) # → 2020-03-08 02:15:00-05:00



違いますね。


3月8日午前2時からは夏時間に突入して1時間早まりますから、03:15:00 にならなくてはいけません。
このような時には normalize() メソッドが使えます。

sum_d = eastern.normalize(new_d + timedelta(minutes = 30))
print(sum_d) # → 2020-03-08 03:15:00-04:00



タイムゾーンが絡んだ処理でいい思い出を持っている方はほとんどいないのではないでしょうか?


混乱を防ぐための一般的な戦略は、コード内でも、データベースへの保存時でも、日時はすべて UTC で処理をすることです。


すべてが UTC で統一されていればサマータイム等の特殊事情も考慮する必要がありませんから、その時点でまずは通常の演算を行ってしまいます。


そしてその結果に対して、必要に応じてタイムゾーンを適用して必要なローカル日時を取得するようにします。

import pytz

print(new_d) # → 2020-03-08 01:45:00-05:00
utc_d = new_d.astimezone(pytz.utc)
print(utc_d) # → 2020-03-08 06:45:00+00:00

later_utc = utc_d + timedelta(minutes = 30)
print(later_utc.astimezone(eastern)) # → 2020-03-08 03:15:00-04:00



タイムゾーンを扱う上でほぼ常に付きまとう疑問は、「タイムゾーン名は何?」ではないでしょうか。


pytz.country_timezones ディクショナリーに key 名として ISO 3166 の国別コードを指定することでタイムゾーン名を取得することができます。

pytz.country_timezones['JP'] # → ['Asia/Tokyo']
pytz.country_timezones['US']

# ['America/New_York',
# 'America/Detroit',
# 'America/Kentucky/Louisville',
# 'America/Kentucky/Monticello',
# 'America/Indiana/Indianapolis',
# 'America/Indiana/Vincennes',
# 'America/Indiana/Winamac',
# 'America/Indiana/Marengo',
# 'America/Indiana/Petersburg',
# 'America/Indiana/Vevay',
# 'America/Chicago',
# 'America/Indiana/Tell_City',
# 'America/Indiana/Knox',
# 'America/Menominee',
# 'America/North_Dakota/Center',
# 'America/North_Dakota/New_Salem',
# 'America/North_Dakota/Beulah',
# 'America/Denver',
# 'America/Boise',
# 'America/Phoenix',
# 'America/Los_Angeles',
# 'America/Anchorage',
# 'America/Juneau',
# 'America/Sitka',
# 'America/Metlakatla',
# 'America/Yakutat',
# 'America/Nome',
# 'America/Adak',
# 'Pacific/Honolulu']

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)