Tipsでは済まなかった
- 最初はTips/チートシートとして、Pythonの日付と時刻処理をまとめようと思いましたが、調べてみると特にタイムゾーンについて配慮が必要なことに気付かされ、結果として普通のレポートになっていました。
目次
- 日付と時刻処理ではタイムゾーンの扱いが重要
- タイムゾーン処理
- 定義と変換
- 任意日付時刻のdatetimeオブジェクト
datetime(2026, 8, 12, 13, 10, 0) # 2026/08/12, 13:10:00
datetime.strptime('2026/8/15, 15:20:10', '%Y/%m/%d, %H:%M:%S')
- datetimeオブジェクトの要素取得
year, month, day, hour, minute, microsecond, tzinfo
- datetimeオブジェクトの変換
datetime->str: dt.strftime("%Y年%m月%d日, %H時%M分%S秒, TZ:%z")
datetime->epoch: dt.timestamp()
epoch->datetime: datetime.fromtimestamp(1786519318.464638)
- 日付/時刻の演算
日付と時刻処理ではタイムゾーンの扱いが重要
- Pythonに限りませんが、日付と時刻の処理ではタイムゾーンの扱い方が重要です。大まかにタイムゾーンは地域の経度で決まりますが、歴史的経緯や夏時間があるため、そんなに単純ではありません。
- 例えば日本国内だけの場合、標準時/協定世界時(UTC)より9時間早い(JST=TZ-9)という「時間シフト」を考えれば良いですが、これを複数の国、例えば日本とアメリカでも使おうとした場合、1国内ですら複数のタイムゾーンが存在するので、タイムゾーンへのケアは必須です。
datetimeオブジェクトのnaiveとaware
- Pythonで日付時刻情報を扱うには
datetimeモジュールのdatetimeクラスを使用します。
- 現在の日付及び時刻の取得は
.now()メソッドを使います。
datetimeオブジェクトはイミュータブルです。
from datetime import datetime
dt_now = datetime.now() # 現在の日付と時刻情報
print(dt_now)
>test.py
2026-08-11 15:35:55.900025
- 引数無しの
.now()メソッドで取得したdatetimeオブジェクトはタイムゾーン情報を持ちません。
- オブジェクトの
tzinfoがNoneです。naive(*1)と呼ばれます。
- 一方タイムゾーン情報を持つ
datetimeオブジェクトはawareと呼ばれます。
from datetime import datetime
dt_now = datetime.now() # Naiveオブジェクト
print(dt_now.tzinfo) # datetimeオブジェクトのtzinfo表示
>test.py
None
awareとタイムゾーン設定とIANA形式
awareオブジェクトを作成する方法は下記2種類です。
datetimeオブジェクト定義時にタイムゾーン情報を与える
naiveオブジェクトにタイムゾーンを追加したオブジェクトを作成する
zoneinfoパッケージのZoneInfoクラスを使用すれば、'Asia/Tokyo'等のIANA形式でタイムゾーンを指定できます。ですが、Windowsでは下記のようにtzdataのインストールが必須(*2)です。ご注意下さい。
>pip install tzdata
- それでは実際にタイムゾーン指定で
awareオブジェクトを作ってみましょう。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_naive = datetime.now() # Naive
dt_aware = datetime.now(tz=ZoneInfo("Asia/Tokyo")) # Aware
print('naive:', dt_naive.tzinfo, 'aware:', dt_aware.tzinfo)
dt_replaced = dt_naive.replace(tzinfo=ZoneInfo("Asia/Tokyo")) # Naive->Aware
print('replaced:', dt_replaced.tzinfo)
>test.py
naive: None, aware: Asia/Tokyo
replaced: Asia/Tokyo
- あまり無いと思いますが、
awareをnaiveへ変更したい時は、tzinfoにNoneを与えます。
dt_naive = dt_aware.replace(tzinfo=None)
UTCタイムゾーンのdatetime取得
- UTC(協定世界時)のIANAタイムゾーン名は
UTC 又は Etc/UTC です。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_utc = datetime.now(tz=ZoneInfo('UTC'))
print(dt_utc)
print(dt_utc.tzinfo)
>test.py
2026-08-12 22:46:27.045797+00:00
UTC
タイムゾーンの変更
.astimezone(tz=...)でタイムゾーンを変更したdatetimeオブジェクトを取得できます。
- ただし
.astimezone(tz=...)はawareオブジェクトに適用して下さい。
from datetime import datetime
from zoneinfo import ZoneInfo
dt_jst = datetime.now(tz=ZoneInfo('Asia/Tokyo')) # aware であることが重要
dt_utc = dt_jst.astimezone(tz=ZoneInfo('UTC')) # タイムゾーン変更
print(dt_jst)
print(dt_utc)
>test.py
2026-08-13 08:35:09.110073+09:00
2026-08-12 23:35:09.110073+00:00
OSからローカルのタイムゾーンを取得する
- タイムゾーンをIANA形式で
datetimeオブジェクトに設定する方法はわかりました。すると次は実行システムのタイムゾーン取得ですね。
- サーバー/オンプレなど異なる実行環境へ対応するには、ローカルタイムゾーン取得は必須です。
- ここまでの例のように、タイムゾーンをリテラルで書くのは「実はまずい」のです。
- Windowsの場合、
tzlocalパッケージの.get_localzone_name()を使う必要があります。
- クロスプラットフォームでは
tzlocal.get_localzone_name()の利用が必須です。
tzlocalは非標準パッケージなのでインストールが必要です。
>pip install tzlocal
- では、ローカルのタイムゾーンをIANA形式で取得してみましょう。そのうえで
datetimeオブジェクトをawareにします。
from tzlocal import get_localzone_name
from datetime import datetime
from zoneinfo import ZoneInfo
tz_iana = get_localzone_name() # ローカルタイムゾーンをIANAで取得
print('get_localzone_name:', tz_iana)
dt_aware = datetime.now(tz=ZoneInfo(tz_iana))
print('tzinfo:', dt_aware.tzinfo) # IANA表示に tzinfo.key を参照
>test.py
get_localzone_name: Asia/Tokyo
tzinfo: Asia/Tokyo
任意日付時刻のdatetimeオブジェクト
- 任意日付時刻の
datetimeオブジェクトは、定義時に値を設定することで作成できます。
from datetime import datetime
from tzlocal import get_localzone_name
from zoneinfo import ZoneInfo
# naive 2026年, 8月, 12日, 13時, 14分, 15秒, 160マイクロ秒
dt_naive = datetime(2026, 8, 12, 13, 14, 15, 160)
# naive -> aware
dt_aware = dt_naive.replace(tzinfo=ZoneInfo(get_localzone_name()))
print(dt_aware)
>test.py
2026-08-12 13:14:15.000160+09:00
datetimeコンストラクタ実行時にtzinfo引数でタイムゾーン指定することもできます。
from datetime import datetime
from tzlocal import get_localzone_name
from zoneinfo import ZoneInfo
# 2026年, 8月, 12日, 13時, 14分, 15秒, 160マイクロ秒
dt_aware = datetime(2026, 8, 12, 13, 14, 15, 160,
tzinfo=ZoneInfo(get_localzone_name())) # tzinfo指定
print(dt_aware)
datetime.strptime()で文字列からdatetimeオブジェクトを作成できます。
- 文字列とフォーマット(
%Y/%m ...)を引数に指定します。
- 使用できるフォーマット文字列は、公式ドキュメントを参照下さい。
from datetime import datetime
dt_naive = datetime.strptime('2026/8/15, 15:20:10', '%Y/%m/%d, %H:%M:%S')
print(dt_naive)
>test.py
2026-08-15 15:20:10
- タイムゾーン指定が無い場合は
naiveになるので、.replace()等でawareにして下さい。
datetimeオブジェクトの要素取得
datetimeオブジェクトが持つ'年'や'分'等の要素を下記属性として取得できます。
| 属性名 | 説明 |
|---|
datetime.year | 4桁数値: '年' | datetime.month | 1〜12: '月' | datetime.day | 1〜31: '日' | datetime.hour | 0〜23': 時' | datetime.minute | 0〜59: '分' | datetime.second | 0〜59: '秒' | datetime.microsecond | 0〜999999: 'マイクロ秒' | datetime.tzinfo | None(naive)又はtzinfo(aware)オブジェクト(*3) |
- 尚
tzinfoを含めたtuple化やdict化のメソッドは無いので、自分で作成/定義が必要です。
datetimeオブジェクトの変換
datetimeオブジェクトを文字列に変換する場合、strftime()メソッドが使用できます。
- 引数は文字列としての出力フォーマットです。
- フォーマット記述方法は
strptime()と同じなので、公式ドキュメントを参照下さい。
from datetime import datetime
from tzlocal import get_localzone_name
from zoneinfo import ZoneInfo
dt = datetime.now(tz=ZoneInfo(get_localzone_name()))
print(dt.strftime("%Y年%m月%d日, %H時%M分%S秒, TZ:%z"))
>test.py
2026年08月12日, 16時12分33秒, TZ:+0900
datetimeオブジェクトをエポック秒に変換するにはtimestamp()メソッドを利用します。
from datetime import datetime
dt = datetime.now()
print(dt.timestamp())
print(dt)
>test.py
1786519318.464638
2026-08-12 16:21:58.464638
- エポック秒を
datetimeへ変換するには、fromtimestamp()メソッドを利用します。
naiveオブジェクトなので、適宜replace()メソッド等でタイムゾーンを追加設定して下さい。
from datetime import datetime
ts_epoch = 1786519318.464638
dt = datetime.fromtimestamp(ts_epoch)
print(dt)
>test.py
2026-08-12 16:21:58.464638
datetime間の日付と時刻差
datetimeオブジェクト間で差分を計算することができます。加算はできません。
from datetime import datetime
from zoneinfo import ZoneInfo
import time
tz_jst = ZoneInfo('Asia/Tokyo')
dt_start = datetime.now(tz=tz_jst); print(dt_start)
time.sleep(5) # 5秒待ち
dt_stop = datetime.now(tz=tz_jst); print(dt_stop)
td_diff = dt_stop - dt_start # 戻り値は timedeltaオブジェクト
print(td_diff)
>test.py
2026-08-13 09:12:20.446030+09:00
2026-08-13 09:12:25.452396+09:00
0:00:05.006366
from datetime import datetime
from zoneinfo import ZoneInfo
dt_jst = datetime.now(tz=ZoneInfo('Asia/Tokyo')) # aware
dt_utc = dt_jst.astimezone(tz=ZoneInfo('UTC')) # タイムゾーン変更
print(dt_jst)
print(dt_utc)
td_diff = dt_utc - dt_jst
print(td_diff)
>test.py
2026-08-13 09:28:13.758421+09:00
2026-08-13 00:28:13.758421+00:00
0:00:00
datetimeとtimedelta間の演算
datetimeとtimedeltaは+と-の両方が演算できます。
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
dt_now = datetime.now(tz=ZoneInfo('Asia/Tokyo'))
dt_before = dt_now - timedelta(hours=10)
dt_after = dt_now + timedelta(hours=10)
print(dt_before)
print(dt_now)
print(dt_after)
>test.py
2026-08-13 00:10:47.748068+09:00
2026-08-13 10:10:47.748068+09:00
2026-08-13 20:10:47.748068+09:00
何日? 何時間?
datetime同士の差分演算結果はtimedeltaであり、日数, 時:分:秒.マイクロ秒が戻ります。
- ここで
timedelta同士は*や/演算も可能なため、これを利用して単位計算ができます。
- 指定できる引数や演算の詳細については公式ドキュメントを参照下さい。
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
dt_pre = datetime(2026, 8, 13, 10, 10, 0, tzinfo=ZoneInfo('Asia/Tokyo'))
dt_post = dt_pre + timedelta(weeks=10) # 10週間後
td = dt_post - dt_pre # timedelta
print(dt_pre)
print(dt_post)
print(td)
print(td/timedelta(weeks=1)) # 週単位
print(td/timedelta(days=1)) # 日数単位
print(td/timedelta(hours=1)) # 時間単位
>test.py
2026-08-13 10:10:00+09:00
2026-10-22 10:10:00+09:00
70 days, 0:00:00
10.0
70.0
1680.0
暦としての差分計算
timedeltaは週単位のweeksまでカバーしますが、月数や年数は持っていません。
- 月数や年数は、月毎の日数、閏年等年毎の日数が変わるためです。
- そのため、
timedeltaでは各月末の計算等は非常に面倒です。
- このような暦を意識した計算では
dateutil.relativedelta()を使用します。
dateutilモジュールは非標準のため、インストールが必要です。
- ちなみにインストール時のパッケージ名は
python-dateutilです。間違えやすいので注意下さい。
>pip install python-dateutil
- それでは
dateutil.relativedelta()を使ってみましょう。各月の月末日を示します。
from datetime import datetime
from zoneinfo import ZoneInfo
from dateutil.relativedelta import relativedelta
dt = datetime(2028, 1, 31, tzinfo=ZoneInfo('Asia/Tokyo'))
for i in range(0, 12):
dt_i = dt + relativedelta(months=i) # 1〜12ヶ月後まで
print(dt_i.strftime("%Y/%m/%d"))
>test.py
2028/01/31
2028/02/29
2028/03/31
2028/04/30
2028/05/31
2028/06/30
2028/07/31
2028/08/31
2028/09/30
2028/10/31
2028/11/30
2028/12/31
|