日付・時刻・時間の処理

目的

Pythonでは日付や時刻に関する処理が可能である.また,機械学習やビッグデータの解析の処理には時間がかかるため,処理にかかった時間を計測したり,処理が終了する時間を推定することがある.ここでは,日付・時刻・時間の処理について学ぶ.

説明

datetimeモジュールによる日付と時刻の処理

今日から春分の日までの日数を求める

日付に関する処理にはdatetimeモジュールのdateオブジェクトを使用する.例えば,今日から2021年の春分の日までの日数を求めたければ,以下のようにすればよい.

import datetime
def main():
spring_equinox_day = datetime.date(2021, 3, 20)
today = datetime.date.today()
difference = spring_equinox_day - today
print(f'spring_equinox_day: {spring_equinox_day}')
print(f'today: {today}')
print(f'difference: {difference}\n')
print(f'spring_equinox_day type: {type(spring_equinox_day)}')
print(f'today type: {type(today)}')
print(f'difference type: {type(difference)}')
if __name__ == '__main__':
main()

1行目でdatetimeモジュールをインポートし,5行目でdatetimeモジュールのdateオブジェクトを生成している.この例では,2021年の春分の日である3月20日を引数で指定して,春分の日のdateオブジェクトを作っている.6行目でdatetimeモジュールのdateオブジェクトのメソッドtodayを使用して,今日のdateオブジェクトを生成している.dateオブジェクト同士は簡単な演算ができるようになっており,7行目のように減算を行うことができる.9から11行目でそれぞれのオブジェクトを表示し,14から16行目でそれぞれのオブジェクトの型を表示している.

このプログラムを実行すると,以下のようになり,2021年の春分の日と今日の日付,今日から春分の日までの日数が表示されていることがわかる.また,春分の日と今日の日付はdatetimeモジュールのdateオブジェクトであり,今日から春分の日までの日数はdatetimeモジュールのtimedeltaオブジェクトであることが確認できる.

spring_equinox_day: 2021-03-20
today: 2020-03-23
difference: 362 days, 0:00:00
spring_equinox_day type: <class 'datetime.date'>
today type: <class 'datetime.date'>
difference type: <class 'datetime.timedelta'>

このように,Pythonで日付を扱う際には,datetimeモジュールのdateオブジェクトを使用することが多く,その差はdatetimeモジュールのtimedeltaオブジェクトとして表現される.

春分の日から秋分の日を推定する

次に,春分の日から半年後の日付を求め,秋分の日を推定してみよう.

import datetime
def main():
spring_equinox_day = datetime.date(2021, 3, 20)
half_year_days = 365 // 2
half_year = datetime.timedelta(days=half_year_days)
estimated_autumn_equinox_day = spring_equinox_day + half_year
print(f'spring_equinox_day: {spring_equinox_day}')
print(f'half_year: {half_year}')
print(f'estimated_spring_equinox_day: {estimated_autumn_equinox_day}')
if __name__ == '__main__':
main()

5行目で2021年の春分の日のdateオブジェクトを生成している.6行目で半年の日数を求め,7行目で,キーワード引数daysに半年の日数を指定して,timedeltaオブジェクトを生成している.8行目で,春分の日のdateオブジェクトに半年を表すtimedeltaオブジェクトを加算し,秋分の日を推定している.

このプログラムを実行すると,以下のようになり,推定された2021年秋分の日が表示されている.結果が正しいかどうかは各自で調べて確認してみよう.

spring_equinox_day: 2021-03-20
half_year: 182 days, 0:00:00
estimated_spring_equinox_day: 2021-09-18

このように,timedeltaオブジェクトはコンストラクタによって作成することができ,dateオブジェクトとの演算(加算・減算)に使用できる.timedeltaオブジェクトのコンストラクタの引数は以下の通りであり,日数の他に様々な数を引数で指定してオブジェクトを生成できる.

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

今から美容室を予約した日時までの時間を求める

以上のように,日付を表す際にはdatetimeモジュールのdateオブジェクトを使用した.ここでは,日時を表す際に使用されるdatetimeモジュールのdatetimeオブジェクトの使い方を確認しよう.

例えば,今から美容室を予約した日時までの時間を求めるには,以下のようにすればよい.

