datetimeの使い方『備忘録の決定版』

Pythonで日時について取り扱いたいと思ったとき、それはいろいろな状況があるはとは思いますが、基本としては次のような場合ではないでしょうか。もしくは次のような状況が解決できるなら、それを応用すればなんとかなるのではないでしょうか。

ということで、以下を中心に取り扱います。

  • 今日は何日?
  • いま何時?
  • 現在の日時
  • グリニッジの日時は?
  • 時刻の「差」を扱いたい
  • 日付の足し算、引き算がしたい
  • 時刻の足し算、引き算がしたい
  • 日付、時刻、日時から文字列へ
  • 文字列から日付、時刻、日時へ

標準ライブラリdatetime

日付(年月日)、時刻(時分秒)、日時(年月日時分秒)を扱う場合には、Pythonには標準ライブラリとしてdatetimeが利用可能です。

まず知っておくべき事として、datetimeには以下の4つがあります。

  • datetime.date
  • datetime.time
  • datetime.datetime
  • datetime.timedelta

日付を使うdate、時刻を扱うtime、そして日時を扱うdatetime、最後に時間差を扱うtimedeltaです。知りたいこと、手に入れたいことにプラスしてそれに隠れた概念である時間差の4点セットですね。

このdatetimeライブラリを使って日時(日付や時間・時刻)の処理ができるほか、日時と文字列を相互に変換するstrftime()メソッド、様々な形式の文字列から時刻へと変換するstrptime()メソッドがあります。

またtimedelta のおかげで引き算や足し算などの演算も可能で、例えば、10日前とか1ヶ月後の日付、や33分後の時刻などを簡単に手に入れることができます。

datetimeオブジェクト

まずはdatetimeオブジェクトから説明します。datetimeオブジェクトは、日付(年月日)と時刻(時分秒、マイクロ秒)の情報を持つオブジェクトです。従ってyear、month、day、hour、minute、second、microsecondの値を持っていて、日常生活で馴染みのある形式(microsecondは馴染みがあるかな?)でアクセスすることが可能です。

今日は何日?

今日という日付を中心に解説しますが、最終目標としてはdatetime.dateの理解です。

今日という日付を手に入れるには、以下のようにします。

today = datetime.date.today()

文字通りtoday()が用意されています。手に入ったtodayをすこし詳しく見てみましょう。

