Pythonで日時の文字列にタイムゾーンをつけるときの注意点

2024.04.30

はじめに

データアナリティクス事業本部のkobayashiです。

pythonで文字列で与えらた日付データをスクリプ内で使用する際にdatetime型に変換した上でタイムゾーンを持たせて使いたいことはよくあるかと思います。そのタイムゾーンをもたせる処理としてはreplaceとastimezoneがありますがこの違いをまとめたいと思います。

調査しようと思った経緯

やりたかったことは「与えられた日付文字列をJSTとしてスクリプト内で使う」で、具体的には タイムゾーン情報を持たない文字列(例えば2024-04-20 10:22:33)をdatetimeクラスのstrptime()メソッドを使用してdatetime型にした後にタイムゾーンの情報を持たせてからタイムゾーンに依存する後続の処理で使用します。コードとしては以下のような処理を行っていました。

from datetime import datetime
from zoneinfo import ZoneInfo

start_dt = datetime.strptime("2024-04-20 10:22:33", "%Y-%m-%d %H:%M:%S")
conv_start_dt.astimezone(ZoneInfo("Asia/Tokyo"))

(タイムゾーンに依存する後続の処理)

このスクリプトですがローカル環境では成功しますがデプロイ先のサーバー環境での実行では意図しない挙動をしました。 pythonのdatetime型をきちんと理解している方ならすぐにお気づきになると思いますが、ローカル環境とサーバー環境ではタイムゾーンが異なることが原因になります。ローカル環境はJST、サーバー環境はUTCで動いています。

replace()メソッドとastimezone()メソッドの違い

環境

  • Python: 3.12.3

上記のスクリプトで本来やりたかった「与えられた日付文字列をJSTとしてスクリプト内で使う」を行うにはreplace()メソッドを使うべきでした。

replace()メソッドとastimezone()メソッドは、どちらもPythonのdatetimeオブジェクトを操作する際に時刻情報を変更するために使用されますが、異なる動作を持っています。

この挙動を確認するためには以下のスクリプトを異なるタイムゾーンを持つローカル環境とサーバー環境で実行してみることで確認できます。

from datetime import datetime
from zoneinfo import ZoneInfo
import time

print(time.tzname)

start_dt = datetime.strptime("2024-04-20 10:22:33", "%Y-%m-%d %H:%M:%S")

print(start_dt)
print(start_dt.replace(tzinfo=ZoneInfo("Asia/Tokyo")))
print(start_dt.astimezone(ZoneInfo("Asia/Tokyo")))

これをまずはJSTのタイムゾーンであるローカル環境で実行してみた結果が以下です。

('JST', 'JST')
2024-04-20 10:22:33
2024-04-20 10:22:33+09:00
2024-04-20 10:22:33+09:00

ローカル環境のタイムゾーンがJSTなのでreplace、astimezoneともに同じ結果が得られます。replaceでは2024-04-20 10:22:33にタイムゾーとしてJSTを追加して2024-04-20 10:22:33+09:00となります。一方astimezoneでは2024-04-20 10:22:33をローカルのタイムゾーンであるJSTの2024-04-20 10:22:33+09:00と解釈したうえで同一時刻となるようにタイムゾーン変換を行いますが変換するタイムゾーンはすでにJSTなのでそのまま2024-04-20 10:22:33+09:00が出力されます。

次にUTCのタイムゾーンであるサーバー環境で同じスクリプトを実行した結果が以下です。

('UTC', 'UTC')
2024-04-20 10:22:33
2024-04-20 10:22:33+09:00
2024-04-20 19:22:33+09:00

サーバー環境のタイムゾーンがUTCなのでreplaceとastimezoneで結果が異なり、astimezoneを使った場合は+9hされています。これはastimezoneでは2024-04-20 10:22:33をローカルのタイムゾーンであるUTCの2024-04-20 10:22:33+00:00と解釈したうえで同一時刻となるようにタイムゾーン変換を行ったので2024-04-20 19:22:33+09:00となります。

これをまとめるとNaiveなdatetimeオブジェクトに対するreplace()メソッドとastimezone()メソッドの挙動は次の通りです。

  • replece()はNaiveなdatetimeオブジェクトにそのままタイムゾーン情報を付け加えてAwareなdatetimeオブジェクトにする
  • astimezone()はNaiveなdatetimeオブジェクトを実行環境のタイムゾーンとして扱い、その上で同日時となるようにタイムゾーン変換をしてAwareなdatetimeオブジェクトにする

まとめ

replace()は既存のdatetimeオブジェクトの属性を変更して新しいオブジェクトを返し、astimezone()は新しいタイムゾーンに変換したdatetimeオブジェクトを返すという違いがあります。適切なメソッドを選択して、タイムゾーンやその他の時刻情報を行いましょう。

最後まで読んで頂いてありがとうございました。