import datetime
def main():
salon_schedule = datetime.datetime(2020, 3, 25, 15, 30)
now = datetime.datetime.now()
difference = salon_schedule - now
print(f'salon_schedule: {salon_schedule}')
print(f'now: {now}')
print(f'difference: {difference}\n')
print(f'salon_schedule type: {type(salon_schedule)}')
print(f'now type: {type(now)}')
print(f'difference type: {type(difference)}\n')
if __name__ == '__main__':
main()

5行目でdatetimeモジュールのdatetimeオブジェクトを生成している.ここでは,2020年3月25日15時30分を引数で指定して,美容室を予約した日時を表すdatetimeオブジェクトを生成している.6行目で,datetimeオブジェクトのメソッドnowを使用し,今の日時を表すdatetimeオブジェクトを生成している.7行目でそれらの差を求めている.9行目から11行目で各オブジェクトの内容を,13行目から16行目で各オブジェクトの型を表示している.

このプログラムを実行すると,以下のようになり,今から美容室を予約した日時までの日数や時間が表示されていることがわかる.また,datetimeオブジェクト同士の差は,dateオブジェクト同士の差と同様に,timedeltaオブジェクトとなっていることがわかる.

salon_schedule: 2020-03-25 15:30:00
now: 2020-03-23 03:39:09.167122
difference: 2 days, 11:50:50.832878
salon_schedule type: <class 'datetime.datetime'>
now type: <class 'datetime.datetime'>
difference type: <class 'datetime.timedelta'>

以上のように,datetimeモジュールのdatetimeオブジェクトを使用すると,日時を表現することができる.また,datetimeオブジェクト同士の差はtimedeltaオブジェクトとして表現される.

美容室を予約した日時から夕食の時刻を推定する

例えば,美容室を予約した日時から夕食の日時を推定するには,以下のようにすればよい.

import datetime
def main():
salon_schedule = datetime.datetime(2020, 3, 25, 15, 30)
salon_time = datetime.timedelta(hours=1, minutes=30)
shopping_time = datetime.timedelta(hours=2, minutes=30)
estimated_dinner_time = salon_schedule + salon_time + shopping_time
print(f'salon_schedule: {salon_schedule}')
print(f'salon_time: {salon_time}')
print(f'shopping_time: {shopping_time}')
print(f'estimated_dinner_time: {estimated_dinner_time}')
if __name__ == '__main__':
main()

5行目で美容室を予約した日時を表すdatetimeオブジェクトを生成している.美容室で髪を切るのに1時間半かかり,その後2時間半買い物をしてから夕食をとることにし,夕食の時間を推定しよう.6行目で,引数で1時間半と指定し,美容室でかかる時間を表すtimedeltaオブジェクトを生成している.7行目で,引数で2時間半と指定し,買い物にかかる時間を表すtimedeltaオブジェクトを生成している.8行目で,美容室を予約した日時に美容室と買い物にかかる時間を加算し,夕食の時刻を推定している.

このプログラムを実行すると,以下のようになり,推定された夕食の時刻が表示される.

salon_schedule: 2020-03-25 15:30:00
salon_time: 1:30:00
shopping_time: 2:30:00
estimated_dinner_time: 2020-03-25 19:30:00

このように,timedeltaオブジェクトはdateオブジェクトだけでなく,datetimeオブジェクトとの演算(加算・減算)にも使用できる.

timeモジュールによる処理時間の計測

次に,timeモジュールを使用した処理時間の計測方法を確認しよう.

処理時間の計測

1から1000000までの整数を2乗した結果を表示し,その処理にかかった時間を計測してみよう.

import time
def main():
num = 1000000
begin_time = time.time()
for i in range(num):
square = (i+1) ** 2
print(f'square of {(i+1):7}: {square:13}')
end_time = time.time()
processing_time = end_time - begin_time
print(f'processing time: {processing_time:.3g}[s]')
if __name__ == '__main__':
main()

5行目で繰り返し回数を設定し,7行目から9行目で繰り返し処理をしている.8行目で整数値の2乗を計算し,9行目で結果を表示している.

1行目でtimeモジュールをインポートし,6行目でtimeモジュールのtime関数を使用して,ある時点から測った秒数を取得し,begin_timeという名前をつけている.10行目でも同様に,ある時刻から測った秒数を取得し,end_timeという名前をつけている.11行目で終了時刻end_timeから開始時刻begin_timeを引くことで,処理にかかった時間を求めている.12行目で処理にかかった時間を表示している.

