Pythonの日付や時間に関する取り扱いについて解説します。
この記事では、3つのモジュールを解説します。
1つ目は標準で利用できるモジュール、2つ目はdateutilのモジュール、3つ目はcalendarモジュールになります。
この記事で解説すること
- 標準モジュールについて解説
- dateutilモジュールについて解説
- calendarモジュールについて解説
実際にサンプルを作って実行し、エラーや注意する点についても解説しています。
標準で利用できる日付や時間のモジュールについて
datetimeモジュールには下記のオブジェクトがあり、日付と時間の他にもタイムゾーンなどを取り扱います。
オブジェクト | 内容 |
datetime | 日時を取り扱う |
date | 日付を取り扱う |
time | 時間を取り扱う |
timedelta | 時間の差を取り扱う |
timezone と tzinfo | タイムゾーン |
Datetimeの使い方
datetimeは、日付と時間を扱います。
まずはdatetimeで利用できる、引数について説明します。
下記がdatetimeで利用する引数になり、時間以降は任意で指定することができます。
datetime(年, 月, 日, [時, [分, [秒, [マイクロ秒, [タイムゾーン]]]]])
マイクロ秒とタイムゾーンについては、ここでは使い方を省いた書き方を説明します。
from datetime import datetime
dt = datetime(2021, 8, 3, 19, 50, 50)
print(dt)
print(type(dt))
1行目で、datetimeオブジェクトをインポートします。
これで、datetimeを利用する準備が整いました。
3行目でdatetimeを呼び出し、年月日と時間を指定しますが、今回は2021年8月3日 19時50分50秒を設定しています。
5行目のprintについては、コンソールに表示するために利用しているだけです。
6行目はどの様な戻り値が返ってくるか、表示させています。
結果は下記の様になります。
2021-08-03 19:50:50
<class 'datetime.datetime'>
datetimeの中のdatetime関数かなって思ったのですが、コード確認するとクラスなんですね。
dateの使い方
dateは日付を取り扱うオブジェクトになります。
dateで利用できる引数は、下記になります。
date(年, 月, 日)
下記が日付を取り扱うサンプルになります。
from datetime import date
d = date(2021, 8, 3)
print(d)
print(type(d))
1行目でdatetimeの中のdateをインポートします。
ここまでは、datetimeの時と同じで3行目でdateを呼び出しています。
引数のところで説明した通り、年月日を指定しています。
5行目と6行目はdatetimeで説明しているので省略します。
結果は下記になります。
2021-08-03
<class 'datetime.date'>
datetimeでは、戻り値がdatetime.datetimeだったのですが、dateの場合はdatetime.dateになっています。
timeの使い方
timeは時間を取り扱うオブジェクトになります。
timeで利用できる引数は下記になります。
time([時, [分, [秒, [マイクロ秒, [タイムゾーン]]]]])
タイムゾーンの指定方法は、後ほど説明しています。
第4引数はミリ秒ではなく、マイクロ秒なので注意してください。
マイクロ秒で指定できるのは、6桁の数字になります。
6桁を超えると下記のエラーが発生します。
ValueError: microsecond must be in 0..999999
実際の動かしてみたサンプルになります。
from datetime import time
t = time(10, 10, 59, 999999)
print(t)
print(type(t))
1行目で、datetimeの中のtimeをインポートしています。
これで、datetimeやdateと同じ様にtimeを使う準備が整いました。
3行目でtimeを呼び出しています。
5行目と6行目はdatetimeで説明しているので省略します。
結果は下記になります。
10:10:59.999999
<class 'datetime.time'>
マイクロ秒が9が6桁としていしていますがエラーにはなりません。
エラーになるのは7桁目からになります。
他にもtimeを全部省略した場合、どの様な動きをするのか試してみました。
from datetime import time
t = time()
print(t)
下記が結果になります。
00:00:00
timedeltaの使い方
timedeltaは、単純な日付の加算や減算ができるわけではありません。
カンタンに式で表すと下記の様ような扱い方はできません。
できない
結果 = 日付 + 日付
結果 = 日付 ー 日付
では何ができるのかと言うと、週や日、時分秒などを使って、足し引きすることができます。
サンプルで確認するとわかりやすいです。
from datetime import timedelta
td = timedelta(days=1.0, hours=24)
print(td)
print(td.days)
print(td.seconds)
print(td.total_seconds())
print(type(td))
1行目で、timedeltaを使う準備をしています。
3行目では、引数のdaysとhoursを指定しています。
どちらも単位は違いますが、1日になります。
5行目はオブジェクトです。
6行目では、daysを利用することで日数を取得することができます。
7行目では、秒を取得しており、
8行目のtotal_secondsでは、合算した秒を取得しています。
結果は、下記になります。
2 days, 0:00:00 # tdの結果
2 # td.daysの結果
0 # td.secondsの結果
172800.0 # td.total_seconds()の結果
<class 'datetime.timedelta'> # 型
timedeltaの引数で、加算されているのがわかります。
daysで1日、horusで1日なので、2行目のdaysは2となります。
3行目の0は、秒になる端数が無いため0になります。
4行目では、合算した時刻を秒として表示しています。
total_daysやtotal_hoursとか関数があるのかなって思ったのですが、ありませんでした。
ちょっと使い道が少ないオブジェクトだなって思いました。
timezoneとtzinfoの使い方
timezone単体では利用することができません。
timezoneを利用するには、timedeltaも利用する必要があります。
tzinfoもタイムゾーン関連のオブジェクトで、単体での使い道はほとんどなく、datetimeやtimeなどを利用して使います。
とりあえず、使ってみることにしました。
下記は、エラーになります。
from datetime import timezone, timedelta, tzinfo, datetime
tz = timezone(timedelta(hours=9))
tzi = tzinfo(tz)
dt = datetime(2021, 8, 1, 20, 20, 20, tzinfo=tzi)
print(tzi)
print(dt)
print(type(tz))
コードの説明すると、4行目のtzinfoの引数はtimezoneです。
datetimeの中に、tzinfoの引数を指定することができるので、コード的に問題ないと思っていました。
しかし下記のエラーが発生します。
Traceback (most recent call last):
File "/path/to/file/timezone_sample.py", line 8, in <module>
print(dt)
NotImplementedError: a tzinfo subclass must implement utcoffset()
8行目のprint(dt)を呼び出した所で、エラーが出ました。
エラーを見ての通り、tzinfoの中にutcoffsetのサブクラスを継承していないからエラーだよ!ってことです。
Pythonの公式サイトの通り、クラス作っていろいろ試してみましたが、使いづらい上に挙動がよくわかりませんでした。
とりあえず、下記で動いたので説明します。
from datetime import timezone, timedelta, datetime
EST = timezone(timedelta(hours=-5), 'EST')
dt = datetime(2021, 8, 4, 20, 51, 0, tzinfo=EST)
print(datetime.now(EST))
print(dt.utcnow())
print(dt.now())
print(dt.tzname())
最初のエラーが出たやつは、日本時間を取得するようにしていましたが、timezoneの時間を変えても時間が変わりませんでした。
そのため、ESTのアメリカ東部の時間で確認しています。
3行目のtimezoneでアメリカ東部のタイムゾーンの設定をしています。
4行目のdatetimeで日時を作成していて、tzinfoに3行目で作成した値を入れています。
結果としては下記になります。
2021-08-04 07:13:31.010499-05:00
2021-08-04 12:13:31.010675
2021-08-04 21:13:31.010680
EST
結果の1行目は、現時刻のアメリカ東部の時間帯を返しています。
2行目のutcnowの結果って、同じくアメリカ東部の時間を返すんじゃ無いの?って思いましたが、違う時間を返しています。
dt.nowでは日本時間を返しており、dt.tznameではESTを返しているので、なぜutcnowではアメリカ東部の時間を返さないのかよくわかりません。
単純にdatetimeで作成とか関係なしに、utcの現在の時間を返しているだけなのかもしれません。
そう考えるとなっとくできます。
強力な拡張モジュールのdateutilについて
Python標準のdatetimeモジュールは扱いづらい部分がありますが、それを強力に拡張して使いやすくしたのがdateutilになります。
インストール
datetuilのインストールを、pipコマンドを利用してインストールします。
$ pip install python-dateutil
Collecting python-dateutil
Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
|████████████████████████████████| 247 kB 2.2 MB/s
Collecting six>=1.5
Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, python-dateutil
Successfully installed python-dateutil-2.8.2 six-1.16.0
anacondaやpypy、minicondaとかで入れている場合は、pipを使ってインストールしないでください。
各Pythonの専用のインストールがあるため、不具合が起きる可能性があります。
anacondaでのインストール方法もとりあえず残しておきます。
$ conda install -c conda-forge python-dateutil
直接ファイルをダウンロードしたい方は、公式サイトからダウンロードしてください。
relativedeltaを使った日の加算と減算
日付の加算と減算を行ってみます。
from datetime import datetime
from dateutil.relativedelta import relativedelta
NOW = datetime.now()
tomorrow = NOW+relativedelta(days=1)
yesterday = NOW+relativedelta(days=-1)
print(tomorrow)
print(yesterday)
1行目は通常通り、pythonの標準ライブラリのdatetimeを呼び出しています。
2行目が今回インストールした拡張モジュールを呼び出している部分です。
relativedeltaの引数でdaysに1または−1を指定します。
NOWにrelativedeltaを足すことで、加算と減算を行っています。
結果は、下記の通りになりました。
2021-08-06 20:46:03.038762
2021-08-04 20:46:03.038762
NOW-relativedeltaをするとどうなるのか気になったので試してみました。
from datetime import datetime
from dateutil.relativedelta import relativedelta
NOW = datetime.now()
tomorrow = NOW-relativedelta(days=1)
yesterday = NOW-relativedelta(days=-1)
print(tomorrow)
print(yesterday)
5行目と6行目のNOW-に変更しています。
結果は下記になります。
2021-08-04 20:49:25.923770
2021-08-06 20:49:25.923770
tomorrowとyesterdayの値が、符号を変えることで逆転しました。
relativedeltaを使った月初と月末の日を取得する方法
calendarモジュールを利用することでも月初や月末を取得することができますが、relativedeltaを使った取得の仕方を紹介します。
まずは、月初の取得の仕方です。
from datetime import date
from dateutil.relativedelta import relativedelta
d = date(2021, 2, 10)
d2 = date(2021, 6, 22)
d3 = date(2021, 8, 31)
dr = d+relativedelta(day=1)
dr2 = d2+relativedelta(day=1)
dr3 = d3+relativedelta(day=1)
print(dr)
print(dr2)
print(dr3
4から6行目で、各日付を作成しています。
relatiedeltaの引数でday=1を指定することで、月初を取得することができます。
daysではなくてdayであることに注意
次に月末を取得してみます。
from datetime import date
from dateutil.relativedelta import relativedelta
d = date(2021, 2, 10)
d2 = date(2021, 6, 22)
d3 = date(2021, 8, 1)
dr = d+relativedelta(day=31)
dr2 = d2+relativedelta(day=31)
dr3 = d3+relativedelta(day=31)
print(dr)
print(dr2)
print(dr3)
relatiedeltaの引数でday=31を指定することで、月末を取得することができます。
parseを使った日付の使い方
parseを使うことで、文字列を日付のdatetimeの型に変換することができます。
from dateutil.parser import parse
ps = parse('2021-08-05')
ps2 = parse('20210805')
print(ps)
print(type(ps))
print(ps2)
parseの引数に文字列の日付を入れるだけで、カンタンに日付の型にしてくれます。
わざわざ年月日などを分ける必要がないので便利ですね。
結果は下記になります。
2021-08-05 00:00:00
<class 'datetime.datetime'>
2021-08-05 00:00:00
ps2の書き方の20210805でも認識しますね。
使い方を間違うとバグの元になりそうですが、間違わなければかなり便利です。
繰り返しの日付を作成できるrruleモジュール
Todoとかの繰り返しの日付を作るのに便利なモジュールです。
from datetime import datetime
from dateutil.rrule import DAILY, rrule
start_day = datetime.now()
rule = rrule(DAILY, count=5, dtstart=start_day)
print(list(rule))
2行目で、rruleとDAILYをインポートします。
繰り返しの頻度として、DAILYの他にも下記の種類があります。
繰り返し頻度
- YEARLY
- MONTHLY
- WEEKLY
- HOURLY
- MINUTELY
- SECONDLY
Calendarモジュールについて
テキストによるカレンダーの表示やHTMLによるカレンダーの表示を行うことができます。
デフォルトの標準で入っているので、インストールとか不要です。
テキスト型のカレンダーを表示
簡単にテキスト型のカレンダーを表示することができます。
import calendar
print(calendar.month(2021, 8))
1行目でcalendarをインポートします。
calendar.monthを使って2021年8月のカレンダーをテキストで表示することができます。
August 2021
Mo Tu We Th Fr Sa Su
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
正直、コンソール画面でテキストで表示できるから何ってなります。
しかもデフォルトで月曜日から始まっています。
日曜日から始めたい場合は、setfirstweeekdayを利用します。
import calendar
calendar.setfirstweekday(6)
print(calendar.month(2021, 8, l=0))
3行目にsetfirstweekday(6)を追加していますが、6が日曜日から始める番号になります。
下記の結果から、日曜日から始まったのがわかります。
August 2021
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
曜日の番号を取得
calendarを利用することで、日付から曜日の番号を取得することができます。
取得するためには、weekdayを利用します。
weekdayの引数は下記になります。
weekday(年, 月, 日)
実際に下記のサンプルを実行してみます。
import calendar
week_num = calendar.weekday(2021, 8, 8)
print(week_num)
2021年8月8日の曜日は、日曜日になります。
下記が結果になります。
6
6が返って来ているので、日曜日の番号が返ってきています。
下記はPythonの曜日の番号になります。
番号 | 曜日 |
0 | 月曜日 |
1 | 火曜日 |
2 | 水曜日 |
3 | 木曜日 |
4 | 金曜日 |
5 | 土曜日 |
6 | 日曜日 |
下記の様に配列として取り扱うと、曜日をマッピングすることができます。
import calendar
week_num = calendar.weekday(2021, 8, 8)
week_name = ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日']
print(week_name[week_num])
結果は、日曜日と返ってきます。
日曜日
英語の表記で良いなら、calendar.day_nameにすると配列を作る必要がないです。
Calendarによる月末の取得
月末を取得するには、monthrangeを利用します。
import calendar
cal = calendar.monthrange(2021, 8)
cal2 = calendar.monthrange(2021, 2)
cal3 = calendar.monthrange(2020, 2)
print(cal, cal2, cal3)
monthrangeを利用すると、月に初めが何曜日から始まっていて月の日数を同時に返します。
結果は下記になります。
(6, 31) (0, 28) (5, 29)
2020年はうるう年なので、29日まであります。
問題なく月の日数を取得できているのがわかります。
配列で取得するの嫌だよって方は、変数の入れ方を変えればカンタンに取得できます。
import calendar
_, cal = calendar.monthrange(2021, 8)
_, cal2 = calendar.monthrange(2021, 2)
_, cal3 = calendar.monthrange(2020, 2)
print(cal, cal2, cal3)
このようにすることで、cal、cal2、cal3の値に31と28と29が入ってきます。
まとめ
Pythonで日付関連を操作してみましたが、PHPでいうCarbonやNode.jsなどのMomentの様に使い勝手がいい感じなモジュールはありませんでした(調査不足?)。
拡張モジュールやCalendarを利用することで、日付周りを強力にサポートすることができるので慣れなんだと思っています。
誰か作ってくれないかなって思いつつ、慣れていくしかないですね。