プロジェクト

全般

プロフィール

8章学習記録 » 履歴 » バージョン 1

Tatsuya ISHIGAKI, 2025/07/07 11:59

1 1 Tatsuya ISHIGAKI
# 第8章 日付と時刻の処理
2
3
## 8.1 日付や時刻を扱う datetime
4
5
datetime モジュールのオブジェクト
6
|オブジェクト名|用途|
7
|---|---|
8
|date|日付|
9
|time|時刻|
10
|datetime|日時|
11
|timedelta|2つの日時の差|
12
13
- date オブジェクト
14
  - `date(year, month, day)`
15
  - インスタンスメソッド
16
    |メソッド名|解説|戻り値|
17
    |---|---|---|
18
    |`weekday()`|曜日を返す (月曜日 0, 日曜日 6)|int|
19
    |`isoweekday()`|曜日を返す (月曜日 1, 日曜日 7)|int|
20
    |`isoformat()`|ISO 8601形式 (YYYY-MM-DD) で表した文字列を返す|str|
21
    |`strftime(format)`|指定したフォーマットに従って日付文字列を返す|str|
22
23
  - 属性
24
    |属性名|解説|戻り値|
25
    |---|---|---|
26
    |`year`|年|int|
27
    |`month`|月|int|
28
    |`day`|日|int|
29
30
  - クラスメソッド
31
    |メソッド名|解説|戻り値|
32
    |---|---|---|
33
    |`today()`|今日の日付の date オブジェクトを返す|datetime.date|
34
    |`fromisoformat(date_string)`|ISO 8601形式 (YYYY-MM-DD) で表した日付文字列から date オブジェクトを生成する|datetime.date|
35
36
  ```python
37
  >>> from datetime import date
38
  >>> today = date.today()
39
  >>> today
40
  datetime.date(2025, 7, 2)
41
  >>> birthday = date(1983, 9, 28)
42
  >>> birthday
43
  datetime.date(1983, 9, 28)
44
  >>> today.isoweekday()
45
  3
46
  >>> today.isoformat()
47
  '2025-07-02'
48
  >>> tomorrow = date.fromisoformat('2025-07-03')
49
  >>> tomorrow.day
50
  3
51
  >>> tomorrow.isoweekday()
52
  4
53
  >>> tomorrow.strftime("明日は西暦 %Y 年 %m 月 %d 日 (%a) です")
54
  '明日は西暦 2025 年 07 月 03 日 (Thu) です'
55
  ```
56
57
- time オブジェクト
58
  - `time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)`
59
  - インスタンスメソッド
60
    |メソッド名|解説|戻り値|
61
    |---|---|---|
62
    |`isoformat(timespec='auto')`|ISO 8601 形式 (HH:MM:SS.ffffff) の文字列を返す<br>マイクロ秒が 0 の場合は HH:MM:SS<br>(*1)|str|
63
    |`strftime(format)`|(略)|str|
64
    |`tzname()`|タイムゾーン名|str|
65
    - (*1) `timespec` : 大きいほうからどこまで表示するかを指定する (非表示部分は切り捨て)
66
      - `hours` HH
67
      - `minutes` HH:MM
68
      - `seconds` HH:MM:SS
69
      - `milliseconds` HH:MM:SS.sss
70
      - `microseconds` HH:MM:SS.mmmmmm
71
      - `auto` : microsecond が 0 なら `seconds` 、そうでない場合 `microseconds`
72
73
  - 属性
74
    |属性名|解説|戻り値|
75
    |---|---|---|
76
    |`hour`|時|int|
77
    |`minute`|分|int|
78
    |`second`|秒|int|
79
    |`microsecond`|マイクロ秒|int|
80
    |`tzinfo`|タイムゾーン情報|オブジェクト|
81
    |`fold`|(略) 0 または 1|int|
82
83
  - クラスメソッド
84
    |メソッド名|解説|戻り値|
85
    |---|---|---|
86
    |`fromisoformat(time_string)`|ISO 8601 形式 (HH:MM:SS.ffffff または HH:MM:SS) の文字列から time オブジェクトを生成|datetime.time|
87
88
  ```python
89
  >>> from datetime import time
90
  >>> t1 = time.fromisoformat("21:54:12.345678")
91
  >>> t1
92
  datetime.time(21, 54, 12, 345678)
93
  >>> t1.isoformat()
94
  '21:54:12.345678'
95
  >>> t1.hour
96
  21
97
  >>> t1.second
98
  12
99
  >>> t1.strftime("今は %H 時 %M 分 %S 秒 です")
100
  '今は 21 時 54 分 12 秒 です'
101
  ```
