Python, datetime, pytzでタイムゾーンを設定・取得・変換・削除

Modified: | Tags: Python, 日時処理

Pythonで日時(日付・時刻)を処理するdatetimeモジュールではタイムゾーンを扱うことができる。

UTC(協定世界時)からの時間差を指定して処理するだけならdatetimeモジュールで十分だが、サードパーティライブラリのpytzを導入すると、Asia/Tokyoのような表記を使ったりDST(夏時間、サマータイム)を簡単に扱ったりできる。

datetime, date, timeオブジェクトの基本や、文字列との相互変換については以下の記事を参照。

本記事のサンプルコードでは、以下のようにdatetimeモジュールをインポートしている。

import datetime

naiveオブジェクトとawareオブジェクト

datetimeオブジェクト(日時=日付と時刻)とtimeオブジェクト(時刻)は、naiveとawareの2種類に分類される。以降のサンプルコードではdatetimeオブジェクトを例とする。

タイムゾーンについての情報を格納するtzinfo属性を持っていないのがnaiveなオブジェクトで、持っているのがawareなオブジェクト。

例えば、コンストラクタdatetime()で引数tzinfoを省略するとtzinfo属性がNoneのnaiveなオブジェクトとなり、何らかの値を指定するとawareなオブジェクトとなる。詳細は後述。

dt_naive = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000)
print(dt_naive)
# 2022-12-31 05:00:30.001000

print(dt_naive.tzinfo)
# None

dt_aware = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                             tzinfo=datetime.timezone.utc)
print(dt_aware)
# 2022-12-31 05:00:30.001000+00:00

print(dt_aware.tzinfo)
# UTC

print(type(dt_aware.tzinfo))
# <class 'datetime.timezone'>

タイムゾーンを表すtimezoneオブジェクトの生成

上の例で引数tzinfoに指定したdatetime.timezone.utctzinfoクラスのサブクラスであるtimezoneクラスのオブジェクトで、UTC(協定世界時)を表す。

print(datetime.timezone.utc)
# UTC

print(type(datetime.timezone.utc))
# <class 'datetime.timezone'>

print(issubclass(datetime.timezone, datetime.tzinfo))
# True

timezoneクラスはUTCから±24時間の差分を表すシンプルなクラス。夏時間などの複雑な処理を行いたい場合は後述のpytzを使う。

任意の時差を表すtimezoneオブジェクトは、コンストラクタtimezone()timedeltaオブジェクトを指定して生成する。

tz_jst = datetime.timezone(datetime.timedelta(hours=9))
print(tz_jst)
# UTC+09:00

print(type(tz_jst))
# <class 'datetime.timezone'>

timedeltaオブジェクトについては以下の記事を参照。

デフォルトでは上の例のようにUTC+HH:MMUTC-HH:MMといった名前がつくが、コンストラクタtimezone()の第二引数nameに任意の名前を指定することもできる。

tz_jst_name = datetime.timezone(datetime.timedelta(hours=9), name='JST')
print(tz_jst_name)
# JST

タイムゾーンを設定したawareなオブジェクトを生成

ここでは、タイムゾーンを設定したawareなdatetimeオブジェクトを生成する方法を説明する。既存のオブジェクトのタイムゾーンの変換や削除については後述。

コンストラクタ: datetime()

コンストラクタdatetime()の引数tzinfotimezoneオブジェクトを指定すると、タイムゾーンが設定されたdatetimeオブジェクトが生成される。

dt_utc = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone.utc)
print(dt_utc)
# 2022-12-31 05:00:30.001000+00:00

print(dt_utc.tzinfo)
# UTC
dt_jst = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone(datetime.timedelta(hours=9)))
print(dt_jst)
# 2022-12-31 05:00:30.001000+09:00

print(dt_jst.tzinfo)
# UTC+09:00

現在時刻: now(), utcnow()

現在時刻のdatetimeオブジェクトはdatetime.now()で生成できる。

デフォルトはtzinfo属性がNoneで、タイムゾーンは設定されない。日時自体は実行環境のタイムゾーンの日時となる。

dt_now = datetime.datetime.now()
print(dt_now)
# 2023-10-03 21:12:49.683787

print(dt_now.tzinfo)
# None

