pythonで日時を扱いたい時、標準のdatetime
モジュールを使うことが多いかと思います。
>>> from datetime import datetime
>>> datetime.now()
datetime.datetime(2021, 8, 24, 22, 52, 51, 0)
普通に使うならこれで困りません(UTCになっているということもなく、コンピュータの設定時刻と同じものを出力してくれます)。
では、 タイムゾーンを表示してくれ、 となったら…?
「なあんだ、そんなの datetime.strftime()
でできるじゃないか」と思うかもしれません。
確かに実装そのものは可能です:
>>> datetime.now().strftime('%Y-%m-%dT%H:%M:%S%z')
'2021-08-24T22:55:39'
…おや? %z
を入れているのにタイムゾーンが表示されません。
そう、datetime.now()
はタイムゾーンの情報を持たないのです。
(これをnaiveオブジェクト
と呼ぶようです。反対にタイムゾーンを持つdatetimeはawareオブジェクト
と呼ばれます)
コードを書く側はJSTがUTC+09:00であることはわかりますが、それをどうやってdatetime
に
載せればいいのでしょうか。
当然こうする訳ではありません:
>>> datetime.now().strftime('%Y-%m-%dT%H:%M:%S+09:00')
'2021-08-24T23:00:08+09:00'
「python タイムゾーン 設定」などのワードでググると、pytz
による実装例が多く見つかります。
>>> import pytz
>>> datetime.now(tz=pytz.timezone('Asia/Tokyo'))
datetime.datetime(2021, 8, 24, 23, 1, 48, 818381, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
ISO 8601
形式の表現が欲しいときは、
>>> datetime.now(tz=pytz.timezone('Asia/Tokyo')).isoformat()
'2021-08-24T23:02:48.502160+09:00'
>>> datetime.now(tz=pytz.timezone('Asia/Tokyo')).isoformat(timespec='seconds')
'2021-08-24T23:03:38+09:00'
datetime.isoformat()
で取得することができます。非常に簡単です。
…では、 pytz.timezone(tzstring) は何を返しているのでしょうか。
まず pytz.timezone()
の中身を見てみましょう。
>>> pytz.timezone('Asia/Tokyo')
<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>
>>> type(pytz.timezone('Asia/Tokyo'))
<class 'pytz.tzfile.Asia/Tokyo'>
pytz.tzfile.Asia/Tokyo
が返っているようです。
datetime.now()
の引数tz
が受け取るのは Optional[_tzinfo]
であり、
datetimeのソースから_tzinfo
はtzinfo
つまりdatetime.tzinfo
であることがわかります。
つまり issubclass(type(pytz.timezone('Asia/Tokyo')), datetime.tzinfo) == True
であるはずです:
from datetime import tzinfo
>>> issubclass(type(pytz.timezone('Asia/Tokyo')), tzinfo)
True
ドキュメントを見ると、
class datetime.tzinfo このクラスは抽象基底クラスで、直接インスタンス化すべきでないことを意味します。 tzinfo のサブクラスを定義し、ある特定のタイムゾーンに関する情報を保持するようにしてください。
tzinfo (の具体的なサブクラス) のインスタンスは datetime および time オブジェクトのコンストラクタに渡すことができます。後者のオブジェクトでは、データ属性をローカル時刻におけるものとして見ており、 tzinfo オブジェクトはローカル時刻の UTC からのオフセット、タイムゾーンの名前、 DST オフセットを、渡された日付および時刻オブジェクトからの相対で示すためのメソッドを提供します。
ということなので、
datetime.datetime
にタイムゾーンを渡すにはdatetime.tzinfo
を渡せばよい- しかし
datetime.tzinfo
をそのまま渡すことはできず、サブクラス化する必要がある pytz
は各地のタイムゾーンをdatetime.tzinfo
形式で返してくれるライブラリ
ということがわかります。
つまりpytzが特別なライブラリというわけではなく、単純にtzinfo
を返すクラスを自分で作れれば
タイムゾーンを自由に定義できるということになります。
作ってみましょう。
from datetime import datetime, timedelta, tzinfo
from typing import Optional
class NewGeneration(tzinfo):
def utcoffset(self, dt: Optional[datetime]) -> Optional[timedelta]:
return timedelta(hours=23)
def dst(self, dt: Optional[datetime]) -> Optional[timedelta]:
return timedelta(0)
def tzname(self, dt: Optional[datetime]) -> Optional[str]:
return "NEW☆GENERATION"
できました。 UTC+23:00
とかいう狂気のタイムゾーン です。どこに位置してるんだ。
これを使ってdatetime
を初期化すると…
>>> date_newzone = datetime.now(tz=NewGeneration())
>>> date_newzone.tzinfo.tzname(None)
'NEW☆GENERATION'
>>> date_newzone.isoformat(timespec='seconds')
'2021-08-25T13:23:30+23:00'
自作タイムゾーンがdatetime
により保持されていることがわかります。
標準のdatetime.timezone
モジュールを使うのが一番早いと思います。