Python3でのdatetime、timezoneの使い方について簡単にまとめてみました。タイムゾーンについては、pytzパッケージを使うこともできますが、標準のdatetime.timezoneもあるようです。icalendarパッケージを使ったりすると、これがpytzに依存しているので、pytzを使ってもいいとなりそうですが、標準のパッケージだけで何とかしたいという場合もあるでしょう。
簡単なサンプルプログラムを作って動かして見ていたら、そこそこの時間をかけてしまいました。個人的には、整理できてよかったです。解説をつけるのは少し大変そう。
ポイントとしては、datetime.datetime.now()やdatetime.datetime()でdatetimeオブジェクトを作成する場合は、タイムゾーンを指定した方が良さそうだということです。指定がない場合はLOCALEの環境に依存するようになるので、手元の日本語環境では自動でAsia/Tokyoのタイムゾーンとなっているようでした。
サンプルプログラムは下記になります。少しずつ確認したいことを追加していったら、結構長くなりました…
#!/usr/bin/env python # -*- coding: utf-8 -*- from datetime import datetime, date, timedelta, timezone import pytz import re # タイムゾーン (Python 3.2 以降で追加された, 時間差での指定が必要) tz_jst = timezone(timedelta(hours=+9), 'JST') # pytzによるタイムゾーン (timezoneと同じく抽象基底クラスtzinfoの実装) pytz_jst = pytz.timezone('Asia/Tokyo') # 現在時刻 current_time = datetime.now() # タイムゾーンはNoneになる current_time_utc = datetime.now(timezone.utc) # +09:00(JST)での現在時刻 current_time_tz_jst = datetime.now(tz_jst) # Asia/Tokyo(pytz利用)での現在時刻 current_time_pytz_jst = datetime.now(pytz_jst) # 時刻の表示。フォーマットを指定しないなら str() が使える。 print("current_time:" + str(current_time)) print("current_time_utc:" + str(current_time_utc)) print("current_time_tz_jst:" + str(current_time_tz_jst)) print("current_time_pytz_jst:" + str(current_time_pytz_jst)) # タイムゾーンの表示 print("current_time.tzname():" + str(current_time.tzname())) print("current_time_utc.tzname():" + str(current_time_utc.tzname())) print("current_time_tz_jst.tzname():" + str(current_time_tz_jst.tzname())) print("current_time_pytz_jst.tzname():" + str(current_time_pytz_jst.tzname())) # タイムスタンプの表示 print("current_time.timestamp():" + str(current_time.timestamp())) print("current_time_utc.timestamp():" + str(current_time_utc.timestamp())) print("current_time_tz_jst.timestamp():" + str(current_time_tz_jst.timestamp())) print("current_time_pytz_jst.timestamp():" + str(current_time_pytz_jst.timestamp())) # 文字列への変換 print('datetime.strftime()') print(' current_time, current_time_utc, current_time_tz_jst, current_time_pytz_jst') for t in (current_time, current_time_utc, current_time_tz_jst, current_time_pytz_jst,): dt_str = datetime.strftime(t, '%Y-%m-%d %H:%M:%S') print("datetime.strftime(t, '%Y-%m-%d %H:%M:%S'):" + dt_str) # datetime.strftime()でのタイムゾーン指定 print("datetime.strftime(current_time_tz_jst, '%z'):" + datetime.strftime(current_time_tz_jst, '%z')) print("datetime.strftime(current_time_tz_jst, '%Z'):" + datetime.strftime(current_time_tz_jst, '%Z')) # ISO8601 基本形式の方が文字数が少ない。拡張形式は視認性を高めたフォーマット dt_str = datetime.strftime(current_time_utc, '%Y%m%dT%H%M%SZ') # UTC時刻にはZをつける print("ISO8601 基本形式 current_time_utc:" + dt_str) dt_str = datetime.strftime( current_time_utc, '%Y-%m-%dT%H:%M:%SZ') # UTC時刻にはZをつける print("ISO8601 拡張形式 current_time_utc:" + dt_str) dt_str = datetime.strftime(current_time_tz_jst, '%Y%m%dT%H%M%S%z') print("ISO8601 基本形式 current_time_tz_jst:" + dt_str) dt_str = datetime.strftime(current_time_tz_jst, '%Y-%m-%dT%H:%M:%S%z') print("ISO8601 拡張形式 current_time_tz_jst:" + '{0}:{1}'.format(dt_str[:22], dt_str[22:])) # 時刻の情報を取得 print("current_time.year:" + str(current_time.year)) print("current_time.month:" + str(current_time.month)) print("current_time.day:" + str(current_time.day)) print("current_time.hour:" + str(current_time.hour)) print("current_time.minute:" + str(current_time.minute)) print("current_time.second:" + str(current_time.second)) print("current_time.microsecond:" + str(current_time.microsecond)) # 0: 月, 1: 火, 2: 水, 3: 木, 4: 金, 5: 土, 6: 日 print("current_time.weekday():" + str(current_time.weekday())) # 時間の計算 minutes, seconds も使えます after_a_week = current_time + timedelta(weeks=1) before_10_days = current_time - timedelta(days=10) after_10_hours = current_time + timedelta(hours=10) print("after_a_week:" + str(after_a_week)) print("before_10_days:" + str(before_10_days)) print("after_10_hours:" + str(after_10_hours)) # 2時間30分後の時刻データを作って、差分を確認 start_time = current_time end_time = current_time + timedelta(hours=2, minutes=30) duration = end_time - start_time # type(duration) is datetime.timedelta duration_minutes = int(duration.seconds / 60) print("duration_minutes=end_time-start_time: " + str(duration_minutes) + ' = ' + str(end_time) + ' - ' + str(start_time)) # 文字列からの変換 dt_str = '2018-12-30 12:34:56' dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') print("2018-12-30 12:34:56 -> '%Y-%m-%d %H:%M:%S':" + str(dt)) dt_iso8601_utc_str = '20181230T123456Z' # 最後のZをUTCに変換してstrptimeで%Zを使うとタイムゾーンの情報がつかない dt_str = dt_iso8601_utc_str.replace('Z', '+0000') dt = datetime.strptime(dt_str, '%Y%m%dT%H%M%S%z') print("20181230T123456Z -> %Y%m%dT%H%M%S%z':" + str(dt)) print("dt.tzname():" + str(dt.tzname())) dt_str = '2018-12-30T12:34:56+09:00' # '20181230T123456+0900'にしてから処理が簡単 # dt_iso8601_str = dt_str.replace('-', '').replace(':', '') でも良い dt_iso8601_str = re.sub('[-:]', '', dt_str) dt = datetime.strptime(dt_iso8601_str, '%Y%m%dT%H%M%S%z') print("2018-12-30T12:34:56+09:00 -> 20181230T123456+0900 -> '%Y%m%dT%H%M%S%z':" + str(dt)) print("dt.tzname():" + str(dt.tzname())) # datetime.datetimeオブジェクトの作成, locale依存をなくすにはtzinfoはつけた方が良い dt = datetime(2018, 12, 30, tzinfo=timezone.utc) # 年月日は必須 print("datetime(2018, 12, 30, tzinfo=timezone.utc):" + str(dt)) dt = datetime(2018, 12, 30, 12, 34, 56, tzinfo=tz_jst) # 時、分、秒も指定可能 print("datetime(2018, 12, 30, 12, 34, 56, tzinfo=tz_jst):" + str(dt)) # datetime.datetimeからdatetime.dateへの変換 date_obj = date(dt.year, dt.month, dt.day) print("date_obj:" + str(date_obj)) # タイムゾーンの変更 print('current_time_tz_jst.astimezone(timezone.utc)') current_time_astimezone_utc = current_time_tz_jst.astimezone(timezone.utc) print("current_time_astimezone_utc.tzname():" + str(current_time_astimezone_utc.tzname())) print("current_time_astimezone_utc:" + str(current_time_astimezone_utc)) # 時刻情報に影響を与えずにタイムゾーンを変更するには replace()を使う current_time_tz_replace_utc = current_time_tz_jst.replace(tzinfo=timezone.utc) print("current_time_tz_replace_utc.tzname():" + str(current_time_tz_replace_utc.tzname())) print("current_time_tz_replace_utc:" + str(current_time_tz_replace_utc))
これを、sample.pyという名前で保存したとします。pytzパッケージが必要なので、インストールしてから実行します。個人的にはvenvをよく使っているので、その手順だと次のようになります。venv/bin/activate でPython仮想環境を有効にして、deactivate でPython仮想環境を抜けます。
$ python3 -m venv venv $ . venv/bin/activate (venv)$ pip install pytz (venv)$ python sample.py (venv)$ TZ=UTC python sample.py (venv)$ deactivate
TZ=UTCをつけて実行すると、タイムゾーンを指定しない場合の処理結果がどうなるか、わかるはずです。参考までに。
さて、TZ=UTCをつけない場合の実行結果は下記のようになります。コードと結果を見比べると、いろいろとわかってきます。datetime.datetime.now()で作成したdatetime.datetimeオブジェクトのtimestamp()の値は、タイムゾーンを指定してもしなくても同じになるのですね。ただ、datetime.datetime.tzname()でタイムゾーンの値がとれないと、他のシステムとか、icalendarフォーマットのファイルを読み込んで処理をしたりするときに、何かと不便になりそうなので、タイムゾーンは指定しておいた方が良さそう。
current_time:2018-12-30 14:42:14.228792 current_time_utc:2018-12-30 05:42:14.228799+00:00 current_time_tz_jst:2018-12-30 14:42:14.228802+09:00 current_time_pytz_jst:2018-12-30 14:42:14.228802+09:00 current_time.tzname():None current_time_utc.tzname():UTC current_time_tz_jst.tzname():JST current_time_pytz_jst.tzname():JST current_time.timestamp():1546148534.228792 current_time_utc.timestamp():1546148534.228799 current_time_tz_jst.timestamp():1546148534.228802 current_time_pytz_jst.timestamp():1546148534.228802 datetime.strftime() current_time, current_time_utc, current_time_tz_jst, current_time_pytz_jst datetime.strftime(t, '%Y-%m-%d %H:%M:%S'):2018-12-30 14:42:14 datetime.strftime(t, '%Y-%m-%d %H:%M:%S'):2018-12-30 05:42:14 datetime.strftime(t, '%Y-%m-%d %H:%M:%S'):2018-12-30 14:42:14 datetime.strftime(t, '%Y-%m-%d %H:%M:%S'):2018-12-30 14:42:14 datetime.strftime(current_time_tz_jst, '%z'):+0900 datetime.strftime(current_time_tz_jst, '%Z'):JST ISO8601 基本形式 current_time_utc:20181230T054214Z ISO8601 拡張形式 current_time_utc:2018-12-30T05:42:14Z ISO8601 基本形式 current_time_tz_jst:20181230T144214+0900 ISO8601 拡張形式 current_time_tz_jst:2018-12-30T14:42:14+09:00 current_time.year:2018 current_time.month:12 current_time.day:30 current_time.hour:14 current_time.minute:42 current_time.second:14 current_time.microsecond:228792 current_time.weekday():6 after_a_week:2019-01-06 14:42:14.228792 before_10_days:2018-12-20 14:42:14.228792 after_10_hours:2018-12-31 00:42:14.228792 duration_minutes=end_time-start_time: 150 = 2018-12-30 17:12:14.228792 - 2018-12-30 14:42:14.228792 2018-12-30 12:34:56 -> '%Y-%m-%d %H:%M:%S':2018-12-30 12:34:56 20181230T123456Z -> %Y%m%dT%H%M%S%z':2018-12-30 12:34:56+00:00 dt.tzname():UTC 2018-12-30T12:34:56+09:00 -> 20181230T123456+0900 -> '%Y%m%dT%H%M%S%z':2018-12-30 12:34:56+09:00 dt.tzname():UTC+09:00 datetime(2018, 12, 30, tzinfo=timezone.utc):2018-12-30 00:00:00+00:00 datetime(2018, 12, 30, 12, 34, 56, tzinfo=tz_jst):2018-12-30 12:34:56+09:00 date_obj:2018-12-30 current_time_tz_jst.astimezone(timezone.utc) current_time_astimezone_utc.tzname():UTC current_time_astimezone_utc:2018-12-30 05:42:14.228802+00:00 current_time_tz_replace_utc.tzname():UTC current_time_tz_replace_utc:2018-12-30 14:42:14.228802+00:00
ISO8601の基本形式・拡張形式は良く使うので、datetime.datetimeオブジェクトとの相互変換について、どうするかも確認してあります。汎用的に作成するのはちょっと面倒そうですが、UTC, Asia/Tokyo, JST, Japan だけをサポートするなら、難しくはなさそうです。