6.10.6 tzinfo オブジェクト

tzinfo は抽象基底クラスです。つまり、このクラスは直接 インスタンス化して利用しません。具体的なサブクラスを導出し、 (少なくとも) 利用したい datetime のメソッドが必要と する tzinfo の標準メソッドを実装してやる必要があります。 datetime モジュールでは、tzinfo の具体的な サブクラスは何ら提供していません。

tzinfo (の具体的なサブクラス) のインスタンスは datetime および time オブジェクトのコンストラクタに 渡すことができます。 後者のオブジェクトでは、データメンバをローカル時刻におけるものとして 見ており、tzinfo オブジェクトはローカル時刻の UTC からの オフセット、タイムゾーンの名前、DST オフセットを、渡された 日付および時刻オブジェクトからの相対で示すためのメソッドを 提供します。

pickle 化についての特殊な要求事項: tzinfo のサブクラスは 引数なしで呼び出すことのできる __init__ メソッドを持たねば なりません。そうでなければ、pickle 化することはできますがおそらく unpickle 化することはできないでしょう。これは技術的な側面からの 要求であり、将来緩和されるかもしれません。

tzinfo の具体的なサブクラスでは、以下のメソッドを 実装する必要があります。厳密にどのメソッドが必要なのかは、 aware な datetime オブジェクトがこのサブクラスの インスタンスをどのように使うかに依存します。不確かならば、 単に全てを実装してください。

utcoffset( self, dt)
ローカル時間の UTC からのオフセットを、UTC から東向きを正とした分で 返します。ローカル時間が UTC の西側にある場合、この値は負になります。 このメソッドは UTC からのオフセットの総計を返すように意図されている ので注意してください; 例えば、 tzinfo オブジェクトが タイムゾーンと DST 修正の両方を表現する場合、utcoffset() はそれらの合計を返さなければなりません。UTC オフセットが未知である 場合、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 を送出します。

dst( self, dt)
夏時間 (DST) 修正を、UTC から東向きを正とした分で 返します。DST 情報が未知の場合、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 を送出します。

tzname( self, dt)
datetime オブジェクト dt に対応するタイムゾーン名 を文字列で返します。 datetime モジュールでは文字列名について何も定義しておらず、 特に何かを意味するといった要求仕様もまったくありません。 例えば、"GMT"、"UTC"、 "-500"、 "-5:00"、 "EDT"、 "US/Eastern"、 "America/New York" は全て有効な応答となります。 文字列名が未知の場合には None を返してください。 tzinfo のサブクラスでは、 特に、tzinfo クラスが夏時間について記述している場合のように、 渡された dt の特定の値によって異なった名前を返したい 場合があるため、文字列値ではなくメソッドとなっていることに注意してください。

デフォルトの tzname() 実装は NotImplementedError を送出します。

以下のメソッドは datetimetime オブジェクトにおいて、 同名のメソッドが呼び出された際に応じて呼び出されます。datetime オブジェクトは自身を引数としてメソッドに渡し、time オブジェクトは 引数として None をメソッドに渡します。従って、tzinfo の サブクラスにおけるメソッドは引数 dtNone の場合と、 datetime の場合を受理するように用意しなければなりません。

None が渡された場合、最良の応答方法を決めるのはクラス設計者次第 です。例えば、このクラスが tzinfo プロトコルと関係をもたない ということを表明させたければ、None が適切です。 標準時のオフセットを見つける他の手段がない場合には、 標準 UTC オフセットを返すために utcoffset(None) を使うともっと便利かもしれません。

datetime オブジェクトが datetime メソッド の応答として返された場合、dt.tzinfoself と同じオブジェクトになります。ユーザが直接 tzinfo メソッド を呼び出さないかぎり、tzinfo メソッドは dt.tzinfoself が同じであることに依存します。 その結果 tzinfo メソッドは dt がローカル時間であると 解釈するので、他のタイムゾーンでのオブジェクトの振る舞いについて 心配する必要がありません。

fromutc( self, dt)
デフォルトの datetime.astimezone() 実装で呼び出されます。 datetime.astimezone() から呼ばれた場合、dt.tzinfoself であり、 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 時間の固定オフセット) のみを表すクラス) を使う限り、あいまいさは 発生しません。

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。