引数にtimezoneオブジェクトを指定すると、そのタイムゾーンに変換されtzinfo属性が設定されたdatetimeオブジェクトが生成される。

UTCの場合。

dt_now_utc = datetime.datetime.now(datetime.timezone.utc)
print(dt_now_utc)
# 2023-10-03 12:12:49.689641+00:00

print(dt_now_utc.tzinfo)
# UTC

JST(日本標準時)の場合。+9時間の差分を指定する。この例はJST環境で実行しているので、引数なしの場合と日時の値は同じ。

dt_now_jst = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9)))
print(dt_now_jst)
# 2023-10-03 21:12:49.693979+09:00

print(dt_now_jst.tzinfo)
# UTC+09:00

datetime.utcnow()という関数もある。現在のUTCの日時でtzinfo属性がNonedatetimeオブジェクトが生成される。

dt_utcnow = datetime.datetime.utcnow()
print(dt_utcnow)
# 2023-10-03 12:12:49.697851

print(dt_utcnow.tzinfo)
# None

現在のUTCの日時でtzinfo属性がUTCに設定されたdatetimeオブジェクトは上記のようにdatetime.now()の引数にdatetime.timezone.utcを指定して生成する。

文字列から変換: strptime(), fromisoformat()

文字列からdatetimeオブジェクトを生成するにはstrptime()を使う。

タイムゾーンを表す書式化コードは%z+HHMMまたは-HHMMの形式でUTCとの差分を示す。

s = '2022/12/31 05:00:30+0900'

dt = datetime.datetime.strptime(s, '%Y/%m/%d %H:%M:%S%z')
print(dt)
# 2022-12-31 05:00:30+09:00

print(dt.tzinfo)
# UTC+09:00

ISO 8601形式では末尾に+HH:MMまたは-HH:MMの形式でタイムゾーン情報を含む。fromisoformat()を使って、ISO 8601形式の文字列からタイムゾーンを含むdatetimeオブジェクトを生成できる。

s = '2022-12-31T05:00:30+09:00'

dt = datetime.datetime.fromisoformat(s)
print(dt)
# 2022-12-31 05:00:30+09:00

print(dt.tzinfo)
# UTC+09:00

タイムゾーンの情報を取得: tzinfo属性, utcoffset()

タイムゾーンが設定されたdatetimeオブジェクトは、tzinfo属性とutcoffset()メソッドでタイムゾーンの情報を取得できる。utcoffset()はUTCとの差分(時差)を示すtimedeltaオブジェクトを返す。

dt_utc = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone.utc)
print(dt_utc.tzinfo)
# UTC

print(type(dt_utc.tzinfo))
# <class 'datetime.timezone'>

print(dt_utc.utcoffset())
# 0:00:00

print(type(dt_utc.utcoffset()))
# <class 'datetime.timedelta'>
dt_jst = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone(datetime.timedelta(hours=9)))
print(dt_jst.tzinfo)
# UTC+09:00

print(type(dt_jst.tzinfo))
# <class 'datetime.timezone'>

print(dt_jst.utcoffset())
# 9:00:00

print(type(dt_jst.utcoffset()))
# <class 'datetime.timedelta'>

タイムゾーンを変換・削除 ・追加: astimezone(), replace()

既存のdatetimeオブジェクトのタイムゾーンを変換・削除・追加するにはastimezone()およびreplace()メソッドを使う。

astimezone()メソッドはタイムゾーン情報をもとに日時の値を変換し、replace()メソッドは日時の値はそのままで単にtzinfo属性を置き換える。

awareなオブジェクトのタイムゾーンを変換・削除

UTC+9時間の時差が設定された以下のdatetimeオブジェクトを例とする。

dt_jst = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone(datetime.timedelta(hours=9)))
print(dt_jst)
# 2022-12-31 05:00:30.001000+09:00

print(dt_jst.tzinfo)
# UTC+09:00

タイムゾーンを変換するにはastimezone()メソッドを使う。引数にtimezoneオブジェクトを指定する。タイムゾーンが考慮された同時刻のdatetimeオブジェクトが生成される。

dt_jst_to_utc = dt_jst.astimezone(datetime.timezone.utc)
print(dt_jst_to_utc)
# 2022-12-30 20:00:30.001000+00:00

