Python

Pythonの標準モジュールとCalendar、拡張モジュールを使った日付と時間の操作

2021年8月8日

Python日付関連

Pythonの日付や時間に関する取り扱いについて解説します。

この記事では、3つのモジュールを解説します。

1つ目は標準で利用できるモジュール、2つ目はdateutilのモジュール、3つ目はcalendarモジュールになります。

この記事で解説すること

  • 標準モジュールについて解説
  • dateutilモジュールについて解説
  • calendarモジュールについて解説

実際にサンプルを作って実行し、エラーや注意する点についても解説しています。

標準で利用できる日付や時間のモジュールについて

Python標準モジュール

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_daystotal_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拡張モジュール

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を利用することで、日付周りを強力にサポートすることができるので慣れなんだと思っています。

誰か作ってくれないかなって思いつつ、慣れていくしかないですね。

-Python
-