102
103
- datetime オブジェクト
104
  - `datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)`
105
  - インスタンスメソッド
106
    |メソッド名|解説|戻り値|
107
    |---|---|---|
108
    |`date()`|同じ年月日の date を返す|datetime.date|
109
    |`time()`|同じ時分秒の time を返す|datetime.time|
110
    |`isoformat(sep='T', timespec='auto')`|`sep` は 1 文字で、日付と時刻の間に配置される<br>他は date, time の同メソッド参照|str|
111
    |`strftime(format)`|(略)|str|
112
    |`tzname()`|(略)|str|
113
114
  - 属性
115
    - date, time と同じ
116
    - `year` `month` `day` `hour` `minute` `second` `microsecond` `tzinfo` `fold`
117
118
  - クラスメソッド
119
    |メソッド名|解説|戻り値|
120
    |---|---|---|
121
    |`today()`|デフォルトタイムゾーンの現在日時を表す datetime を返す<br>※「today」だが、時刻も設定される|datetime.datetime|
122
    |`now(tz=None)`|`today()` と同様だが、`tz` に zoneinfo.ZoneInfo オブジェクトを渡せばタイムゾーンを指定可能|datetime.datetime|
123
    |`utcnow()`|UTC の現在日時を表す datetime を返す|datetime.datetime|
124
    |`fromisoformat(date_string)`|ISO 8601 形式 (YYYY-MM-DDTHH:MM:SS.ffffff) の文字列から datetime を生成|datetime.datetime|
125
    |`strptime(date_string, format)`|指定したフォーマットに従って文字列から datetime オブジェクトを生成|datetime.datetime|
126
127
- timedelta オブジェクト
128
date, time, datetime の差を扱うオブジェクト
129
  - `timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)`
130
  - date, time, datetime の差演算結果は timedelta オブジェクトとして返る
131
  ```python
132
  from datetime import date, timedelta
133
  >>> today = date.today()
134
  >>> today
135
  datetime.date(2025, 7, 2)
136
  >>> birthday = date(1983, 9, 28)
137
  >>> delta = today - birthday
138
  >>> delta.days / 365
139
  41.78904109589041
140
  ```
141
142
- strftime() での指定子
143
代表的なもの
144
  |指定子|意味|使用例|
145
  |---|---|---|
146
  |%d|0 埋めした月中の日にち|01,02,...,31|
147
  |%m|0 埋めした月|01,02,...,12|
148
  |%y|0 埋めした西暦下2桁|00,01,...,99|
149
  |%Y|0 埋めした西暦4桁|0001,0002,...,9999|
150
  |%H|0 埋めした時 (24時間表記)|00,01,...,23|
151
  |%M|0 埋めした分|00,01,...,59|
152
  |%S|0 埋めした秒|00,01,...,59|
153
154
- パースエラー
155
  - `fromisoformat()` `strptime()` でパースできない文字列を渡すと、ValueError が発生する
156
157
## 8.2 時刻を扱う time
158
time モジュールはエポック (epoch) という基準時刻からの経過時間 (エポック秒) を扱う. 通常エポックは **1970年1月1日0時0分0秒**
159
160
- 主な関数
161
  |関数名|解説|戻り値|
162
  |---|---|---|
163
  |`gmtime([secs])`|UTC の現在時刻を表す struct_time を返す<br>secs が指定された場合はエポック秒 secs として扱う|time.struct_time|
164
  |`localtime([secs])`|ローカルの現在時刻を表す struct_time を返す<br>secs が指定された場合はエポック秒 secs として扱う|time.struct_time|
165
  |`strftime(format[, t])`|`t`(time.struct_time) を指定されたフォーマットに従った文字列として返す<br>`t` が指定されない場合は localtime() の値を使用|str|
166
  |`time()`|エポックからの秒数を返す|float|
167
168
  ```python
169
  >>> import time
170
  >>> time.gmtime()
171
  time.struct_time(tm_year=2025, tm_mon=7, tm_mday=2, tm_hour=14, tm_min=13, tm_sec=21, tm_wday=2, tm_yday=183, tm_isdst=0)
172
  >>> time.localtime()
173
  time.struct_time(tm_year=2025, tm_mon=7, tm_mday=2, tm_hour=23, tm_min=13, tm_sec=30, tm_wday=2, tm_yday=183, tm_isdst=0)
174
  >>> time.strftime("日本は %H:%M:%S")
175
  '日本は 23:14:34'
176
  >>> time.time()
177
  1751465685.0863643
178
  ```
