datetimeとpytzでタイムゾーンを変換する


Python標準ライブラリにはtime、datetime、calendarの3つのモジュールが提供されており、その中で最も広く応用されているのはdatetimeであり、変換タイムゾーンもそれによって行われています.
タイムゾーンというものはとても抽象的で、それを処理する時よくめまいがして、後でめまいを起こさないように記録するしかありません.
まずタイムゾーン間の転換関係を知る必要がありますが、実はこれは簡単です.現地時間を現地タイムゾーンから差し引いて、残りはグリニッジ時間です.例えば北京時間の18:00が18:00+08:00で、減ってから10:00+00:00なので、グリニッジ時間の10:00です.
グリニッジ時間に現地タイムゾーンを加えると、現地時間が得られます.例えばグリニッジ時間の10:00は10:00+00:00で、太平洋標準時間に変換すると-8時間加算されるので02:00-08:00です.
太平洋の標準時間を北京時間に変換するのも同じで、時間帯が減らせばいい.例えば太平洋標準時の02:00-08:00は北京時間と-16時間の差があるため、結果は18:00+08:00となった.
Pythonのdatetimeではoffset-naiveとoffset-awareの2種類の時間を処理できます.前者はタイムゾーン情報が含まれていない時間を指し、後者はタイムゾーン情報が含まれている時間を指し、同じタイプの時間だけ減算と比較を行うことができる.
残念なことに、datetimeモジュールの関数は、now()、utcnow()、fromtimestamp()、utcfromtimestamp()、strftime()などのoffset-naiveタイプのdatetimeオブジェクトのみをデフォルトで生成します.ここでnow()とfromtimestamp()は、offset-awareタイプのdatetimeオブジェクトを生成するためにtzinfoオブジェクトを受け入れることができますが、標準ライブラリは実装済みのtzinfoクラスを提供しておらず、自分で衣食を豊かにするしかありません...次はグリニッジ時間と北京時間のtzinfoクラスを実現する例です.
ZERO_TIME_DELTA = timedelta(0)
LOCAL_TIME_DELTA = timedelta(hours=8) #       

class UTC(tzinfo):
    def utcoffset(self, dt):
        return ZERO_TIME_DELTA

    def dst(self, dt):
        return ZERO_TIME_DELTA

class LocalTimezone(tzinfo):
    def utcoffset(self, dt):
        return LOCAL_TIME_DELTA

    def dst(self, dt):
        return ZERO_TIME_DELTA

    def tzname(self, dt):
        return '+08:00'

