note.nkmk.me

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

Date: 2019-01-21 / tags: Python

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

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

ここでは以下の内容について説明する。

  • naiveなオブジェクトとawareなオブジェクト
  • タイムゾーンを表すtimezoneオブジェクトの生成
  • タイムゾーンを設定したオブジェクトを生成
    • コンストラクタ: datetime()
    • 現在時刻: now(), utcnow()
    • 文字列から変換: strptime(), fromisoformat()
  • タイムゾーンの情報を取得: tzinfo属性, utcoffset()
  • タイムゾーンを変換・削除 ・追加: astimezone(), replace()
    • awareなオブジェクトのタイムゾーンを変換・削除
    • naiveなオブジェクトにタイムゾーンを追加
  • pytzの利用
    • pytzのインストール
    • タイムゾーンを生成: pytz.timezone()
    • pytzを使う注意点: replace()などの使用
    • pytzを使う注意点: サマータイム

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

スポンサーリンク

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

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

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

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

import datetime

dt_naive = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000)

print(dt_naive)
# 2018-12-31 05:00:30.001000

print(dt_naive.tzinfo)
# None

dt_aware = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000,
                             tzinfo=datetime.timezone.utc)

print(dt_aware)
# 2018-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(type(datetime.timezone.utc), 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

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

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

コンストラクタ: datetime()

上の例でもあるように、コンストラクタdatetime()の引数tzinfotimezoneオブジェクト(tzinfoクラスのサブクラス)を指定すると、タイムゾーンが設定されたdatetimeオブジェクトが生成される。

dt_utc = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone.utc)

print(dt_utc)
# 2018-12-31 05:00:30.001000+00:00

print(dt_utc.tzinfo)
# UTC
dt_jst = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone(datetime.timedelta(hours=9)))

print(dt_jst)
# 2018-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)
# 2019-01-20 23:53:16.711783

print(dt_now.tzinfo)
# None

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

UTCの場合。

dt_now_utc = datetime.datetime.now(datetime.timezone.utc)

print(dt_now_utc)
# 2019-01-20 14:53:16.735097+00:00

print(dt_now_utc.tzinfo)
# UTC

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

dt_now_jst = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9)))

print(dt_now_jst)
# 2019-01-20 23:53:16.758037+09:00

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

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

dt_utcnow = datetime.datetime.utcnow()

print(dt_utcnow)
# 2019-01-20 14:53:16.802029

print(dt_utcnow.tzinfo)
# None

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

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

文字列からdatetimeオブジェクトを生成するにはstrptime()を使う。詳細は以下の記事を参照。

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

例えば以下のような文字列は%zを使ってタイムゾーンを含んだdatetimeオブジェクトを生成できる。

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

dt = datetime.datetime.strptime(s, '%Y/%m/%d %H:%M:%S%z')

print(dt)
# 2018-12-31 05:00:30+09:00

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

Python3.7からISO 8601形式の文字列をdatetimeオブジェクトなどに変換するfromisoformat()が追加された。詳細は以下の記事を参照。

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

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

dt = datetime.datetime.fromisoformat(s)

print(dt)
# 2018-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(2018, 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(2018, 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()を使う。タイムゾーンが設定されたawareなオブジェクトか設定されていないnaiveなオブジェクトかによって振る舞いが異なる。

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

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

dt_jst = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000,
                           tzinfo=datetime.timezone(datetime.timedelta(hours=9)))

print(dt_jst)
# 2018-12-31 05:00:30.001000+09:00

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

タイムゾーンを変換するにはastimezone()メソッドを使う。引数にtimezoneオブジェクト(tzinfoクラスのサブクラス)を指定する。

タイムゾーンが考慮された同時刻のdatetimeオブジェクトが生成される。

dt_jst_to_utc = dt_jst.astimezone(datetime.timezone.utc)

print(dt_jst_to_utc)
# 2018-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)
# 2018-12-30 15:00:30.001000-05:00

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

タイムゾーンを置き換えるにはreplace()メソッドを使う。引数tzinfotimezoneオブジェクト(tzinfoクラスのサブクラス)を指定する。

元のdatetimeオブジェクトの日時の値はそのままでtzinfo属性が置き換わったdatetimeオブジェクトが生成される。

dt_jst_to_utc_replace = dt_jst.replace(tzinfo=datetime.timezone.utc)

print(dt_jst_to_utc_replace)
# 2018-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)
# 2018-12-31 05:00:30.001000

print(dt_jst_to_naive.tzinfo)
# None

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

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

dt_naive = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000)

print(dt_naive)
# 2018-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)
# 2018-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)
# 2018-12-31 05:00:30.001000+09:00

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

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

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

dt_naive_to_utc = dt_naive.astimezone(datetime.timezone.utc)

print(dt_naive_to_utc)
# 2018-12-30 20:00:30.001000+00:00

print(dt_naive_to_utc.tzinfo)
# UTC

pytzの利用

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

pytzのインストール

pippip3)でインストールできる。

$ 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(2018, 12, 31, 5, 0, 30, 1000,
                             tzinfo=datetime.timezone.utc)

print(dt_aware)
# 2018-12-31 05:00:30.001000+00:00

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

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

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

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

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

print(dt_aware.replace(tzinfo=eastern))
# 2018-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)))
# 2018-12-31 05:00:30.001000+09:00

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

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

dt_naive = datetime.datetime(2018, 12, 31, 5, 0, 30, 1000)

print(dt_naive)
# 2018-12-31 05:00:30.001000

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

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

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

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

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

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

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

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

print(datetime.datetime.now(jst))
# 2019-01-21 00:00:42.540529+09:00

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

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

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

dt_dst = datetime.datetime(2018, 3, 11, 2, 0, 0, 0)

print(eastern.localize(dt_dst))
# 2018-03-11 02:00:00-05:00

print(eastern.normalize(eastern.localize(dt_dst)))
# 2018-03-11 03:00:00-04:00
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事