179
180
- 時刻オブジェクト struct_time
181
  - struct_time は名前付きタプル
182
183
  |インデックス|属性名|解説|戻り値|
184
  |---|---|---|---|
185
  |0|tm_year|年|int|
186
  |1|tm_mon|月|int|
187
  |2|tm_mday|日|int|
188
  |3|tm_hour|時|int|
189
  |4|tm_min|分|int|
190
  |5|tm_sec|秒|int|
191
  |6|tm_wday|曜日 (0:月曜日)|int|
192
  |7|tm_yday|年の中での日 (1 - 366)|int|
193
  |8|tm_isdst|夏時間かどうか (0:夏時間ではない)|int|
194
  ||tm_zone|タイムゾーン名|str|
195
  ||tm_gmtoff|タイムゾーンの UTC からのオフセット秒|int|
196
197
  ```python
198
  >>> jtime = time.localtime()
199
  >>> jtime
200
  time.struct_time(tm_year=2025, tm_mon=7, tm_mday=2, tm_hour=23, tm_min=16, tm_sec=33, tm_wday=2, tm_yday=183, tm_isdst=0)
201
  >>> jtime[0]
202
  2025
203
  >>> jtime.tm_hour
204
  23
205
  >>> jtime.tm_sec
206
  33
207
  ```
208
209
- スレッドの一時停止 sleep()
210
- time モジュール使用例
211
  - 処理失敗時のリトライ時に sleep() で待機
212
  - datetime と組み合わせて日時計算
213
  - 計測対象の処理前後で現在時刻取得をして、簡易処理時間計測
214
- time.sleep() と async/await 構文は意図通りに動作しない可能性あり
215
  - async/await は 19.1 (p.446)
216
217
## 8.3 IANA タイムゾーンデータベースを扱う zoneinfo
218
219
- ZoneInfo オブジェクト
220
  - `ZoneInfo(key)`
221
    - `key` タイムゾーン名
222
    ```python
223
    >>> from zoneinfo import ZoneInfo
224
    >>> TOKYO = ZoneInfo('Asia/Tokyo')
225
    Traceback (most recent call last):
226
      File "C:\Python313\Lib\zoneinfo\_common.py", line 12, in load_tzdata
227
        return resources.files(package_name).joinpath(resource_name).open("rb")
228
               ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
229
      File "C:\Python313\Lib\importlib\resources\_common.py", line 46, in wrapper
230
        return func(anchor)
231
      File "C:\Python313\Lib\importlib\resources\_common.py", line 56, in files
232
        return from_package(resolve(anchor))
233
                            ~~~~~~~^^^^^^^^
234
      File "C:\Python313\Lib\functools.py", line 934, in wrapper
235
        return dispatch(args[0].__class__)(*args, **kw)
236
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
237
      File "C:\Python313\Lib\importlib\resources\_common.py", line 82, in _
238
        return importlib.import_module(cand)
239
               ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
240
      File "C:\Python313\Lib\importlib\__init__.py", line 88, in import_module
241
        return _bootstrap._gcd_import(name[level:], package, level)
242
               ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
243
      File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
244
      File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
245
      File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
246
      File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
247
      File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
248
      File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
249
      File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
250
      File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
251
      File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
252
      File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
253
      File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
254
    ModuleNotFoundError: No module named 'tzdata'
255
    
256
    During handling of the above exception, another exception occurred:
257
    
258
    Traceback (most recent call last):
259
      File "<python-input-1>", line 1, in <module>
260
        TOKYO = ZoneInfo('Asia/Tokyo')
261
      File "C:\Python313\Lib\zoneinfo\_common.py", line 24, in load_tzdata
262
        raise ZoneInfoNotFoundError(f"No time zone found with key {key}")
263
    zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key Asia/Tokyo'
264
    ```
265
    - Windows では上記の様にエラーが出る (OS の タイムゾーンデータベースの形式が TZif でない為)
266
      - tzdata パッケージをインストールすれば解決する
267
      - `pip install tzdata`
