Python3でのdatetime、timezoneの使い方について簡単にまとめてみた

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 だけをサポートするなら、難しくはなさそうです。

同じタグの記事: DateTime
同じタグの記事: Python
同じカテゴリの記事: Program