こんにちは。投資エンジニアの三年坊主(@SannenBouzu)です。
今回は、Pythonのdatetimeライブラリで日付や時間を操作したい人の疑問に答えます。
Pythonのdatetimeで日付や時間を操作したい。
文字列と相互に変換したり、日付や時間の差を計算するにはどうするんだろう。実務での使用例やハマりどころについても知りたい。
この記事では、エンジニアの実務・大学の研究・趣味で、合わせて5年以上Pythonを使ってきた経験を生かして、Pythonのdatetimeで日付や時間を操作する方法を紹介します。
- Pythonのdatetimeを使って日付や時間の操作をしたい方
- 実務での使用例やハマりどころを知りたい方
日付/時間を扱うPython標準ライブラリdatetimeで使う主なデータ型まとめ
参考:8.1. datetime — 基本的な日付型および時間型 — Python 3.6.5 ドキュメント
datetimeで使う主なデータ型を、実際の使用例とあわせて紹介します。
datetimeをインポートした前提で読み進めてください。Google Colaboratory上で実行して確認しています。
1 | import datetime |
datetime.date:日付
dateオブジェクトは日付(年・月・日)を表します。属性として year, month, dateを持ちます。
1 2 3 4 5 6 7 8 9 | date_last_heisei = datetime.date(2019, 4, 30) print(date_last_heisei) print(type(date_last_heisei)) print(date_last_heisei.year) # 出力結果 2019-04-30 <class 'datetime.date'> 2019 |
today()を使って、実行日当日のdateオブジェクトを生成できます。
1 2 3 4 5 6 7 | date_today = datetime.date.today() print(date_today) print(date_today.month) # 出力結果 2019-01-09 1 |
datetime.time:時間
timeオブジェクトは時刻(時・分・秒・マイクロ秒)を表します。属性として hour, minute, second, microsecond を持ちます。
1 2 3 4 5 6 7 8 9 | time_test = datetime.time(13, 32, 30, 1234) print(time_test) print(type(time_test)) print(time_test.hour) # 出力結果 13:32:30.001234 <class 'datetime.time'> 13 |
引数は省略するとゼロになります。
1 2 3 4 5 6 7 8 9 | time_default = datetime.time() print(time_default) print(time_default.minute) print(time_default.microsecond) # 出力結果 00:00:00 0 0 |
timeオブジェクトでは、now()のようなメソッドを使って現在時刻を取得することはできません。
1 2 3 4 5 6 7 8 9 | datetime.time.now() # 出力結果 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-30-b219250d9b59> in <module>() ----> 1 datetime.time.now() AttributeError: type object 'datetime.time' has no attribute 'now' |
datetime.datetime:日付と時間
datetimeオブジェクトは、dateオブジェクト(年・月・日)とtimeオブジェクト(時・分・秒・マイクロ秒)の情報が入っているオブジェクトです。
属性として year, month, day, hour, minute, second, microsecond を持ちます。
年・月・日の引数は必須で、その他の引数は任意です。
1 2 3 4 5 6 7 8 9 10 11 | datetime_dt = datetime.datetime(2019, 4, 30, 15) print(datetime_dt) print(type(datetime_dt)) print(datetime_dt.year) print(datetime_dt.minute) # 出力結果 2019-04-30 15:00:00 <class 'datetime.datetime'> 2019 0 |
「マイクロ秒」は1,000,000分の1秒です。「ミリ秒」を飛ばして「マイクロ秒」を使うのは少し不思議な感じがしますが。
- 1秒=1,000ミリ秒
- 1ミリ秒=1,000マイクロ秒
now()やtoday()を使って、実行日当日の日付と時刻をdatetimeオブジェクトとして取得できます。
1 2 3 4 5 6 7 8 9 | datetime_now = datetime.datetime.now() print(datetime_now) print(datetime_now.year) print(datetime_now.hour) # 出力結果 2019-01-09 05:24:16.609775 2019 5 |
datetime.timedelta:date, datetimeの比較・引き算で得られる時間差
timedeltaオブジェクトは、2つの日付や時刻の時間差・経過時間を表すオブジェクトです。
属性として days, seconds, microseconds が保持されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | dt1 = datetime.datetime(2019, 1, 10, 13, 45, 12) dt2 = datetime.datetime(2019, 1, 23, 21, 18, 7) td = dt2 - dt1 print(td) print(type(td)) print(td.days) print(td.seconds) # 7 hours 32 minutes 55 seconds = 27,175 seconds print(td.microseconds) # 出力結果 13 days, 7:32:55 <class 'datetime.timedelta'> 13 27175 0 |
dateやdatetimeオブジェクトに対してtimedeltaオブジェクトの足し算・引き算など四則演算も可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def get_datetime_with_delta(dt, days_delta): return dt + datetime.timedelta(days=days_delta) dt1 = datetime.datetime(2019, 1, 10, 13, 45, 12) days_delta = 3 print(dt1) print(get_datetime_with_delta(dt1, days_delta)) d1 = datetime.date(2019, 1, 10) days_delta = 3 print(d1) print(get_datetime_with_delta(d1, days_delta)) # 出力結果 2019-01-10 13:45:12 2019-01-13 13:45:12 2019-01-10 2019-01-13 |
datetime.tzinfo, datetime.timezone:タイムゾーン
2つ以上のタイムゾーンをまたぐ国や地域で日付や時間の比較をするには、タイムゾーンを意識する必要があります。
timezone クラスは tzinfo のサブクラスで、各インスタンスは UTC からの固定されたオフセットで定義されたタイムゾーンを表しています。
1 2 3 4 5 6 7 8 | offset = datetime.timedelta(hours=+9) jst = datetime.timezone(offset) # pytzライブラリを使う場合は pytz.timezone('Asia/Tokyo') print(jst) print(type(jst)) # 出力結果 UTC+09:00 <class 'datetime.timezone'> |
datetimeオブジェクトで実行日当日の日付と時刻を取得するnow()の例では、タイムゾーンを指定していなかったので、実行したマシンの時刻(UTCで朝5時台)が返ってきました。
下のように日本のタイムゾーンを指定して実行すると、日本のタイムゾーンの情報を含んだdatetimeが返ってきます。
1 2 3 4 5 6 7 | datetime_now_tz = datetime.datetime.now(tz=jst) print(datetime_now_tz) print(datetime_now_tz.tzinfo) # 出力結果 2019-01-09 16:07:22.150023+09:00 UTC+09:00 |
Pythonのdatetimeライブラリ:実務での使用例・ハマりどころ
①:strftime()とstrptime()が紛らわしい。strftime()は日付→文字列にフォーマット、strptime()は逆
strftime()は日付・時間→文字列に変換、逆にstrptime()は文字列→日付・時間に変換するメソッドです。
fとp、一文字しか違わず紛らわしいですが、覚え方としてはこのように考えるのがおすすめです。
- fはフォーマットのf。日付・時間を文字列に変換するためのフォーマット(書式)を指定してあげて、時刻を表現する文字列を生成する。
- pはパースのp。文字列をパースして日付・時間に変換する。
parse, pointerなど諸説あるようですが、実用上はstrftime()をきちんと覚えて、strptime()がその逆と分かれば問題ない場合が多いです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | datetime_now_tz = datetime.datetime.now(tz=jst) print(datetime_now_tz) print(type(datetime_now_tz)) # strftime datetime_now_tz_str = datetime_now_tz.strftime('%Y-%m-%dT%H:%M:%S.%f%z') print(datetime_now_tz_str) print(type(datetime_now_tz_str)) datetime_now_tz_str_2 = datetime_now_tz.strftime('%H:%M:%S, %A %B %d, %Y') print(datetime_now_tz_str_2) # 出力結果 2019-01-09 17:00:06.645838+09:00 <class 'datetime.datetime'> 2019-01-09T17:00:06.645838+0900 <class 'str'> 17:00:06, Wednesday January 9, 2019 |
②:strftime()のフォーマットとstr.format()に比べた利点
文字列のstr.format()を使って値を変数を代入していくよりも、strftime()を使った方が効率よく文字列を生成できますし、フォーマットのミスをする可能性も低いです。
私も、strftime()になじみがない頃コードレビューで指摘されて気がつきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # strftime() datetime_now_tz_str = datetime_now_tz.strftime('%Y-%m-%dT%H:%M:%S.%f%z') print(datetime_now_tz_str) print(type(datetime_now_tz_str)) # str.format() datetime_now_tz_str_2 = '{:%Y-%m-%dT%H:%M:%S}.{:06.0f}{:%z}'.format(datetime_now_tz, datetime_now_tz.microsecond, datetime_now_tz) print(datetime_now_tz_str_2) print(type(datetime_now_tz_str_2)) # 出力結果 2019-01-09T17:10:20.204903+0900 <class 'str'> 2019-01-09T17:10:20.204903+0900 <class 'str'> |
実行速度を簡単に検証してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | print(datetime_now_tz) import time LOOP_NUM = 1000000 start1 = time.time() for _ in range(LOOP_NUM): datetime_now_tz_str = datetime_now_tz.strftime('%Y-%m-%dT%H:%M:%S.%f%z') elapsed_time1 = time.time() - start1 print ("elapsed_time1:{0}".format(elapsed_time1) + "[sec]") start2 = time.time() for _ in range(LOOP_NUM): datetime_now_tz_str_2 = '{:%Y-%m-%dT%H:%M:%S}.{:06.0f}{:%z}'.format(datetime_now_tz, datetime_now_tz.microsecond, datetime_now_tz) elapsed_time2 = time.time() - start2 print ("elapsed_time2:{0}".format(elapsed_time2) + "[sec]") # 出力結果 2019-01-09 17:37:45.715118+09:00 elapsed_time1:3.585350513458252[sec] elapsed_time2:7.165010929107666[sec] |
③:ISOフォーマットで出力形式を統一
日付と時刻の表記方法については、ISO 8601という国際規格があり、この国際規格に合わせておくと、APIなどでデータを利用する際もシンプルで分かりやすいのがメリットです。
ISO 8601に準拠した形で時刻を表す文字列を出力してくれるのが、isoformat()というメソッドです。
日付と時間を分ける文字(セパレータ)を指定できます(デフォルトは’T’)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # strftime datetime_now_tz_str = datetime_now_tz.strftime('%Y-%m-%dT%H:%M:%S.%f%z') print(datetime_now_tz_str) print(type(datetime_now_tz_str)) # isoformat datetime_now_tz_str_iso = datetime_now_tz.isoformat() print(datetime_now_tz_str_iso) # isoformat datetime_now_tz_str_iso_2 = datetime_now_tz.isoformat(' ') print(datetime_now_tz_str_iso_2) # 出力結果 2019-01-09T17:57:45.715118+0900 <class 'str'> 2019-01-09T17:57:45.715118+09:00 2019-01-09 17:57:45.715118+09:00 |
業務で「データベースの値を抽出してCSV形式で日次レポートを出力してほしい」と依頼されたことがあったのですが、その際に時刻のフォーマットとしてISOフォーマットが指定されていました。
当時はisoformat()を知らなかったのですが、少し時間を割いて公式ドキュメントを読んでみると、自分のやりたいことを実現してくれる便利な機能がすでに用意されていることもありますね。
Pythonのdatetimeで日付と時間を自由自在に操作して、快適なエンジニア生活を送りましょう。