print(dt_jst_to_utc.tzinfo)
# UTC
dt_jst_to_m5h = dt_jst.astimezone(datetime.timezone(datetime.timedelta(hours=-5)))
print(dt_jst_to_m5h)
# 2022-12-30 15:00:30.001000-05:00

print(dt_jst_to_m5h.tzinfo)
# UTC-05:00

シンプルにタイムゾーンを置き換えるにはreplace()メソッドを使う。引数tzinfotimezoneオブジェクトを指定する。元のdatetimeオブジェクトの日時の値はそのままでtzinfo属性が置き換わったdatetimeオブジェクトが生成される。

dt_jst_to_utc_replace = dt_jst.replace(tzinfo=datetime.timezone.utc)
print(dt_jst_to_utc_replace)
# 2022-12-31 05:00:30.001000+00:00

print(dt_jst_to_utc_replace.tzinfo)
# UTC

astimezone()メソッドの場合は元のオブジェクトと生成されたオブジェクトが同時刻を示すが、replace()メソッドの場合は別の時刻となるので注意。

タイムゾーンの設定を表すtzinfo属性を削除したい場合はreplace()メソッドの引数にNoneを指定すればよい。

dt_jst_to_naive = dt_jst.replace(tzinfo=None)
print(dt_jst_to_naive)
# 2022-12-31 05:00:30.001000

print(dt_jst_to_naive.tzinfo)
# None

naiveなオブジェクトにタイムゾーンを追加

タイムゾーンが設定されていない以下のdatetimeオブジェクトを例とする。

dt_naive = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000)
print(dt_naive)
# 2022-12-31 05:00:30.001000

print(dt_naive.tzinfo)
# None

replace()メソッドの引数tzinfotimezoneオブジェクト(tzinfoクラスのサブクラス)を指定すると、単純にそのタイムゾーンの情報が付与される。

dt_naive_to_utc_replace = dt_naive.replace(tzinfo=datetime.timezone.utc)
print(dt_naive_to_utc_replace)
# 2022-12-31 05:00:30.001000+00:00

print(dt_naive_to_utc_replace.tzinfo)
# UTC
dt_naive_to_jst_replace = dt_naive.replace(
    tzinfo=datetime.timezone(datetime.timedelta(hours=9))
)
print(dt_naive_to_jst_replace)
# 2022-12-31 05:00:30.001000+09:00

print(dt_naive_to_jst_replace.tzinfo)
# UTC+09:00

naiveなオブジェクトからastimezone()メソッドを呼ぶと、システムのローカルなタイムゾーンを前提に変換される。

以下の例はJST(+9時間)の環境で実行したので、タイムゾーンが設定されていないnaiveなオブジェクトもJSTであるという前提で変換されている。

dt_naive_to_utc = dt_naive.astimezone(datetime.timezone.utc)
print(dt_naive_to_utc)
# 2022-12-30 20:00:30.001000+00:00

print(dt_naive_to_utc.tzinfo)
# UTC

pytzの利用

これまでの例のように、単純にUTCからの時差を加えたり引いたりするだけであれば標準ライブラリのdatetimeモジュールを使えば問題ないが、サードパーティライブラリのpytzを導入すると、Asia/Tokyoのような表記を使ったりDST(夏時間、サマータイム)を簡単に扱ったりできる。

pytzのインストール

pip(環境によってはpip3)でインストールできる。

$ pip install pytz

タイムゾーンを生成: pytz.timezone()

pytz.timezone()でタイムゾーンを表すオブジェクトを生成できる。引数には'Asia/Tokyo''US/Eastern'などの文字列を指定する。

import datetime
import pytz

jst = pytz.timezone('Asia/Tokyo')
print(jst)
# Asia/Tokyo

eastern = pytz.timezone('US/Eastern')
print(eastern)
# US/Eastern

生成されたオブジェクトのクラスはdatetime.tzinfoのサブクラスであり、これまでの例のdatetime.timezoneオブジェクトの代わりに引数に指定できるが、使用には注意が必要。

print(type(jst))
# <class 'pytz.tzfile.Asia/Tokyo'>

print(issubclass(type(jst), datetime.tzinfo))
# True

pytzを使う注意点: replace()などでの使用

