LoginSignup
8
2

Pythonでサーバーから返されたGMT(UTC)時間を日本時間に変換する

Last updated at Posted at 2023-02-16

はじめに

以前投稿した記事「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の代わりにUTCJSTが使われていても同じ結果でタイムゾーンの情報がなくなってしまう

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()を使用する。

python-dateutilモジュールをインストールする必要がある45

python3 -m pip install python-dateutil
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を使用する方法

標準ライブラリではないのでdateutilモジュールをインストールする必要がある45

python3 -m pip install python-dateutil
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の処理をするためのライブラリ。

標準ライブラリではないのでpytzモジュールをインストールする必要がある45

python3 -m pip install pyt
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.timezonepytz.timezoneには完全な互換性はない

答え:標準ライブラリ

今回やりたいことは標準ライブラリだけで十分できる。

まとめ

今回の目的、サーバーから取得したRSSフィールドに含まれている日時の文字列を日本時間(=ローカル時間2)に変換したいだけなら

  • dateutil.parser.parse()を使用してAwareなdatetimeオブジェクトを取得
  • datetime.astimezone()を使用してローカル時間に変換

でできる。

参考情報

  1. 中国FriendlyELEC社のARM搭載SBC(シングルボードコンピュータ)。分かりやすく書けば「ラズパイ(Raspberry Pi)みたいなもの」

  2. この記事では「操作者」と「稼働するコンピュータ」が日本でコンピューター上の時間設定も日本時間で、これをローカル時間とする。 2 3

  3. Windows環境(Windows 10(22H2) 64bit、Python 3.9.13)ではJSTを指定したらエラーになった

  4. 複数バージョンのPythonがインストールされている環境の事を考えるとpipコマンドよりもpythonコマンドを使用するのが望ましい。(参考ホームページ) 2 3

  5. #apt install python3-dateutilでもインストールできたがバージョンが異なっていた(古かった)。システム(周り)で使用されている可能性があるのでaptコマンドは使用しない方が無難か? 2 3

  6. Pythonのタイムゾーンはpytzよりzoneinfoかdateutils.tzを使おう、という話

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2