はじめに
以前投稿した記事「Twitterでの情報収集を RSS へ(一部)移行」のプログラムを改良してたのだが、RSSフィードのなかの日時の変換ではまった。
インターネットにもいろいろな情報があるにはあるのだが、Pythonのバージョンだったり、使うライブラリだったり、OS等によっていろいろあるらしく混乱した。自分の頭の整理のためにもまとめた。
環境
- NanoPi NEO21
- Armbian 22.11.4 (debian 11 (bullseye) ベース)
- Python V3.9.2
- dateutil v2.8.1
- pytz 2022.7.1
Pythonのライブラリは一部OSに依存する(タイムゾーン名、等)。OSによって動作が若干異なる事があるので注意
やりたかったこと
やりたいことは単純で、サーバーから取得したRSSフィードに含まれている日時の文字列、今回の場合は'Mon, 13 Feb 2023 10:40:59 GMT'
という形式、を日本時間(=ローカル時間2)に変換すること。
ハマった点(1)
問題点:Awareなdatetimeオブジェクトにならない
PythonのdatetimeオブジェクトにはAware(タイムゾーン情報あり)とNaive(タイムゾーン情報なし)の2種類がある
タイムゾーン情報が付与された日時文字列'Mon, 13 Feb 2023 10:40:59 GMT'
をdatetimeオブジェクトにしようとするとdatetime.strptime()
では%Z
で指定しているタイムゾーン情報GMT
がなくなってしまう。(タイムゾーン情報が欠落したNaiveなdatetimeオブジェクトになってしまう。)
日時文字列が'Mon, 13 Feb 2023 10:40:59+00:00'
のような時差形式で%z
を使用する場合このようにはならない
import datetime
s = 'Mon, 13 Feb 2023 10:40:59 GMT'
fmt = '%a, %d %b %Y %H:%M:%S %Z'
dt = datetime.datetime.strptime(s, fmt)
print(dt)
#2023-02-13 10:40:59 # タイムゾーン情報がなくなる
print(dt.tzinfo)
#None # タイムゾーン情報がなくなる
GMT
の代わりにUTC
やJST
が使われていても同じ結果でタイムゾーンの情報がなくなってしまう
import datetime
fmt = '%a, %d %b %Y %H:%M:%S %Z'
dt = datetime.datetime.strptime('Mon, 13 Feb 2023 10:40:59 GMT', fmt)
print(dt, dt.tzinfo)
#2023-02-13 10:40:59 None # タイムゾーン情報(GMT)がなくなる
dt = datetime.datetime.strptime('Mon, 13 Feb 2023 10:40:59 UTC', fmt)
print(dt, dt.tzinfo)
#2023-02-13 10:40:59 None # タイムゾーン情報(UTC)がなくなる
dt = datetime.datetime.strptime('Mon, 13 Feb 2023 10:40:59 JST', fmt)
print(dt, dt.tzinfo)
#2023-02-13 10:40:59 None # タイムゾーン情報(JST)がなくなる
しかし、上の例のdatetime.strptime()
の第2引数の%Z
の部分が無視されているわけではなく%Z
を省略するとエラーになってしまう。また、不適切なタイムゾーン名、例えばUTC+9
を指定するとdatetime.strptime()
がエラーになってしまう。
# フォーマットに%Zを指定しない場合
dt = datetime.datetime.strptime('Mon, 13 Feb 2023 10:40:59 GMT', '%a, %d %b %Y %H:%M:%S')
#エラー
# タイムゾーンとして'UTC+9'を使用した場合
dt = datetime.datetime.strptime('Mon, 13 Feb 2023 10:40:59 UTC+9', '%a, %d %b %Y %H:%M:%S %Z')
#エラー
Pythonの公式ドキュメント「strftime() と strptime() の書式コード」の注釈(6)によればdatetime.strptime()
の第2引数のタイムゾーン(%Z
)には(日本で使用する場合)'UTC'、'GMT'、'JST'しかダメらしい。3
解決策:dateutilライブラリを使用する
datetime.strptime()
の代わりにdateutil.parser.parse()
を使用する。
import dateutil.parser
s = 'Mon, 13 Feb 2023 10:40:59 GMT'
dt = dateutil.parser.parse(s)
print(dt)
#2023-02-13 10:40:59+00:00 # タイムゾーン情報(+00:00)が残っている
print(dt.tzinfo)
# UTC # タイムゾーン(UTC)が残っている
もちろんJST
も使用できる。
import dateutil.parser
s = 'Mon, 13 Feb 2023 10:40:59 JST'
dt = dateutil.parser.parse(s)
print(dt)
#2023-02-13 10:40:59+09:00 # タイムゾーン情報(+09:00)が残っている
print(dt.tzinfo)
# JST # タイムゾーン(JST)が残っている
ハマった点(2)
疑問:日本時間に変換する方法がたくさんあるがどれがいい?
タイムゾーン情報付きのdatetimeオブジェクト(Awareなdatetimeオブジェクト)から日本時間(正確にはローカルタイム2)に変換する方法がいろいろあってどの方法を選択すればいいか分からない。
(1)標準ライブラリを使用する方法
import datetime
# 変換前の時間(UTC)
dt = datetime.datetime(2023, 2, 13, 10, 40, 59, tzinfo=datetime.timezone.utc)
print(dt)
#2023-02-13 10:40:59+00:00
# 方法1:システム設定にまかせる
print(dt.astimezone())
#2023-02-13 19:40:59+09:00
# 方法2:時差を指定する(欧米等、夏時間がある地域は難しい)
td = datetime.timedelta(hours=9)
tz = datetime.timezone(td)
print(dt.astimezone(tz)
#2023-02-13 19:40:59+09:00
# 方法3:zoneinfo.ZoneInfoを使用(Python V3.9以降)(OSまたは、tzdataパッケージ依存)
import zoneinfo
print(dt.asttimezone(zoneinfo.ZoneInfo('Asia/Tokyo')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(zoneinfo.ZoneInfo('Japan')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(zoneinfo.ZoneInfo('localtime')))
#2023-02-13 19:40:59+09:00
(2)dateutil.tzを使用する方法
import datetime
from dateutil import tz
# 変換前の時間(UTC)
dt = datetime.datetime(2023, 2, 13, 10, 40, 59, tzinfo=datetime.timezone.utc)
print(dt)
#2023-02-13 10:40:59+00:00
# 方法1:タイムゾーン名(OS依存)を指定する
print(dt.astimezone(tz.gettz('Asia/Tokyo')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(tz.gettz('Japan')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(tz.gettz('localtime')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(tz.gettz('')))
#エラー
print(dt.astimezone(tz.gettz()))
#2023-02-13 19:40:59+09:00
(3) pytzライブラリを使用する方法
pytzはtimezoneの処理をするためのライブラリ。
import datetimme
import pytz
# 変換前の時間(UTC)
dt = datetime.datetime(2023, 2, 13, 10, 40, 59, tzinfo=datetime.timezone.utc)
print(dt)
#2023-02-13 10:40:59+00:00
# 方法1:タイムゾーン名を指定する
print(dt.astimezone(pytz.timezone('Asia/Tokyo')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(pytz.timezone('Japan')))
#2023-02-13 19:40:59+09:00
print(dt.astimezone(pytz.timezone('localtime')))
#エラー
print(dt.astimezone(pytz.timezone('')))
#エラー
print(dt.astimezone(pytz.timezone()))
#エラー
# 方法2:localize()はnaiveなdatetimeではないため使えない
print(pytz.timezone('Asia/Tokyo').localize(dt)))
#エラー
なお、pytz
はPython2系の時からある超メジャーなライブラリらしいが、過去(1888年より前とか)で時差が19分ずれたり、pytz.timezone()
とdatetime.tzinfo
との互換性に癖があったりするらしい6ので避けた方がいいかもしれない。(特にzoneinfoモジュールが追加されたPython V3.9以降の場合)
標準ライブラリのdatetime.timezone
とpytz.timezone
には完全な互換性はない
答え:標準ライブラリ
今回やりたいことは標準ライブラリだけで十分できる。
まとめ
今回の目的、サーバーから取得したRSSフィールドに含まれている日時の文字列を日本時間(=ローカル時間2)に変換したいだけなら
-
dateutil.parser.parse()
を使用してAwareなdatetime
オブジェクトを取得 -
datetime.astimezone()
を使用してローカル時間に変換
でできる。
参考情報
-
中国FriendlyELEC社のARM搭載SBC(シングルボードコンピュータ)。分かりやすく書けば「ラズパイ(Raspberry Pi)みたいなもの」 ↩
-
この記事では「操作者」と「稼働するコンピュータ」が日本でコンピューター上の時間設定も日本時間で、これをローカル時間とする。 ↩ ↩2 ↩3
-
Windows環境(Windows 10(22H2) 64bit、Python 3.9.13)では
JST
を指定したらエラーになった ↩ -
複数バージョンのPythonがインストールされている環境の事を考えると
pip
コマンドよりもpython
コマンドを使用するのが望ましい。(参考ホームページ) ↩ ↩2 ↩3 -
#apt install python3-dateutil
でもインストールできたがバージョンが異なっていた(古かった)。システム(周り)で使用されている可能性があるのでapt
コマンドは使用しない方が無難か? ↩ ↩2 ↩3