## Saturday, July 18, 2009

### Python Date class

I doubt whether the world really needs another one, but here goes. This post contains code for a Date class in Python. The class instances know how to calculate the day of the week, and they also know how to calculate the number of days separating two dates. This is all pretty standard.

What may be of interest is the method. The fundamental data structure is a list containing one element for each year since the year 1, with an extra 0 at the beginning to allow use of 1-based indexing. Each element in this list is itself a list of the number of days for each month of that particular year. We calculate this once, when the first Date object is instantiated. (Unless a date beyond 2050 is desired, then the list is extended as far as needed). We also cache the total number of days for each year in another list, to reduce calculation.

Using these two lists, it is trivial to calculate for any date the number of days since Jan 1 of the year 1 (a Saturday), and then, the day of the week is a simple mod 7 operation.

Here is output from the Unix cal function:

 `\$ cal 9 1752 September 1752Su Mo Tu We Th Fr Sa 1 2 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30`

The month of September in the year 1752 was special (in Great Britain and the United States, at least). It is the month from which extra days were dropped in order to bring the calendar back into register with the earth's actual position, before changing from the Julian to the Gregorian calendar. (In Catholic Europe the change was made in October 1582, and only ten days were dropped, from 5 October 1582 to 14 October 1582).

We deal with this simply by making L[1752][9] = 19.

Here is a test function that exercises the class:

 `def test(): t1 = (1,1,1) t2 = (7,4,1776) t3 = (4,15,1865) t4 = (7,20,1969) t5 = (7,19,2009) t6 = (7,19,2637) L = [t1,t2,t3,t4,t5,t6] for t in L: d = Date(t) print d.wd, d d1 = Date(t5) d2 = Date(t4) print d1 - d2`

And the output:

 `Sat Jan 01, 1Thu Jul 04,1776Sat Apr 15,1865Sun Jul 20,1969Sun Jul 19,2009Tue Jul 19,263714609`

14,609 days since the first moonwalk. I watched on TV!

I do hope our descendants are still here in 2637. Update: there seems to be a bug, either in my code or the cal function. It shows July 19, 2637 as a Wed. All the other dates check out. Ideas?

The entire listing:

 `class Date: # 4 class variables mlen = [0,31,28,31,30,31,30, 31,31,30,31,30,31 ] day = {1:'Sat',2:'Sun', 3:'Mon',4:'Tue', 5:'Wed',6:'Thu', 7:'Fri'} # a list of month lengths, indexed by year yL = None # the sum of yL elements, indexed by year ylen = None def __init__(self,t): m,d,y = t self.month = m self.day = d self.year = y if not Date.yL: Date.yL = self.populateYList() if y > 2050: Date.yL = self.populateYList(y+1) if not Date.ylen: Date.ylen = [ sum(mL) for mL in Date.yL] self.n = self.daynumber() self.wd = self.dayofweek() def populateYList(self,N=2050): yL = [[0]] for year in range(1,N): months = Date.mlen[:] if year < 1752: # Julian if not year % 4: months[2] = 29 elif year == 1752: # special months[2] = 29 months[9] = 19 else: # Gregorian if not year % 400: months[2] = 29 elif not year % 100: pass elif not year % 4: months[2] = 29 yL.append(months) return yL def daynumber(self): # should guard against Sep 1752 n = self.day n += sum(Date.ylen[:self.year]) mlen = Date.yL[self.year] n += sum(mlen[:self.month]) return n def dayofweek(self): #01 Jan of year 1 was a Sat r = self.n % 7 return Date.day[r] def __sub__(self,other): n1 = self.n n2 = other.n if n2 > n1: return n2 - n1 return n1 - n2 def __repr__(self): D = [0,'Jan','Feb','Mar','Apr', 'May','Jun','Jul','Aug', 'Sep','Oct','Nov','Dec'] y = str(self.year) d = self.day if d < 10: sd = '0' + str(d) else: sd = str(d) s = D[self.month] + ' ' return s + sd + ',' + y.rjust(4)#=============================def test(): t1 = (1,1,1) t2 = (7,4,1776) t3 = (4,15,1865) t4 = (7,20,1969) t5 = (7,19,2009) t6 = (7,19,2637) L = [t1,t2,t3,t4,t5,t6] for t in L: d = Date(t) print d.wd, d d1 = Date(t5) d2 = Date(t4) print d1 - d2if __name__ == '__main__': test()`