(LearningPython) MacBookPro:07.datetime $ python
Python 3.8.1 (default, Jan  9 2020, 03:42:29)
[Clang 11.0.0 (clang-1100.0.20.17)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> today = datetime.date.today()
>>> today
datetime.date(2020, 11, 6)
>>>

ソースコードとコマンドラインからの実行という形では無く、あえて対話モードを使ったのは、最後の結果のdatetime.date(2020, 11, 6)の表示を楽に手に入れたかったからです。

これで逆に、日付ってどうやって手に入れるかが想像ついたと思います。

2001-01-01 の日付を手に入れたい場合は

datetime.date(2001, 1, 1)

とすれば良いです。

datetime.date(year=2020, month=12, day=25)

という風に冗長に指定することでも可能です。この冗長な指定はdate()にはあまり意味がありませんが、このあと登場するtimedeltaでは恩恵にあずかれるかと思います。

オブジェクトの形のまま眺めていてもどうにもならないので、それぞれの値を取り出してみたいと思います。

サンプル datetime_date.py

import datetime

today = datetime.date.today()
print(today)

print(today.year)
print(today.month)
print(today.day)

実行

(LearningPython) MacBookPro:07.datetime $ python datetime_date.py
2020-11-06
2020
11
6
(LearningPython) MacBookPro:07.datetime $

自然な形で取り出せました。year、month、dayはすべて全てintです。ですので、ちょっと頑張りが必要ではありますが、今の時点で前の日や次の日を作り出すことも可能ですね。

でも月の初めの1日の前の日って、月も前の月で、28、29、30、31日のどれなのかを判断し・・・ああ、月も0月にならないようにするって事か…ってことで、そういう面倒なことはtimedeltaを使えば解決できるので少し待って下さい。

ちなみに、11月には31日はありませんが、それを指定するとエラーとなります。

>>> date1 = datetime.date(year=2020, month=11, day=31)
Traceback (most recent call last):
  File "", line 1, in 
ValueError: day is out of range for month
>>>

いま何時?

続いて現在の時刻を扱ってみます。today()があったのでnow()じゃないかなということで、その通り!ただし、datetime.time.now()じゃないんです。

datetime.datetime.now()

なんです。ということで、この節で説明することはありません。次に進んでください。

現在の日時

前節のとおり現在時刻を知るには、datetimeオブジェクトにあるnow()メソッドを利用します。datetime.now()とするだけで、現在の年・月・日・時・分・秒などを保持しているdatetime オブジェクトが得られます。

実行例
>>> datetime.datetime.now()
datetime.datetime(2020, 11, 6, 22, 22, 41, 127975)
>>>

datetimeは年、月、日、時、分、秒、マイクロ秒で構成されているのが見て取れます。

従ってdateと同様に、自分の思ったdatetimeオブジェクトが欲しければ以下の指定で手に入ります。

datetime.datetime(2020, 11, 6, 22, 22, 41, 127975)

でも秒はまだしもミリ秒って指定するの?って思われるかと思います。そうですよね、それは大変です。意図通りかどうかは別にして、指定が必須なのは年、月、日までで、それ以外は指定しなかった場合には0がデフォルト値として用いられてます。

datetime.datetime(2000, 10, 10)

が最低限の指定です。

さて、datetimeオブジェクトのそれぞれの値を取り出してみます。

サンプル datetime_datetime.py
import datetime

now = datetime.datetime.now()
print(now)

print(now.year)
print(now.month)
print(now.day)
print(now.hour)
print(now.minute)
print(now.second)
print(now.microsecond)
実行
(LearningPython) MacBookPro:07.datetime $ python datetime_datetime.py
2020-11-06 22:32:56.786102
2020
11
6
22
32
56
786102
(LearningPython) MacBookPro:07.datetime $

同様に全てintです。これで値としての操作は可能になりました。

グリニッジの日時は?

さて、これまでは何の疑いも無く時刻を扱ってきましたが、都合よくご自身の手元の時計と一致していたと思います。つまりそれって、ご自身の環境の時刻って事ですね。おそらくは日本標準時だと思われます。

ということは、この世のどこかの時刻が必要となったときにどうするかということも覚えておくべきことの一つといえます。datetime.date のところで前の日計算の忌まわしい例を挙げましたが、自力で9時間戻すだなんて悪夢は是非とも見たくありません。ではtimedelta の登場か?というと、そんなことはありません。

Pythonではタイムゾーンの考え方が取り入れられていて、それを指定することで簡単に手に入ります。お題は勝手にグリニッジにしてしまいましたが、場所はどこでも構いません。グリニッジ、グリニッジと繰り返していますが、実際にプログラマーとして扱う事が多いのはUTCですね。

UTCって何?とか、UTCとGMT(グリニッジ標準時)の違いって?っていうのは、ググって調べてみて下さい。そもそもUTC自体もCoordinated Universal Timeの略でUTCって「あいつ、おかしいんじゃない?」ってあたりからも小ネタが広がりそうですね。

さて話題を元に戻しますと、now()の引数にタイムゾーンを指定します。

>>> import datetime
>>> from datetime import timezone
>>> datetime.datetime.now(timezone.utc)
datetime.datetime(2020, 11, 6, 14, 4, 30, 243971, tzinfo=datetime.timezone.utc)
>>>

結果をみるとなんだか「何年何月何日 何時何分何秒 正しUTCです」というイメージで時刻が得られています。この時刻の扱いをマニュアル等ではaware なdatetime と呼ばれています。

一方で、UTCを手に入れるには9時間ずらせば良いって話もありますね。それを実現しているのがutcnow()です。

>>> utc_now = datetime.datetime.utcnow()
>>> utc_now
datetime.datetime(2020, 11, 6, 15, 8, 18, 871584)
>>>

awareに対してnativeなdatetimeオブジェクトと呼んでいますが、要するにtzinfoがついていないのがそれです。

以下のサンプルでその違いを体験しましょう。

サンプル datetime_utc.py

import datetime
from datetime import timezone
utc_now = datetime.datetime.utcnow()

# now では同一時刻を得ることが難しいので、指定した日時でのdatetimeオブジェクトで実施します

native_date = datetime.datetime(2000, 10, 10, 9, 0, 0)
native_utc_date = datetime.datetime(2000, 10, 10, 0, 0, 0)
if native_date == native_utc_date:
    print(native_date)
    print(native_utc_date)
    print("は、同一時刻です")
else:
    print(native_date)
    print(native_utc_date)
    print("は、残念ながら別時刻です")

JST = datetime.timezone(datetime.timedelta(hours=9))

aware_date = datetime.datetime(2000, 10, 10, 9, 0, 0, tzinfo=JST)
aware_utc_date = datetime.datetime(2000, 10, 10, 0, 0, 0, tzinfo=timezone.utc)

if aware_date == aware_utc_date:
    print(aware_date)
    print(aware_utc_date)
    print("は、同一時刻です")
else:
    print("残念ながら別時刻です")

【お詫び】tzinfo=timezone.jstと書きたいところなのですが、ご不便をおかけして申し訳ありませんが、tzinfoに指定する値はご自身で生成頂く形になり、timedeltaの説明が未済の状況においても使わざるを得ない事を大変遺憾に思いますとともに国民の皆様にお詫び申し上げる次第です。(もう、一体誰得なんだよ…)

実行

(LearningPython) MacBookPro:07.datetime $ python datetime_utc.py
2000-10-10 09:00:00
2000-10-10 00:00:00
は、残念ながら別時刻です
2000-10-10 09:00:00+09:00
2000-10-10 00:00:00+00:00
は、同一時刻です
(LearningPython) MacBookPro:07.datetime $

1つ目のnativeなdatetimeオブジェクトを比較していて等しくないのは、値が違っているので当然の結果ですね。つまりnow()とutcnow()も人間が判断する限りにおいては同じ時刻といえますが、値としてはきっちり9時間ずれているということです。

一方で、awareなdatetimeはタイムゾーン込みのデータになっているため、見た目つまり文字列化すると異なってはいますが、タイムゾーン部分も加味されて比較され、両時刻は等しいと判断されます。

この意味が分かっていないと、色々と迷子になる可能性があります。

時刻の「差」を扱いたい

お待たせ致しました。時間の「差」のお時間です。先ほどすでにtimedeltaがフライングで登場してしまっていましたが、やっとその説明です。これで1月1日の前日とか、閏年の2月28日の翌日とか、tzinfoの値の生成とか、心おきなく可能になります。

早速ですが月またぎの前日をやってみましょう。

>>> import datetime
>>> from datetime import timedelta
>>> one_day = timedelta(days=1)
>>> day_1 = datetime.date(2020, 11, 1)
>>> day_31 = day_1 - one_day
>>> day_31
datetime.date(2020, 10, 31)
>>>

あっさりと10月末日である31日が手に入りました。timedelta() は以下の通り様々な引数がとれます。

datetime.timedelta(days=0seconds=0microseconds=0milliseconds=0minutes=0hours=0weeks=0)

そして、非常に重要な事実として負の値も取ることが出来るため、

one_day = timedelta(days=-1)

とすることも可能です。その場合day_31を求める式は

day_31 = day_1 + one_day

となると説明すれば、とてもこの値がどんな値なのかを素直に把握出来るのではないかと思います。

もう少し深掘りしてみます。1日は24時間ですが、プログラミングしている最中に25時間後になってしまうことに気がついたら、day=1, hours=1だなんて考えたくも無いですよね。大丈夫です。hours=25とすればきちんと考慮してくれます。

>>> td = timedelta(days=1, hours=25)
>>> day_one = datetime.date(2020, 11, 1)
>>> day_one = day_one + td
>>> day_one
datetime.date(2020, 11, 3)
>>>

このように、使う側にやさしく考慮してくれます。ちなみに、以下の様に換算されます。

  • 1 ミリ秒は 1000 マイクロ秒に変換されます。
  • 1 分は 60 秒に変換されます。
  • 1 時間は 3600 秒に変換されます。
  • 1 週間は 7 日に変換されます。

日付の足し算、引き算がしたい

すでに「時刻の差」のところで足し算が登場しましたが、それを見ればおかわりの通り、足し算引き算の文脈でtimedelta()が登場します。そして引き算だけは、日付と日付の引き算が存在し得ます。

  • date2 = date1 + timedelta
  • date2 = date1 – timedelta
  • timedelta = date2 – date1

時刻の足し算、引き算がしたい

日付の足し算、引き算と同じですね。

  • time2 = time1 + timedelta
  • time2 = time1 – timedelta
  • timedelta = time2 – time1

日付、時刻、日時から文字列へ

もうあとは文字列との間を行ったり来たりできれば一人で生きていけそうですね。まずは文字列への変換からです。文字列に変換するといっても、何でも良いから文字列にしたいだけであれば、例えばdateの場合で、

>>> today = datetime.date.today()
>>> today
datetime.date(2020, 11, 8)
>>> print(today)
2020-11-08
>>>

とすれば表示されるように、ようするにtoday.__str__()で文字列化は可能なわけですが、順番がーとか、区切り文字を/あるいは年、月、日としたいとか、とにかく色々とやりたいことがあると思います。

そんな場合には、strftime()を用います。

>>> today.strftime("%Y年%m月%d日")
'2020年11月08日'
>>>

ここで例としてdatetime.dateであるtodayを使っていますが、date, time, datetime オブジェクトは全て strftime(format) メソッドをサポートをしています。

大事なのはformat部分ということになりますが、全てを書き尽くすのは厳しいので、全量はマニュアルに任せます。

指定子意味
%Y西暦で年の4桁表示。
日本人には馴染みがありませんが、%yと小文字を使うと2桁になります。
2020
%m月の2桁表示。
1月〜9月は左に0詰めされます。
01, 02,…09
%d日の2桁表示。
1日〜9日は、左に0詰めされます。
01,02,…09
%H時間の2桁表示。
24時間表記。0時から9時は左に0詰めされます。
00,01,…09
%M分の2桁表示。
0分から9分は左に0詰めされます。
00,01,…09
%S秒の2桁表示。
0秒から9秒は左に0詰めされます。
00,01,…09
%zUTCオフセットを ±HHMM[SS[.ffffff]] の形式で表示します
(オブジェクトがnaiveであれば空文字列)
+0900
%%%を表示します。%

よく登場するのはこのくらいでしょうか。ただ、この機能はtoday.yearなどのように値でアクセス出来た時点で、知らなくてもなんとかなる事実といいますか、datetimeと無関係の変数と絡めて文字列にするときを考えると、そもそもが微妙な立ち位置とも言える内容だったりしますね。

文字列から日付、時刻、日時へ

さて、最後になりますが文字列から日付、時刻、日時を得る方法です。

「文字列に」する場合には、それぞれの値を用いてなんとかなりましたが「文字列から」になると少し面倒くささが増しますね。特にデータとして文字列を手に入れたものの、それを日時として扱う事で価値の出るケース、よくあるケースとしてはDB上のカラム定義が日時になっているケースなどでは、フォーマットを指定することでサクッとオブジェクトになってくれるとありがたいですね。

そんな機能を提供してくれるのがstrptime() メソッドなのですが、実はdatetimeにしかありません。つまり、日付にも時刻にもなりません。常に日時として手に入ります。

>>> import datetime
>>> datetime.datetime.strptime("2020/11/08", "%Y/%m/%d")
datetime.datetime(2020, 11, 8, 0, 0)
>>>

dateで欲しければ、datetime.date()があるのでこれを使います。

>>> datetime.datetime.strptime("2020/11/08", "%Y/%m/%d").date()
datetime.date(2020, 11, 8)
>>>

timeで欲しければ、datetime.time()でですが…上記の例だとあんまりですがそのまま突き進みます。

>>> datetime.datetime.strptime("2020/11/08", "%Y/%m/%d").time()
datetime.time(0, 0)
>>>

まとめ

いかがでしたでしょうか。以下の項目に沿って、それぞれをまとめてみました。

  • 今日は何日?
  • いま何時?
  • 現在の日時
  • グリニッジの日時は?
  • 時刻の「差」を扱いたい
  • 日付の足し算、引き算がしたい
  • 時刻の足し算、引き算がしたい
  • 日付、時刻、日時から文字列へ
  • 文字列から日付、時刻、日時へ

時刻に関する話題としては、Unix Timeとの相互変換というのもあるのですが、datetimeとは別だったり別じゃ無かったり、datetimeとは別の話題も触れたいところなので、別途記事にしたいと思います。