このプログラムを実行すると,以下のように,処理結果の後に処理にかかった時間が表示される.

square of 1: 1
square of 2: 4
square of 3: 9
...
square of 999998: 999996000004
square of 999999: 999998000001
square of 1000000: 1000000000000
processing time: 1.38[s]

経過時間と推定残り時間の表示

時間がかかる処理の場合には,処理の途中で,経過時間と推定の残り時間を表示したいことがある.例えば,以下のように記述すると,経過時間と推定残り時間を表示することができる.

import time
def main():
num = 1000000
begin_time = time.time()
for i in range(num):
square = (i+1) ** 2
current_time = time.time()
elapsed_time = current_time - begin_time
remaining_time = elapsed_time * (num-i) / (i+1)
print(f'square of {(i+1):7}: {square:13}, elapsed time: {elapsed_time:4.1f}, remaining time: {remaining_time:4.1f}')
if __name__ == '__main__':
main()

6行目で開始時刻を取得している.繰り返し処理の度に,9行目でその時点の時刻を取得し,10行目で経過時間を求めている.11行目で,全体の繰り返し回数に対するその時点の繰り返し回数の比により,残り時間を推定している.12行目で処理結果と経過時間と推定残り時間を表示している.

このプログラムを実行すると,以下のように,処理結果とともに経過時間と推定残り時間が表示される.

square of 1: 1, elapsed time: 0.0, remaining time: 2.6
square of 2: 4, elapsed time: 0.0, remaining time: 9.4
square of 3: 9, elapsed time: 0.0, remaining time: 7.2
...
square of 999998: 999996000004, elapsed time: 2.4, remaining time: 0.0
square of 999999: 999998000001, elapsed time: 2.4, remaining time: 0.0
square of 1000000: 1000000000000, elapsed time: 2.4, remaining time: 0.0

時間がかかる処理の経過時間と推定残り時間の表示

機械学習やビッグデータの解析の処理には,数時間・数日・数週間かかることがある.時間がかかる処理を模したプログラムを作るために,timeモジュールのsleep関数を使用してみよう.

import time
def main():
num = 100
begin_time = time.time()
for i in range(num):
time.sleep(0.123456789)
current_time = time.time()
elapsed_time = current_time - begin_time
remaining_time = elapsed_time * (num-i) / (i+1)
print(f'elapsed time: {elapsed_time:4.1f}, remaining time: {remaining_time:4.1f}')
if __name__ == '__main__':
main()

8行目でtimeモジュールのsleep関数を使用して,引数で指定した秒数だけ処理を中断している.このプログラムを実行すると,以下のように,経過時間と推定残り時間が表示される.

elapsed time: 0.1, remaining time: 12.4
elapsed time: 0.2, remaining time: 12.2
elapsed time: 0.4, remaining time: 12.1
...
elapsed time: 12.1, remaining time: 0.4
elapsed time: 12.2, remaining time: 0.2
elapsed time: 12.4, remaining time: 0.1

スリープする時間を大きくし,繰り返し回数を増やしてみよう.

import time
def main():
num = 100000
begin_time = time.time()
for i in range(num):
time.sleep(1.23456789)
current_time = time.time()
elapsed_time = current_time - begin_time
remaining_time = elapsed_time * (num-i) / (i+1)
print(f'elapsed time: {elapsed_time:.1f}, remaining time: {remaining_time:.1f}')
if __name__ == '__main__':
main()

この例では,1度にかかる時間が1.23456789秒である処理を100000回繰り返すプログラムを模している.このプログラムを実行すると,以下のように,推定残り時間の秒数が大きすぎて,どの程度時間がかかるかを把握するのに時間がかかる.(PyCharmで実行を途中で止めるには,Runツールウィンドウの左にある停止ボタン(赤い正方形のボタン)を押せばよい)

elapsed time: 1.2, remaining time: 123581.7
elapsed time: 2.5, remaining time: 123562.6
elapsed time: 3.7, remaining time: 123569.4
...

このような場合,時間の表示をわかりやすくするには,以下のようにすればよい.

