Date type

Armin Wittfoth armin_wittfoth at yahoo.com.au
Thu May 22 03:27:46 EDT 2003


"Batista, Facundo" <FBatista at uniFON.com.ar> wrote in message news:<mailman.1053095964.23749.python-list at python.org>...
> I need a "date type" and I can t find it anywhere.

I recently wrote such a Date type, you might be able to use my code as
a starting point.  Since 'Date' is subclassed to the built-in 'int',
you will have to be using a version of Python that can subclass
built-ins (which is when Python really made it imho).  Date,
understands dates in ISO-8601 'internal' (ie sans hyphens, eg
20030522) format, and behaves just like an int except in these
regards:  it will only accept a integer that represents a valid date,
adding or subtracting n to it will result in the date +/- n days from
the original date (ie 20010101 - 1 = 19991231, not 20010100) and it
can be pretty printed.  Oh yeah, and it has the attributes, year,
month and day, that you were after.  However, it doesn't to time,
sorry.

This code could be improved upon!  A lot. (Any suggestions gratefully
considered) In particular '__add__' and '__sub__' work iteratively,
whereas you (or someone else here) should be able to work out a
constant time solution.  The reason for this is that my application
only ever has to find the tomorrow or yesterday of any given date and
it was easier to write this way.  With little extra effort (put the
super call in __init__ after the, slightly amended regexp etc.), you
should get it to accept iso8601 'standard' (with hyphens) format date
strings as well.  (This version requires an integer argument).  You
would also want to rewrite the pretty print method to get the kind of
output you were after (though I note there you are also interested in
outputting time info, a lot more work there).


class Date (int) :

    def __init__ (self, val) :
        super(int, self).__init__(val)
        match = re.search('^(\d{1,4})(\d{2})(\d{2})$', str(self))
        if match :
            year, month, day = match.groups()
            self.year = int(year)
            self.month = int(month)
            self.day = int(day)
            if not self._isdate() :
                raise InvalidDate
        else :
            raise InvalidDateFormat

    def __add__ (self, incr) :
        year = self.year
        month = self.month

        day = self.day
        for i in range(incr) :
            #max = calendar.monthrange(year, month)[1]
            max = self.daysofmonth(year, month)
            day, carry = self._incrDay(day, max)
            if carry :
                month, carry = self._incrMonth(month)
                if carry :
                    year += 1
        return Date(int("%04d%02d%02d" % (year, month, day)))

    def _incrDay (self, day, max) :
        m = 0
        day += 1
        if day  > max :
            day -= max
            m = 1
        return (day, m)

    def _incrMonth (self, month) :
        y = 0
        month += 1
        if month > 12 :
            month -= 12
            y = 1
        return month, y

    def __sub__ (self, decr) :
        year = self.year
        month = self.month
        day = self.day
        for i in range(decr) :
            prev_month, carry = self._decrMonth(month)
            p_year = carry and year - 1 or year
            #max = calendar.monthrange(p_year, prev_month)[1]
            max = self.daysofmonth(p_year, prev_month)       
            day, carry = self._decrDay(day, max)
            if carry :
                month, carry = self._decrMonth(month) 
                if carry :
                    year -= 1
        return Date(int("%04d%02d%02d" % (year, month, day)))

    def _decrDay (self, day, max) :
        m = 0
        day -= 1
        if day < 1 :
            day += max
            m = 1
        return (day, m)

    def _decrMonth (self, month) :
        y = 0 
        month -= 1
        if month < 1 :
            month += 12
            y = 1
        return (month, y)

    def daysofmonth (self, year, month) :
        #does what calendar.monthrange(yr, mn)[1] should do.
        #in 2.2 monthrange seems to be broken for years before 1970.  
        #Fixed in 2.3 :) 
        monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        if month == 2 and calendar.isleap(year) :
            max = 29
        else :
            max = monthdays[month - 1]
        return max

    def _isdate (self) :
        if not 1 <= self.month <= 12 :
            ret = False
        else :
            ret = 1 <= self.day <= self.daysofmonth(self.year,
self.month)
        return ret

    def pretty (self) :
        months = { '01' : 'January',
                   '02' : 'February',
                   '03' : 'March',
                   '04' : 'April',
                   '05' : 'May',
                   '06' : 'June',
                   '07' : 'July',
                   '08' : 'August',
                   '09' : 'September',
                   '10' : 'October',
                   '11' : 'November',
                   '12' : 'December'}
        found = re.search('(\d{1,4})(\d{2})(\d{2})', str(self))
        if found :
            (year, month, day) = found.groups()
            nmonth = months[month]
            nday = int(day)
            return "%s %s %s" % (nday, nmonth, year)
        else :
            raise TypeError

Obviously you'll need to import 're' and 'calendar' as well as create
the 'InvalidDate' and 'InvalidDateFormat' exceptions (which I have
subclassed to nothing more imaginative than StandardError).

Sample usage:
>>> today = Date('%04d%02d%02d' % time.localtime()[:3])
>>> today
20030522
>>> today + 14
20030605
>>> (today - 1).pretty()
'21 May 2003'
>>>


cheers,
Armin




More information about the Python-list mailing list