tzinfo は抽象基底クラスです。つまり、このクラスは直接 インスタンス化して利用しません。具体的なサブクラスを導出し、 (少なくとも) 利用したい datetime のメソッドが必要と する tzinfo の標準メソッドを実装してやる必要があります。 datetime モジュールでは、tzinfo の具体的な サブクラスは何ら提供していません。
tzinfo (の具体的なサブクラス) のインスタンスは datetime および time オブジェクトのコンストラクタに 渡すことができます。 後者のオブジェクトでは、データメンバをローカル時刻におけるものとして 見ており、tzinfo オブジェクトはローカル時刻の UTC からの オフセット、タイムゾーンの名前、DST オフセットを、渡された 日付および時刻オブジェクトからの相対で示すためのメソッドを 提供します。
pickle 化についての特殊な要求事項: tzinfo のサブクラスは 引数なしで呼び出すことのできる __init__ メソッドを持たねば なりません。そうでなければ、pickle 化することはできますがおそらく unpickle 化することはできないでしょう。これは技術的な側面からの 要求であり、将来緩和されるかもしれません。
tzinfo の具体的なサブクラスでは、以下のメソッドを 実装する必要があります。厳密にどのメソッドが必要なのかは、 aware な datetime オブジェクトがこのサブクラスの インスタンスをどのように使うかに依存します。不確かならば、 単に全てを実装してください。
self, dt) |
None
を返してください。そうでない場合には、
返される値は -1439 から 1439 の両端を含む値 (1440 = 24*60 ;
つまり、オフセットの大きさは 1 日より短くなくてはなりません)
が分で指定された timedelta オブジェクトでなければなりません。
ほとんどの utcoffset() 実装は、おそらく以下の二つのうちの一つに
似たものになるでしょう:
return CONSTANT # fixed-offset class return CONSTANT + self.dst(dt) # daylight-aware class
utcoffset() が None
を返さない場合、
dst() も None
を返してはなりません。
utcoffset() のデフォルトの実装は NotImplementedError を送出します。
self, dt) |
None
が返されます。
DST が有効でない場合には timedelta(0)
を返します。
DST が有効の場合、オフセットは timedelta オブジェクト
で返します (詳細はutcoffset() を参照してください)。
DST オフセットが利用可能な場合、この値は utcoffset()
が返すUTC からのオフセットには既に加算されているため、
DST を個別に取得する必要がない限り dst() を使って
問い合わせる必要はないので注意してください。
例えば、datetime.timetuple() は tzinfo メンバ
の dst() メソッドを呼んで tm_isdst フラグが
セットされているかどうか判断し、tzinfo.fromutc()
は dst() タイムゾーンを移動する際に DST による変更
があるかどうかを調べます。
標準および夏時間の両方をモデル化している tzinfo サブクラスの インスタンス tz は以下の式:
tz.utcoffset(dt) - tz.dst(dt)
が、dt.tzinfo == tz
全ての datetime オブジェクト
dt について常に同じ結果を返さなければならないという点で、
一貫性を持っていなければなりません。
正常に実装された tzinfo のサブクラスでは、この式は
タイムゾーンにおける "標準オフセット (standard offset)" を表し、
特定の日や時刻の事情ではなく地理的な位置にのみ依存していなくては
なりません。datetime.astimezone() の実装はこの事実に
依存していますが、違反を検出することができません;
正しく実装するのはプログラマの責任です。tzinfo の
サブクラスでこれを保証することができない場合、tzinfo.fromutc()
の実装をオーバライドして、astimezone() に関わらず
正しく動作するようにしてもかまいません。
ほとんどの dst() 実装は、おそらく以下の二つのうちの一つに 似たものになるでしょう:
def dst(self): # a fixed-offset class: doesn't account for DST return timedelta(0)
or
def dst(self): # Code to set dston and dstoff to the time zone's DST # transition times based on the input dt.year, and expressed # in standard local time. Then if dston <= dt.replace(tzinfo=None) < dstoff: return timedelta(hours=1) else: return timedelta(0)
デフォルトの dst() 実装は NotImplementedError を送出します。
self, dt) |
None
を返してください。
tzinfo のサブクラスでは、
特に、tzinfo
クラスが夏時間について記述している場合のように、
渡された dt の特定の値によって異なった名前を返したい
場合があるため、文字列値ではなくメソッドとなっていることに注意してください。
デフォルトの tzname() 実装は NotImplementedError を送出します。
以下のメソッドは datetime や time オブジェクトにおいて、
同名のメソッドが呼び出された際に応じて呼び出されます。datetime
オブジェクトは自身を引数としてメソッドに渡し、time オブジェクトは
引数として None
をメソッドに渡します。従って、tzinfo の
サブクラスにおけるメソッドは引数 dt が None
の場合と、
datetime の場合を受理するように用意しなければなりません。
None
が渡された場合、最良の応答方法を決めるのはクラス設計者次第
です。例えば、このクラスが tzinfo プロトコルと関係をもたない
ということを表明させたければ、None
が適切です。
標準時のオフセットを見つける他の手段がない場合には、
標準 UTC オフセットを返すために utcoffset(None)
を使うともっと便利かもしれません。
datetime オブジェクトが datetime メソッド
の応答として返された場合、dt.tzinfo
は self
と同じオブジェクトになります。ユーザが直接 tzinfo メソッド
を呼び出さないかぎり、tzinfo メソッドは dt.tzinfo
と self が同じであることに依存します。
その結果 tzinfo メソッドは dt がローカル時間であると
解釈するので、他のタイムゾーンでのオブジェクトの振る舞いについて
心配する必要がありません。
self, dt) |
dt.tzinfo
は self であり、 dt の日付および時刻データメンバは
UTC 時刻を表しているものとして見えます。fromutc()
の目的は、self のローカル時刻に等しい datetime オブジェクト
を返すことにより日付と時刻データメンバを修正することにあります。
ほとんどの tzinfo サブクラスではデフォルトの fromutc() 実装を問題なく継承できます。デフォルトの実装は、固定オフセットのタイムゾーン や、標準時と夏時間の両方について記述しているタイムゾーン、そして DST 移行時刻が年によって異なる場合でさえ、扱えるくらい強力なものです。 デフォルトの fromutc() 実装が全ての場合に対して正しく 扱うことができないような例は、標準時の (UTCからの) オフセットが 引数として渡された特定の日や時刻に依存するもので、これは政治的な理由に よって起きることがあります。 デフォルトの astimezone() や fromutc() の実装は、 結果が標準時オフセットの変化にまたがる何時間かの中にある場合、 期待通りの結果を生成しないかもしれません。
エラーの場合のためのコードを除き、デフォルトの fromutc() の 実装は以下のように動作します:
def fromutc(self, dt): # raise ValueError error if dt.tzinfo is not self dtoff = dt.utcoffset() dtdst = dt.dst() # raise ValueError if dtoff is None or dtdst is None delta = dtoff - dtdst # this is self's standard offset if delta: dt += delta # convert to standard local time dtdst = dt.dst() # raise ValueError if dtdst is None if dtdst: return dt + dtdst else: return dt
以下に tzinfo クラスの使用例を示します:
from datetime import tzinfo, timedelta, datetime ZERO = timedelta(0) HOUR = timedelta(hours=1) # A UTC class. class UTC(tzinfo): """UTC""" def utcoffset(self, dt): return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): return ZERO utc = UTC() # A class building tzinfo objects for fixed-offset time zones. # Note that FixedOffset(0, "UTC") is a different way to build a # UTC tzinfo object. class FixedOffset(tzinfo): """Fixed offset in minutes east from UTC.""" def __init__(self, offset, name): self.__offset = timedelta(minutes = offset) self.__name = name def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return ZERO # A class capturing the platform's idea of local time. import time as _time STDOFFSET = timedelta(seconds = -_time.timezone) if _time.daylight: DSTOFFSET = timedelta(seconds = -_time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET class LocalTimezone(tzinfo): def utcoffset(self, dt): if self._isdst(dt): return DSTOFFSET else: return STDOFFSET def dst(self, dt): if self._isdst(dt): return DSTDIFF else: return ZERO def tzname(self, dt): return _time.tzname[self._isdst(dt)] def _isdst(self, dt): tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = _time.mktime(tt) tt = _time.localtime(stamp) return tt.tm_isdst > 0 Local = LocalTimezone() # A complete implementation of current DST rules for major US time zones. def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += timedelta(days_to_go) return dt # In the US, DST starts at 2am (standard time) on the first Sunday in April. DSTSTART = datetime(1, 4, 1, 2) # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. # which is the first Sunday on or after Oct 25. DSTEND = datetime(1, 10, 25, 1) class USTimeZone(tzinfo): def __init__(self, hours, reprname, stdname, dstname): self.stdoffset = timedelta(hours=hours) self.reprname = reprname self.stdname = stdname self.dstname = dstname def __repr__(self): return self.reprname def tzname(self, dt): if self.dst(dt): return self.dstname else: return self.stdname def utcoffset(self, dt): return self.stdoffset + self.dst(dt) def dst(self, dt): if dt is None or dt.tzinfo is None: # An exception may be sensible here, in one or both cases. # It depends on how you want to treat them. The default # fromutc() implementation (called by the default astimezone() # implementation) passes a datetime with dt.tzinfo is self. return ZERO assert dt.tzinfo is self # Find first Sunday in April & the last in October. start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) # Can't compare naive to aware objects, so strip the timezone from # dt first. if start <= dt.replace(tzinfo=None) < end: return HOUR else: return ZERO Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") Central = USTimeZone(-6, "Central", "CST", "CDT") Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
標準時間 (standard time) および夏時間 (daylight time) の両方を 記述している tzinfo のサブクラスでは、回避不能の難解な問題が年に 2 度あるので注意してください。具体的な例として、東部アメリカ時刻 (US Eastern, UTC -5000) を考えます。EDT は 4 月の最初の日曜日 の 1:59 (EST) 以後に開始し、10 月の最後の日曜日の 1:59 (EDT) に 終了します:
UTC 3:MM 4:MM 5:MM 6:MM 7:MM 8:MM EST 22:MM 23:MM 0:MM 1:MM 2:MM 3:MM EDT 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM start 22:MM 23:MM 0:MM 1:MM 3:MM 4:MM end 23:MM 0:MM 1:MM 1:MM 2:MM 3:MM
DST の開始の際 ("start" の並び) ローカルの壁時計は 1:59 から
3:00 に飛びます。この日は 2:MM の形式をとる時刻は実際には無意味と
なります。従って、astimezone(Eastern)
は DST が開始する
日には hour == 2
となる結果を返すことはありません。
astimezone() がこのことを保証するようにするには、
tzinfo.dst() メソッドは "失われた時間" (東部時刻における
2:MM) が夏時間に存在することを考えなければなりません。
DST が終了する際 ("end" の並び) では、問題はさらに悪化します: 1 時間の間、ローカルの壁時計ではっきりと時刻をいえなくなります: それは夏時間の最後の 1 時間です。東部時刻では、その日の UTC での 5:MM に夏時間は終了します。ローカルの壁時計は 1:59 (夏時間) から 1:00 (標準時) に再び巻き戻されます。ローカルの時刻に おける 1:MM はあいまいになります。astimezone() は二つの UTC 時刻を同じローカルの時刻に対応付けることで ローカルの時計の振る舞いをまねます。 東部時刻の例では、5:MM および 6:MM の形式をとる UTC 時刻は 両方とも、東部時刻に変換された際に 1:MM に対応づけられます。 astimezone() がこのことを保証するようにするには、 tzinfo.dst() は "繰り返された時間" が標準時に存在する ことを考慮しなければなりません。このことは、例えばタイムゾーンの標準の ローカルな時刻に DST への切り替え時刻を表現することで簡単に設定する ことができます。
このようなあいまいさを許容できないアプリケーションは、 ハイブリッドな tzinfo サブクラスを使って問題を回避しなければ なりません; UTC や、他のオフセットが固定された tzinfo の サブクラス (EST (-5 時間の固定オフセット) のみを表すクラスや、 EDT (-4 時間の固定オフセット) のみを表すクラス) を使う限り、あいまいさは 発生しません。
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。