import time
import datetime
def main():
num = 100000
begin_time = time.time()
for i in range(num):
time.sleep(1.23456789)
current_time = time.time()
elapsed_time = current_time - begin_time
remaining_time = elapsed_time * (num-i) / (i+1)
elapsed_time_td = datetime.timedelta(seconds=elapsed_time)
remaining_time_td = datetime.timedelta(seconds=remaining_time)
print(f'elapsed time: {elapsed_time_td}, remaining time: {remaining_time_td}')
if __name__ == '__main__':
main()

13,14行目で,datetimeモジュールのtimedeltaオブジェクトを生成している.その際に,キーワード引数secondsで秒数で表された経過時間と推定残り時間を指定している.このtimedeltaオブジェクトを15行目のように表示すれば,以下のように,日数・時間・分・秒等に変換して表示することができる.推定残り時間から1日半程度で処理が終わることが確認できる.

elapsed time: 0:00:01.234755, remaining time: 1 day, 10:17:55.480080
elapsed time: 0:00:02.469741, remaining time: 1 day, 10:18:05.820431
elapsed time: 0:00:03.705681, remaining time: 1 day, 10:18:40.240346
...

tqdmモジュールによるプログレスバーの表示

経過時間や推定残り時間をグラフィカルに表示するものにプレグレスバーがある.繰り返し処理にかかる時間をプログレスバーで表示するには,例えばtqdmモジュールを使用すればよい.

import time
from tqdm import tqdm
def main():
iterable100 = range(100)
for i in tqdm(iterable100):
time.sleep(0.123456789)
if __name__ == '__main__':
main()

2行目でtqdmモジュールのtqdm関数をインポートし,7行目のようにfor文のinの後ろで,イテラブルオブジェクトを引数としてtqdm関数を呼び出している.このプログラムを実行すると,以下のように,プログレスバーが表示される.

27%|██▋ | 27/100 [00:03<00:09, 8.06it/s]

2つのイテラブルオブジェクトに対して繰り返しを行う際にzip関数を使用した.以下のように要素数の異なる2つのイテラブルオブジェクトに対してzip関数を使用して繰り返しを行い,tqdm関数を使用してプログレスバーを表示してみよう.

import time
from tqdm import tqdm
def main():
iterable100 = range(100)
iterable200 = range(200)
for i0, i1 in tqdm(zip(iterable100, iterable200)):
time.sleep(0.123456789)
if __name__ == '__main__':
main()

実行結果は以下のようになり,正しくプログレスバーが表示されないことがわかる.これは,複数のイテラブルオブジェクトのうち,どのオブジェクトの要素数を基準とするかが明確でないために起こる.

27it [00:03, 8.07it/s]

基準となる回数(トータルの繰り返し回数)はtqdm関数のキーワード引数totalで指定することができる.zip関数では,要素数が最も小さいオブジェクトの要素数だけ繰り返しが起こるため,以下のように指定することで,プログレスバーを正しく表示することができる.

import time
from tqdm import tqdm
def main():
iterable100 = range(100)
iterable200 = range(200)
for i0, i1 in tqdm(zip(iterable100, iterable200), total=min(len(iterable100), len(iterable200))):
time.sleep(0.123456789)
if __name__ == '__main__':
main()
27%|██▋ | 27/100 [00:03<00:09, 8.07it/s]

課題

課題0

卒業までの日数を調べよ.

課題1

自分の誕生日から今日までの日数を求めよ.

課題2

自分の誕生日から10000日目の日を求めよ.

課題3

ある年の春学期開始日・終了日,秋学期開始日・終了日から,夏休みの日数と年間の学期中の日数を求めよ.

課題4

年が明けるまでの日数・時間・分・秒を求めよ.

課題5

自分の誕生日から1000000000秒後の日を求めよ.

課題6

1から1000000までの整数を2乗した結果を表示し,その処理にかかった時間を計測せよ.

課題7

1から1000000までの整数を2乗した結果と経過時間と推定残り時間を表示せよ.

課題8

timeモジュールのsleep関数を使用して,数日程度時間がかかる処理を模したプログラムを作り,経過時間と推定残り時間を表示せよ.

課題9

timeモジュールのsleep関数を使用して,数分程度時間がかかる処理を模したプログラムを作り,プログレスバーを表示せよ.