[Python-checkins] python/nondist/sandbox/datetime EU.py,NONE,1.1

gvanrossum@users.sourceforge.net gvanrossum@users.sourceforge.net
Fri, 27 Dec 2002 19:53:55 -0800


Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory sc8-pr-cvs1:/tmp/cvs-serv5442

Added Files:
	EU.py 
Log Message:
A tzinfo object implementing the European DST rules.


--- NEW FILE: EU.py ---
"""A tzinfo object implementing the European DST rules.

See http://webexhibits.org/daylightsaving/g.html

Start: last Sunday in March at 1am UTC
End: last Sunday in October at 1am UTC

Note the subtle difference with the US: all European countries switch
to and from DST *at the same instant*, where in the US states in
different timezones switch at the same local time, which means that
while Eastern time is already on DST, Pacific time will be on normal
time three more hours.  (These are the EU rules; Russia switches at
2am local time.)  Another difference with the US is that the US
switches on the first Sunday in April.  Cuba switches on April
1st. :-)  All switch back on the same date.

"""

from datetime import date, time, timedelta, datetime, datetimetz, tzinfo

def last_sunday_in(year, month):
    assert month in [3, 10] # This only works for 31-day months!
    d = date(year, month, 31)
    w = d.weekday() # Mon = 0, Sun = 6
    if w != 6:
        d -= timedelta(days=w+1)
    return d

DAY = timedelta(days=1)
HOUR = timedelta(hours=1)
ZERO = timedelta()
ONE_AM = time(1)

class Fixed(tzinfo):

    def __init__(self, offset, name):
        self.offset = offset
        self.name = name

    def tzname(self, dt):
        return self.name

    def utcoffset(self, dt):
        return self.offset

    def dst(self, dt):
        return ZERO

class Europe(tzinfo):

    def __init__(self, offset, stdname, dstname):
        self.offset = offset
        self.stdname = stdname
        self.dstname = dstname

    def tzname(self, dt):
        if self.dst(dt):
            return self.dstname
        else:
            return self.stdname

    def utcoffset(self, dt):
        return self.offset + self.dst(dt)

    def dst(self, dt):
        dston = last_sunday_in(dt.year, 3)
        dstoff = last_sunday_in(dt.year, 10)
        d = dt.date()
        # Get a quick result on dates not near a DST switch:
        if dston < d < dstoff - DAY:
            return HOUR
        if d < dston - DAY or dstoff < d:
            return ZERO
        # Need to be more careful in edge cases:
        dston = datetime.combine(dston, ONE_AM)
        dstoff = datetime.combine(dstoff, ONE_AM)
        if dt.tzinfo is self:
            # Can use self.offset
            d = datetime.combine(dt.date(), dt.time()) - self.offset
        else:
            # Can call dt.tzoffset() without risking recursion
            d = datetime.combine(dt.date(), dt.time()) - dt.utcoffset()
        if dston <= d < dstoff:
            return HOUR
        else:
            return ZERO

UTC = Fixed(ZERO, "UTC")
London = Europe(ZERO, "WET", "WDT")
Amsterdam = Europe(HOUR, "MET", "MDT")

demo = """
We start easy, Saturday noon in Amsterdam on the last day of DST:

>>> dt = datetimetz(2002, 10, 26, 12, 0, 0, tzinfo=Amsterdam)
>>> dt.ctime()
'Sat Oct 26 12:00:00 2002'

The UTC offset is 2 hours:

>>> dt.utcoffset().seconds
7200

Now add one day, getting Sunday noon on the first day of standard time:

>>> dt += timedelta(days=1)
>>> dt.ctime()
'Sun Oct 27 12:00:00 2002'

The UTC offset is 1 hour:

>>> dt.utcoffset().seconds
3600

Even easier (but taking a different path through the code), the last
Wednesday before DST ends:

>>> dt = datetimetz(2002, 10, 23, 12, 0, 0, tzinfo=Amsterdam)
>>> dt.ctime()
'Wed Oct 23 12:00:00 2002'
>>> dt.utcoffset().seconds
7200

And a week later:

>>> dt += timedelta(days=7)
>>> dt.ctime()
'Wed Oct 30 12:00:00 2002'
>>> dt.utcoffset().seconds
3600

Now let's get real close to the end of DST -- in fact, one second
before it ends:

>>> dt = datetimetz(2002, 10, 27, 1, 59, 59, tzinfo=Amsterdam)
>>> dt.ctime() + ' ' + dt.tzname()
'Sun Oct 27 01:59:59 2002 MDT'
>>> dt.astimezone(UTC).ctime() + ' UTC'
'Sat Oct 26 23:59:59 2002 UTC'

Hey, that was actually an hour before it ended!  That's because the
last hour of DST is unrepresentable -- at 3am, the clock jumps back to
2am; but times between 2am and 3am are (arbitrarily) assumed to be
standard time.  Let's see what time it is in London at the same moment:

>>> dt1 = dt.astimezone(London)
>>> dt1.ctime() + ' ' + dt1.tzname()
'Sun Oct 27 00:59:59 2002 WDT'

Yes, in London it's also still DST, but the clock shows an hour
earlier (they are always an hour behind Amsterdam).

Now add one second, getting to 2am.

>>> dt += timedelta(seconds=1)

This lands us in standard time:

>>> dt.ctime() + ' ' + dt.tzname()
'Sun Oct 27 02:00:00 2002 MET'

Now it's 1am in UTC:

>>> dt.astimezone(UTC).ctime() + ' UTC'
'Sun Oct 27 01:00:00 2002 UTC'

And also 1am in London:

>>> dt1 = dt.astimezone(London)
>>> dt1.ctime() + ' ' + dt1.tzname()
'Sun Oct 27 01:00:00 2002 WET'

Paradox: did we lose an hour or gain an hour?  That depends on your
point of view.  (See also the discussion of the lost days on the
calendar in Thomas Pynchon's Mason & Dixon.)

"""
__test__ = {'demo': demo}

def _test():
    import doctest, EU
    return doctest.testmod(EU)

if __name__ == "__main__":
    _test()