268
      ```python
269
      >>> from zoneinfo import ZoneInfo
270
      >>> TOKYO = ZoneInfo('Asia/Tokyo')
271
      >>> TOKYO
272
      zoneinfo.ZoneInfo(key='Asia/Tokyo')
273
      >>> from datetime import datetime
274
      >>> dt = datetime(1983, 9, 28, tzinfo=TOKYO)
275
      >>> dt
276
      datetime.datetime(1983, 9, 28, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
277
      >>> dt.astimezone(ZoneInfo('America/Los_Angeles')
278
      ... )
279
      datetime.datetime(1983, 9, 27, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles'))
280
      ```
281
282
## 8.4 datetime の強力な拡張モジュール dateutil
283
- インストール
284
  - `pip install python-dateutil`
285
286
### 日付文字列の構文解析 parser
287
- `parser.parse(timestr, parserinfo=None, **kwargs)` (戻り値: datetime.datetime)
288
  - `timestr` 日時文字列
289
  - `parserinfo` 日付解析の振る舞いを変更するためのオブジェクト
290
  - `**kwargs`
291
    - `default` 変換するときのデフォルト値となる datetime オブジェクト
292
    - `dayfirst` True を指定すると "1/2/3" などの先頭を「日」として解析する
293
    - `yearfirst` True を指定すると "1/2/3" などの先頭を「年」として解析する 
294
295
  ```python
296
  >>> from dateutil.parser import parse
297
  >>> parse("2025/7/3 21:54")
298
  datetime.datetime(2025, 7, 3, 21, 54)
299
  >>> parse("1/2/3")
300
  datetime.datetime(2003, 1, 2, 0, 0)  # 指定無しは 月/日/年 と解析
301
  >>> parse("1/2/3", dayfirst=True)  
302
  datetime.datetime(2003, 2, 1, 0, 0)  # 日/月/年 と解析
303
  >>> parse("1/2/3", yearfirst=True)
304
  datetime.datetime(2001, 2, 3, 0, 0)  # 年/月/日 と解析
305
  >>> from datetime import datetime
306
  >>> default_dt = datetime.today()
307
  >>> default_dt
308
  datetime.datetime(2025, 7, 3, 21, 57, 47, 441323)  # デフォルトを 2025-07-03 21:57:47 とする
309
  >>> parse("12:30:55", default=default_dt)
310
  datetime.datetime(2025, 7, 3, 12, 30, 55)  # 文字列に無い値はデフォルトの値になる
311
  ```
312
313
### 日付の差の計算 relativedelta
314
- `relativedelta.relativedelta(引数下記)`
315
  - date または datetime を 2 つ渡すと、その差を relativedelta として生成
316
    - `dt1=None`
317
    - `dt2=None`
318
  - 相対値引数 : 和差演算の際に、増減させる値を指定 (数値の前に `+` `-` を付加)
319
    - `years=0`
320
    - `months=0`
321
    - `days=0`
322
    - `leapdays=0`
323
    - `weeks=0`
324
    - `hours=0`
325
    - `minutes=0`
326
    - `seconds=0`
327
    - `microseconds=0`
328
  - 絶対値引数 : 和差演算の際に、ここで指定した値に置換する (増減ではない)
329
    - `year=None`
330
    - `month=None`
331
    - `day=None`
332
    - `weekday=None`
333
    - `yearday=None`
334
    - `nlyearday=None`
335
    - `hour=None`
336
    - `minute=None`
337
    - `second=None`
338
    - `microsecond=None`
339
340
  ```python
341
  >>> from datetime import datetime, date
342
  >>> from dateutil.relativedelta import relativedelta
343
  >>> today = date.today()
344
  >>> today
345
  datetime.date(2025, 7, 3)
346
  >>> birthday = date(1983, 9, 28)
347
  >>> birthday
348
  datetime.date(1983, 9, 28)
349
  >>> relativedelta(today, birthday)            # 今日と誕生日の差を relativedelta で
350
  relativedelta(years=+41, months=+9, days=+5)  # 41年9か月と5日
351
  >>> now = datetime.now()
352
  >>> now
353
  datetime.datetime(2025, 7, 3, 22, 28, 37, 209118)    # 2025-07-03 22:28:37
354
  >>> now + relativedelta(days=+3, hours=+1)           # 3日と1時間後
355
  datetime.datetime(2025, 7, 6, 23, 28, 37, 209118)    # 2025-07-06 23:28:37
356
  >>> now + relativedelta(months=+4, day=20)           # 4か月後の20日
357
  datetime.datetime(2025, 11, 20, 22, 28, 37, 209118)  # 2025-11-20 22:28:37
358
  >>> now - relativedelta(months=+8, day=20)           # 8か月前の20日
359
  datetime.datetime(2024, 11, 20, 22, 28, 37, 209118)  # 2024-11-20 22:28:37
360
  ```
