Pythonで日付を扱う方法

5 min read読了の目安(約4800字

想定読者

  • Pythonで日付を扱うときにdatetime型で扱うのが面倒で文字列操作でプログラムを書いてしまう人
  • Pythonで日付文字列とdatetime型を相互変換するときにdatetime.strptime()とdatetime.strftime()を使っているがどっちを使うか毎回思い出せなくなる人
  • datetime.now()を使ったらPythonが動くサーバーのロケールがUTCで9時間ずれて困った経験のある人

TL;DR

  • 日付はdatetime型で扱うと便利
  • Pythonで日付と時刻の文字列を扱うときはISO8601形式で扱おう
  • PythonでISO8601形式の文字列を扱うときはdatetime.fromisoformat()とdatetime.isoformat()が便利
  • 日付はawareなdatetime型で保持しよう

datetime型の便利なところ

Pythonで日付を扱うとき、datetime型の使い方を知らない人は文字列操作だけで何とかしようとするかもしれません。
文字列のままでも書式次第で比較は可能なので、単純な用途であれば何とかなります。

ただし、日付をdatetime型で保持しておくと、いくつか便利な機能が使えます。
以下に2つほど紹介しましょう。

timedeltaによる時間の加算減算

  • datetime型なら秒、分、時間、日単位で加算減算ができます。
  • for, whileループの中で1日ごとの処理を作る時などに便利です。
from datetime import datetime, timedelta

# 起点となる日付のdatetime型の値を定義する
dt = datetime.fromisoformat("2021-01-02T00:00:00")

# 1秒、1分、1時間、1日を起点となる日付に加算する
a = dt + timedelta(seconds=1)
print(a)  # -> 2021-01-02 00:00:01
b = dt + timedelta(minutes=1)
print(b)  # -> 2021-01-02 00:01:00
c = dt + timedelta(hours=1)
print(c)  # -> 2021-01-02 01:00:00
d = dt + timedelta(days=1)
print(d)  # -> 2021-01-03 00:00:00

# 減算もできる
e = dt - timedelta(seconds=1)
print(e)  # -> 2021-01-01 23:59:59
f = dt - timedelta(minutes=1)
print(f)  # -> 2021-01-01 23:59:00
g = dt - timedelta(hours=1)
print(g)  # -> 2021-01-01 23:00:00
h = dt - timedelta(days=1)
print(h)  # -> 2021-01-01 00:00:00

年,月,日,時,分,秒だけをそれぞれ取り出す

  • datetime型の中には年,月,日,時,分,秒の要素が入っているので個別に取り出すことができます。
  • 月初の1日だけ処理したい、といったプログラムを書く時に便利ですね。
print(dt.year)    # -> 2021
print(dt.month)   # -> 1
print(dt.day)     # -> 2
print(dt.hour)    # -> 0
print(dt.minute)  # -> 0
print(dt.second)  # -> 0

datetime.fromisoformat()を覚えよう

上記のように便利なdatetime型ですが、自分でdatetime型の値を作る場合は色々な方法があります。
よく見るのはdatetime.strptime()を使う方法ですが、他にも方法があります。
個人的にはdatetime.fromisoformat()がおすすめです。

from datetime import datetime

# datetime()に年,月,日,時,分,秒の引数を渡して作る
a = datetime(2021, 1, 2, 0, 0, 0)
print(a)  # -> 2021-01-02 00:00:00

# datetime.strptime()を使う
b = datetime.strptime("2021-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")
print(b)  # -> 2021-01-02 00:00:00

# datetime.fromisoformat()を使う
c = datetime.fromisoformat("2021-01-02T00:00:00")
print(c)  # -> 2021-01-02 00:00:00

ISO8601形式について知ろう

上記で出てきたdatetime.fromisoformat()では、ISO8601形式と呼ばれる日付と時刻を文字列(以下日時文字列)で表記する際の世界統一ルールを使っています。
ISO8601形式であれば日時文字列の表記方法が特定できるため、文字列からdatetime型への変換(以下パース)が簡単です。

datetime.strptime()で日時文字列をパースするとき、文字列によってスラッシュ区切りだったりハイフン区切りだったりで毎回パース用の書式を書くのが面倒ではありませんか?

日時文字列を全てISO8601形式で書くことにしてしまえば、そうした書式を気にすることなくパースできます。

また、datetime型を文字列として出力する場合もdatetime.isoformat()を使うことで行なえます。
もうdatetime.strptime()とdatetime.strftime()のどっちがパース関数でどっちが文字列出力関数なのか悩むことはありません。
日時文字列は基本的に全てISO8601形式で扱うようにしましょう。

from datetime import datetime
# ISO8601形式の文字列からdatetime型に変換
dt = datetime.fromisoformat("2021-01-02T00:00:00")

# datetime型の値をISO8601形式で出力する
print(dt.isoformat())  # -> '2021-01-02T00:00:00'

awareなdatetime型を使おう

Pythonのdatetime型はaware, naiveの2種類があります。
awareはタイムゾーン情報があるdatetime型で、
naiveはタイムゾーン情報がないdatetime型です。

タイムゾーンの話

私達が使っている日時文字列は、場所によって変わることのあるものです。
たとえば
東京の
2021-01-02T00:00:00
は、同時刻のアメリカニューヨークでは
2021-01-01T10:00:00
になります。

すなわち、どこのタイムゾーンの時刻なのか?という情報がないと、異なるタイムゾーンの時刻を比較したり正しく表示したりすることができません。

Linuxのロケールで困る話

この問題は、特にLinuxサーバーで動くPythonでdatetime型を扱うときに困ることが多いです。
Linuxサーバーは自分がどこのタイムゾーンなのか?という情報を保持しており、
サーバー設定によってはこのタイムゾーン設定が日本時間でない場合があります。

このとき、datetime.now()で現在時刻を取得しても、日本時間からズレた時間が取得されてしまいます。

タイムゾーンを明示してawareな現在時刻を取得する

上記のような場合は、常に日本時間であることを明示してdatetime.now()を実行すればサーバーの設定に左右されずにawareなdatetime型が取得できます。
datetime.now()の返り値は小数点以下の秒数が入ってしまうため、
datetime.isoformat()を使う際はtimespec引数に"seconds"を指定しておくと
秒数までだけを表示してくれます。

from datetime import datetime, timedelta, timezone
# 日本時間のタイムゾーンを定義
JST = timezone(timedelta(hours=+9), 'JST')
# Pythonが動くサーバーのタイムゾーン設定に左右されずに日本時間の現在時刻を取得
dt = datetime.now(JST)
# ISO8601形式で表示
print(dt.isoformat())  # -> 2021-04-14T01:57:42.809986+09:00
# 小数点以下の秒数が邪魔なので秒数までを表示する
print(dt.isoformat(timespec="seconds"))  # -> 2021-04-14T01:57:42+09:00

datetime.fromisoformat()でawareなdatetime型を作成する

日時文字列からawareなdatetime型を作成する場合もdatetime.fromisoformat()でOKです。
末尾に日本時間であることを示す+09:00を付け加えるだけです。

また、タイムゾーンがどこなのか分からないawareなdatetime型を扱うときには、とりあえずdatetime.astimezone()を使って日本時間のタイムゾーンに変更しておけば日時文字列を表示する場合も安心です。
書き方も簡単なので、Pythonでdatetime型を扱う場合は、awareなdatetime型を使いつつ、日時文字列を表示する前には必ずJSTタイムゾーンの文字列として表示するようにしておくと、日付で困ることが減ると思います。

from datetime import datetime, timedelta, timezone
# awareなdatetime型を定義
dt = datetime.fromisoformat("2021-01-02T00:00:00+09:00")
print(dt.isoformat())  # -> 2021-01-02T00:00:00+09:00

# あるdatetime型がどのタイムゾーンか分からない場合は明示的にJST時間で表示させる
# 日本時間のタイムゾーンを定義
JST = timezone(timedelta(hours=+9), 'JST')
# 変換元datetimeのタイムゾーン設定に左右されずにタイムゾーンを日本時間にしたdatetime型を取得する
jst_dt = dt.astimezone(JST)
print(dt.isoformat())  # -> 2021-01-02T00:00:00+09:00