タイムゾーン情報が設定された以下のawareなオブジェクトを例とする。

dt_aware = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                             tzinfo=datetime.timezone.utc)
print(dt_aware)
# 2022-12-31 05:00:30.001000+00:00

上述のように、astimezone()メソッドの引数にpytz.timezone()で生成したオブジェクトを設定できる。'Asia/Tokyo'といった名称を使って生成できるので非常に便利。

print(dt_aware.astimezone(jst))
# 2022-12-31 14:00:30.001000+09:00

print(dt_aware.astimezone(eastern))
# 2022-12-31 00:00:30.001000-05:00

astimezone()メソッドは問題ないが、replace()メソッドにpytz.timezone()で生成したオブジェクトを設定すると+09:19-04:56のように時差がずれる。

print(dt_aware.replace(tzinfo=jst))
# 2022-12-31 05:00:30.001000+09:19

print(dt_aware.replace(tzinfo=eastern))
# 2022-12-31 05:00:30.001000-04:56

これはpytzが厳格にタイムゾーンを処理しているため。例えば東京のタイムゾーンは1887年から+9:00でそれ以前は+09:19であり、時刻情報がない場合は+09:19が使用される。

以下の記事などを参照。

astimezone()メソッドは時刻情報をもとに変換が行われるため、1887年以降の時刻を変換する場合は+9:00となるが、それ以前は+09:19となる。一方、replace()メソッドはawareなオブジェクトであっても時刻情報を使わずに変換するため、常に+09:19が使われる。

pytzのタイムゾーンを使ってreplace()のような処理を行うには、pytz.timezone()で生成したオブジェクトのlocalize()メソッドを用いる。localize()メソッドの引数にはnaiveなオブジェクトのみ指定可能なので、awareなオブジェクトのtzinfo属性をreplace(tzinfo=None)で削除してから指定する。

print(jst.localize(dt_aware.replace(tzinfo=None)))
# 2022-12-31 05:00:30.001000+09:00

print(eastern.localize(dt_aware.replace(tzinfo=None)))
# 2022-12-31 05:00:30.001000-05:00

naiveなオブジェクトからreplace()メソッドを呼ぶ場合も同様。pytzのタイムゾーンを引数に指定するとタイムゾーンがずれる場合があるので、localize()メソッドを用いる。

dt_naive = datetime.datetime(2022, 12, 31, 5, 0, 30, 1000)
print(dt_naive)
# 2022-12-31 05:00:30.001000

print(dt_naive.replace(tzinfo=jst))
# 2022-12-31 05:00:30.001000+09:19

print(dt_naive.replace(tzinfo=eastern))
# 2022-12-31 05:00:30.001000-04:56

print(jst.localize(dt_naive))
# 2022-12-31 05:00:30.001000+09:00

print(eastern.localize(dt_naive))
# 2022-12-31 05:00:30.001000-05:00

コンストラクタdatetime()の引数tzinfoを指定する場合も同様。コンストラクタの時点では時刻情報が存在しないので+09:19となってしまう。コンストラクタではtzinfoを指定せずnaiveなオブジェクトを生成し、それをlocalize()メソッドの引数に指定すればよい。

print(datetime.datetime(2022, 12, 31, 5, 0, 30, 1000,
                        tzinfo=jst))
# 2022-12-31 05:00:30.001000+09:19

print(jst.localize(datetime.datetime(2022, 12, 31, 5, 0, 30, 1000)))
# 2022-12-31 05:00:30.001000+09:00

datetime.now()の引数にはpytz.timezone()で生成したオブジェクトを指定しても問題ない。

print(datetime.datetime.now(jst))
# 2023-10-03 21:22:22.497686+09:00

pytzを使う注意点: サマータイム

サマータイムの切替時刻にも注意が必要。

localize()だと存在しない時刻を生成する可能性がある。normalize()メソッドを使うと正しく扱われる。

dt_dst = datetime.datetime(2023, 3, 12, 2, 0, 0, 0)

print(eastern.localize(dt_dst))
# 2023-03-12 02:00:00-05:00

print(eastern.normalize(eastern.localize(dt_dst)))
# 2023-03-12 03:00:00-04:00

関連カテゴリー

関連記事