Next / Previous / Contents / TCC Help System / NM Tech homepage

9.12. class USTimeZone: Time zone with daylight time

This class implements the daylight saving time rules for the USA, honoring the change that went into effect in 2007. Dates before 2007 are assumed to use the old rules.

Daylight time always changes on a Sunday. To compute the dates of those Sundays in a particular year, we will need the following function. Given a datetime.date instance dt, it computes the date of the first Sunday on or after that date, and returns the result as a datetime.date.

sidereal.py
def firstSundayOnOrAfter ( dt ):
    """Find the first Sunday on or after a given date.

      [ dt is a datetime.date ->
          return a datetime.date representing the first Sunday
          on or after dt ]
    """

Because the .weekday() method returns a number that represents Sunday as 6, we subtract that number from 6 to get the number of days between dt and the next Sunday. Then we use a datetime.timedelta instance to add to dt to yield the Sunday date.

sidereal.py
    daysToGo  =  dt.weekday()
    if  daysToGo:
        dt  +=  datetime.timedelta ( daysToGo )
    return dt

Here's the class itself.

sidereal.py
# - - - - -   c l a s s   U S T i m e Z o n e

class USTimeZone(datetime.tzinfo):
    """Represents a U.S. time zone, with automatic daylight time.

      Exports:
        USTimeZone ( hh, mm, name, stdName, dstName ):
          [ (hh is an offset east of UTC in hours) and
            (mm is an offset east of UTC in minutes) and
            (name is the composite zone name) and
            (stdName is the non-DST name) and
            (dstName is the DST name) ->
              return a new USTimeZone instance with those values ]

      State/Invariants:
        .__offset:
          [ self's offset east of UTC as a datetime.timedelta ]
        .__name:      [ as passed to constructor's name ]
        .__stdName:   [ as passed to constructor's stdName ]
        .__dstName:   [ as passed to constructor's dstName ]
    """

This class is an adaptation of the USTimeZone class shown in the examples section of the reference material for datetime.tzinfo, with modifications for the new daylight time rules.

We'll need class variables for the calculation of the DST changeover dates. In each case, we want a date such that we can plug in the given year and send it to the firstSundayOnOrAfter() function above to yield a DST changeover date.

Here's the class constructor.

sidereal.py
    def __init__ ( self, hh, mm, name, stdName, dstName ):
        self.__offset   =  datetime.timedelta ( hours=hh, minutes=mm )
        self.__name     =  name
        self.__stdName  =  stdName
        self.__dstname  =  dstName

The .tzname() method returns the current name, which depends on whether the given time is DST or not.

sidereal.py
    def tzname(self, dt):
        if  self.dst(dt):   return self.__dstName
        else:               return self.__stdName

The .utcoffset() method returns the offset east of UTC, taking into account whether DST is in effect. We use the fact that a datetime.timedelta with a zero offset is treated as false in Boolean contexts.

sidereal.py
    def utcoffset(self, dt):
        return self.__offset + self.dst(dt)

The .dst() method returns an offset as a datetime.timedelta, and determines whether DST is in effect.

sidereal.py
    def dst(self, dt):
        """Return the current DST offset.

          [ dt is a datetime.date ->
              if  daylight time is in effect in self's zone on
              date dt ->
                return +1 hour as a datetime.timedelta
              else ->
                return 0 as a datetime.delta ]
        """

This implementation will ignore the .tzinfo attribute of the dt argument, and just use self's zone information.

First we must find the starting and ending Sundays for DST for the year given by dt. The .replace() method gives us a new datetime instance with the year taken from dt.

sidereal.py
        #-- 1 --
        # [ dtStart  :=  Sunday when DST starts in year dt.year
        #   dtEnd    :=  Sunday when DST ends in year dt.year ]
        if  dt.year >= 2007:
            startDate  =  self.DST_START_2007.replace ( year=dt.year )
            endDate  =  self.DST_END_2007.replace ( year=dt.year )
        else:
            startDate  =  self.DST_START_OLD.replace ( year=dt.year )
            endDate  =  self.DST_END_OLD.replace ( year=dt.year )
        dtStart  =  firstSundayOnOrAfter ( startDate )
        dtEnd    =  firstSundayOnOrAfter ( endDate )

The datetime module does not allow naive times to be compared to aware times, so we'll make a copy of dt with its time zone information removed.

sidereal.py
        #-- 2 --
        # [ naiveDate  :=  dt with its tzinfo member set to None ]
        naiveDate  =  dt.replace ( tzinfo=None )

Now we can find out if naiveDate is within the DST range, and return the appropriate offset.

sidereal.py
        #-- 3 --
        # [ if naiveDate is in the interval (dtStart, dtEnd) ->
        #     return DELTA_HOUR
        #   else ->
        #     return DELTA_ZERO ]
        if  dtStart <= naiveDate < dtEnd:
            return  DELTA_HOUR
        else:
            return  DELTA_ZERO