361
362
  ```python
363
  >>> from datetime import datetime, date
364
  >>> from dateutil.relativedelta import relativedelta
365
  >>> from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU
366
  >>> today = date.today()
367
  >>> today
368
  datetime.date(2025, 7, 3)
369
  >>> today + relativedelta(weekday=SU)  # 次の日曜日
370
  datetime.date(2025, 7, 6)
371
  >>> today + relativedelta(weekday=SU(-1))  # 前の日曜日
372
  datetime.date(2025, 6, 29)
373
  >>> today + relativedelta(yearday=365)  # 年の365日目
374
  datetime.date(2025, 12, 31)
375
  >>> today + relativedelta(months=+2, day=1, weekday=SA(+3))  # 2か月後の3回目の土曜日
376
  datetime.date(2025, 9, 20)
377
  ```
378
379
### 繰り返しルール rrule
380
- `rrule.rrule(引数下記)`
381
  - `freq` 繰り返し頻度 `YEARLY` `MONTHLY` `WEEKLY` `DAILY` `HOURLY` `MINUTELY` `SECONDLY`
382
  - `cache=False` True の場合キャッシュする
383
  - `dtstart=None` 開始日時を datetime で指定、指定無しの場合は datetime.now() の値が使われる
384
  - `interval=1` 間を飛ばす数 (例:HOURLY で interval=2 なら、2時間毎)
385
  - `wkst=None` 週の始まりの曜日 (byweekno を指定した場合に意味を持つようだ)
386
  - `count=None` 繰り返し回数 (until と同時には指定できない)
387
  - `until=None` 終了日時を datetime で指定 (count と同時には指定できない)
388
  - `bysetpos=None` byXXXX で指定したルールに対して、何回目のものを有効とするかを `+` `-` の数値で指定
389
  - `bymonth` `bymonthday` `byweekno` `byweekday` `byhour` `byminute` `bysecond` `byeaster` 指定された期間のみを対象とする (フィルタのようなもの). 単一値、またはタプルを指定
390
    - 特定月、特定日(毎月25日等)、特定曜日(月曜と木曜等)、特定番号週(年の週番号指定の週) など
391
392
  ```python
393
  >>> from dateutil.rrule import rrule
394
  >>> from dateutil.rrule import DAILY, WEEKLY, MONTHLY
395
  >>> from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU
396
  >>> import pprint
397
  >>> import sys
398
  >>> sys.displayhook = pprint.pprint  # 表示を見やすくするために設定
399
  >>> start = datetime(2021, 2, 16)
400
  >>> list(rrule(DAILY, count=3, dtstart=start))  # 指定日から3日間
401
  [datetime.datetime(2021, 2, 16, 0, 0),
402
   datetime.datetime(2021, 2, 17, 0, 0),
403
   datetime.datetime(2021, 2, 18, 0, 0)]
404
  >>> list(rrule(DAILY, dtstart=start, until=datetime(2021, 2, 19)))  # 指定期間毎日
405
  [datetime.datetime(2021, 2, 16, 0, 0),
406
   datetime.datetime(2021, 2, 17, 0, 0),
407
   datetime.datetime(2021, 2, 18, 0, 0),
408
   datetime.datetime(2021, 2, 19, 0, 0)]
409
  >>> list(rrule(WEEKLY, count=4, wkst=SU, byweekday=(TU,TH), dtstart=start))  # 毎週火曜、木曜
410
  [datetime.datetime(2021, 2, 16, 0, 0),
411
   datetime.datetime(2021, 2, 18, 0, 0),
412
   datetime.datetime(2021, 2, 23, 0, 0),
413
   datetime.datetime(2021, 2, 25, 0, 0)]
414
  >>> list(rrule(MONTHLY, count=3, byweekday=FR(-1), dtstart=start))  # 毎月最終金曜日
415
  [datetime.datetime(2021, 2, 26, 0, 0),
416
   datetime.datetime(2021, 3, 26, 0, 0),
417
   datetime.datetime(2021, 4, 30, 0, 0)]
418
  ```