1つのtzinfoクラスはutcoffset,dst,tznameの3つの方法を実現する必要がある.ここでutcoffsetは夏の時差調整に戻る必要がある.tznameはタイムゾーン名を返す必要があります.もしあなたが使う必要がなければ、実現しなくてもいいです.
offset-awareタイプのdatetimeオブジェクトを生成すると、astimezone()メソッドを呼び出し、他のタイムゾーンの時間を生成することができます(時差に基づいて計算されます).offset-naiveタイプのdatetimeオブジェクトを取得した場合も、tzinfoを置き換えるためにreplace()メソッドを呼び出すことができますが、この置き換えは時差に応じて他の時間属性を調整しません.したがって、グリニッジ時間のoffset-naiveタイプのdatetimeオブジェクトを取得した場合、replace(tzinfo=UTC()を直接呼び出すとoffset-awareタイプに変換され、astimezone()を呼び出して他のタイムゾーンのdatetimeオブジェクトを生成します.一方、+6:00タイムゾーンのoffset-naiveタイプのdatetimeオブジェクトの場合、+6:00タイムゾーンのtzinfoクラスを作成し、上記のように変換できます.逆にoffset-awareタイプをoffset-naiveタイプに変換する場合は、混ざらないようにastimezone(UTC())でグリニッジ時間を生成してからreplace(tzinfo=None)することをお勧めします.
すべてが簡単に見えるが、上記の夏の季節を覚えているかどうかは分からない.夏の季節というものといえば、本当に頭が痛いです.ルールがないからです.夏の季節を実行している国もあれば、実行していない国もあります.一部の地域では夏の季節だけを実行している国もあります.一部の地域では、夏の季節だけを実行している地域もあります.各地域では夏の季節の開始時間は必ずしも同じではありません.また、TMDは数ヶ月数日で夏の季節の開始時間を指定していないところもあります.ある月の何曜日かという形で、だから、このような一般的なdst方法を書くと人気が死ぬと思います.そこでpytzというサードパーティライブラリを探しました.△sourceforgeにダウンロードしないでください.そのバージョンは4年も更新されていません.深刻なバグがあります.
このpytzのドキュメントは最初は簡単に見えますが、pytzを使います.country_timezones('国家コード')はpytzでこの国のタイムゾーン名のリストを手に入れることができます.timezone('タイムゾーン名')では、tzinfoオブジェクトを取得できます.たとえば、中国の最初のタイムゾーンを取得してdatetimeオブジェクトを生成します.
>>> from datetime import datetime
>>> import pytz
>>> tz = pytz.timezone(pytz.country_timezones('cn')[0])
>>> tz
<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>
>>> datetime.now(tz)
datetime.datetime(2010, 12, 14, 19, 26, 12, 656000, tzinfo=<DstTzInfo 'Asia/Shan
ghai' CST+8:00:00 STD>)

しかし、それは奇妙な罠があって、そのtzに注意して、それは実際に+8:06:00で、北京の時間より6分速いです.さらに薄っぺらなのは、中国のタイムゾーンでは北京時間が見つからず、上海時間を数えるしかありません...普段使っているときは問題ないかもしれませんが、datetimeオブジェクトを構築したり、replaceメソッドを呼び出したりすると、わけがわからず6分も差があります.
>>> dt = datetime.now(tz)
>>> dt
datetime.datetime(2010, 12, 14, 19, 32, 23, 281000, tzinfo=<DstTzInfo 'Asia/Shan
ghai' CST+8:00:00 STD>)
>>> dt2 = datetime(2010, 12, 14, 19, 32, 23, 281000, tzinfo=tz)
>>> dt2
datetime.datetime(2010, 12, 14, 19, 32, 23, 281000, tzinfo=<DstTzInfo 'Asia/Shan
ghai' LMT+8:06:00 STD>)
>>> dt == dt2
False
>>> dt - dt2
datetime.timedelta(0, 360)
>>> dt.tzinfo
<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>
>>> dt2.tzinfo
<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>

同じtzで生成されたdatetimeオブジェクトでは、等しくない結果が得られ、tzinfoも異なる...
この問題を解決するには、台北(Asia/Taipei)時間を使うのが一番簡単です.ちょうど+08:00です.しかし、このように根本を治すことはできません.結局、まだ整点ではないタイムゾーンがたくさんあります.一つ一つ代替タイムゾーンを探すことはできません.実際にlimodouは『pytzに関するいくつかの記録(続き)』という文章でもこの問題に言及し、構築時にoffset-naiveタイプのdatetimeオブジェクトを生成し、tzを用いることを指摘した.localize(dt)は正しい時間を生成します.
>>> dt3 = datetime(2010, 12, 14, 19, 32, 23, 281000)
>>> dt3 = tz.localize(dt3)
>>> dt3
datetime.datetime(2010, 12, 14, 19, 32, 23, 281000, tzinfo=<DstTzInfo 'Asia/Shan
ghai' CST+8:00:00 STD>)
>>> dt == dt3
True

最後に、夏の季節を処理する重要なコードです.normalizeメソッドを使用する必要があります.
>>> eastern = pytz.timezone('US/Eastern')
>>> dt4 = datetime(2002, 10, 27, 1, 0)
>>> dt4 = eastern.localize(dt4)
>>> print dt4
2002-10-27 01:00:00-05:00
>>> dt5 = dt4 - timedelta(minutes=10)
>>> print dt5
2002-10-27 00:50:00-05:00
>>> dt6 = eastern.normalize(dt5)
>>> print dt6
2002-10-27 01:50:00-04:00
>>> dt5.tzinfo
<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>
>>> dt6.tzinfo
<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>

normalize以降、夏の季節の変更を正しく処理できるようになりました.