blob: 63488ce80e131b483d58bf3e4fb8b808db09fa85 [file] [log] [blame]
Alexander Belopolsky401d8562010-07-03 22:05:41 +00001"""
2Class Date supplies date objects that support date arithmetic.
Guido van Rossum102abab1993-10-30 12:38:16 +00003
Alexander Belopolsky401d8562010-07-03 22:05:41 +00004Date(month,day,year) returns a Date object. An instance prints as,
5e.g., 'Mon 16 Aug 1993'.
Guido van Rossum102abab1993-10-30 12:38:16 +00006
Alexander Belopolsky401d8562010-07-03 22:05:41 +00007Addition, subtraction, comparison operators, min, max, and sorting
8all work as expected for date objects: int+date or date+int returns
9the date `int' days from `date'; date+date raises an exception;
10date-int returns the date `int' days before `date'; date2-date1 returns
11an integer, the number of days from date1 to date2; int-date raises an
12exception; date1 < date2 is true iff date1 occurs before date2 (&
13similarly for other comparisons); min(date1,date2) is the earlier of
14the two dates and max(date1,date2) the later; and date objects can be
15used as dictionary keys.
Guido van Rossum2e611031994-10-09 22:36:28 +000016
Alexander Belopolsky401d8562010-07-03 22:05:41 +000017Date objects support one visible method, date.weekday(). This returns
18the day of the week the date falls on, as a string.
19
20Date objects also have 4 read-only data attributes:
21 .month in 1..12
22 .day in 1..31
23 .year int or long int
24 .ord the ordinal of the date relative to an arbitrary staring point
25
26The Dates module also supplies function today(), which returns the
27current date as a date object.
28
29Those entranced by calendar trivia will be disappointed, as no attempt
30has been made to accommodate the Julian (etc) system. On the other
31hand, at least this package knows that 2000 is a leap year but 2100
32isn't, and works fine for years with a hundred decimal digits <wink>.
33
34Tim Peters tim@ksr.com
35not speaking for Kendall Square Research Corp
36
37Adapted to Python 1.1 (where some hacks to overcome coercion are unnecessary)
38by Guido van Rossum
39
40Note that as of Python 2.3, a datetime module is included in the stardard
41library.
42"""
Martin v. Löwis7890c262003-06-07 19:39:56 +000043
Alexander Belopolskybb3565d2010-07-03 21:42:47 +000044import functools
Guido van Rossum2e611031994-10-09 22:36:28 +000045
Guido van Rossum102abab1993-10-30 12:38:16 +000046_MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May',
Guido van Rossumdcd038f1998-09-14 15:34:45 +000047 'June', 'July', 'August', 'September', 'October',
48 'November', 'December' ]
Guido van Rossum102abab1993-10-30 12:38:16 +000049
50_DAY_NAMES = [ 'Friday', 'Saturday', 'Sunday', 'Monday',
Guido van Rossumdcd038f1998-09-14 15:34:45 +000051 'Tuesday', 'Wednesday', 'Thursday' ]
Guido van Rossum102abab1993-10-30 12:38:16 +000052
53_DAYS_IN_MONTH = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
54
55_DAYS_BEFORE_MONTH = []
56dbm = 0
57for dim in _DAYS_IN_MONTH:
58 _DAYS_BEFORE_MONTH.append(dbm)
59 dbm = dbm + dim
60del dbm, dim
61
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000062def _is_leap(year): # 1 if leap year, else 0
Guido van Rossum102abab1993-10-30 12:38:16 +000063 if year % 4 != 0: return 0
64 if year % 400 == 0: return 1
65 return year % 100 != 0
66
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000067def _days_in_year(year): # number of days in year
Guido van Rossum102abab1993-10-30 12:38:16 +000068 return 365 + _is_leap(year)
69
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000070def _days_before_year(year): # number of days before year
Benjamin Petersond7b03282008-09-13 15:58:53 +000071 return year*365 + (year+3)//4 - (year+99)//100 + (year+399)//400
Guido van Rossum102abab1993-10-30 12:38:16 +000072
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000073def _days_in_month(month, year): # number of days in month of year
Guido van Rossum102abab1993-10-30 12:38:16 +000074 if month == 2 and _is_leap(year): return 29
75 return _DAYS_IN_MONTH[month-1]
76
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000077def _days_before_month(month, year): # number of days in year before month
Guido van Rossum102abab1993-10-30 12:38:16 +000078 return _DAYS_BEFORE_MONTH[month-1] + (month > 2 and _is_leap(year))
79
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000080def _date2num(date): # compute ordinal of date.month,day,year
81 return _days_before_year(date.year) + \
82 _days_before_month(date.month, date.year) + \
Guido van Rossumdcd038f1998-09-14 15:34:45 +000083 date.day
Guido van Rossum102abab1993-10-30 12:38:16 +000084
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000085_DI400Y = _days_before_year(400) # number of days in 400 years
Guido van Rossum102abab1993-10-30 12:38:16 +000086
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000087def _num2date(n): # return date with ordinal n
Alexander Belopolskybb3565d2010-07-03 21:42:47 +000088 if not isinstance(n, int):
Collin Winter6f2df4d2007-07-17 20:59:35 +000089 raise TypeError('argument must be integer: %r' % type(n))
Guido van Rossum102abab1993-10-30 12:38:16 +000090
Alexander Belopolsky401d8562010-07-03 22:05:41 +000091 # Get uninitialized Date object. This is necesary because once
92 # attributes are set, they cannot be changed.
93 ans = Date.__new__(Date)
Guido van Rossum102abab1993-10-30 12:38:16 +000094 ans.ord = n
95
Benjamin Petersond7b03282008-09-13 15:58:53 +000096 n400 = (n-1)//_DI400Y # # of 400-year blocks preceding
Guido van Rossum102abab1993-10-30 12:38:16 +000097 year, n = 400 * n400, n - _DI400Y * n400
Benjamin Petersond7b03282008-09-13 15:58:53 +000098 more = n // 365
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000099 dby = _days_before_year(more)
Guido van Rossum102abab1993-10-30 12:38:16 +0000100 if dby >= n:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000101 more = more - 1
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000102 dby = dby - _days_in_year(more)
Alexander Belopolsky401d8562010-07-03 22:05:41 +0000103 year, n = year + more, n - dby
Benjamin Petersond7b03282008-09-13 15:58:53 +0000104 month = min(n//29 + 1, 12)
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000105 dbm = _days_before_month(month, year)
Guido van Rossum102abab1993-10-30 12:38:16 +0000106 if dbm >= n:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000107 month = month - 1
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000108 dbm = dbm - _days_in_month(month, year)
Guido van Rossum102abab1993-10-30 12:38:16 +0000109
110 ans.month, ans.day, ans.year = month, n-dbm, year
111 return ans
112
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000113def _num2day(n): # return weekday name of day with ordinal n
Alexander Belopolsky401d8562010-07-03 22:05:41 +0000114 return _DAY_NAMES[n % 7]
Guido van Rossum102abab1993-10-30 12:38:16 +0000115
Alexander Belopolskybb3565d2010-07-03 21:42:47 +0000116@functools.total_ordering
Guido van Rossum102abab1993-10-30 12:38:16 +0000117class Date:
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000118 def __init__(self, month, day, year):
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000119 if not 1 <= month <= 12:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000120 raise ValueError('month must be in 1..12: %r' % (month,))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000121 dim = _days_in_month(month, year)
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000122 if not 1 <= day <= dim:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000123 raise ValueError('day must be in 1..%r: %r' % (dim, day))
Alexander Belopolsky401d8562010-07-03 22:05:41 +0000124 self.month, self.day, self.year = map(int, (month, day, year))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000125 self.ord = _date2num(self)
Guido van Rossum102abab1993-10-30 12:38:16 +0000126
Guido van Rossum2e611031994-10-09 22:36:28 +0000127 # don't allow setting existing attributes
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000128 def __setattr__(self, name, value):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000129 if name in self.__dict__:
130 raise AttributeError('read-only attribute ' + name)
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000131 self.__dict__[name] = value
Guido van Rossum2e611031994-10-09 22:36:28 +0000132
Alexander Belopolskybb3565d2010-07-03 21:42:47 +0000133 def __eq__(self, other):
134 return self.ord == other.ord
135
136 def __lt__(self, other):
137 return self.ord < other.ord
Guido van Rossum102abab1993-10-30 12:38:16 +0000138
139 # define a hash function so dates can be used as dictionary keys
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000140 def __hash__(self):
141 return hash(self.ord)
Guido van Rossum102abab1993-10-30 12:38:16 +0000142
143 # print as, e.g., Mon 16 Aug 1993
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000144 def __repr__(self):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000145 return '%.3s %2d %.3s %r' % (
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000146 self.weekday(),
147 self.day,
Walter Dörwald70a6b492004-02-12 17:35:32 +0000148 _MONTH_NAMES[self.month-1],
149 self.year)
Guido van Rossum102abab1993-10-30 12:38:16 +0000150
Guido van Rossum2e611031994-10-09 22:36:28 +0000151 # Python 1.1 coerces neither int+date nor date+int
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000152 def __add__(self, n):
Alexander Belopolskybb3565d2010-07-03 21:42:47 +0000153 if not isinstance(n, int):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000154 raise TypeError('can\'t add %r to date' % type(n))
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000155 return _num2date(self.ord + n)
Guido van Rossum2e611031994-10-09 22:36:28 +0000156 __radd__ = __add__ # handle int+date
Guido van Rossum102abab1993-10-30 12:38:16 +0000157
Guido van Rossum2e611031994-10-09 22:36:28 +0000158 # Python 1.1 coerces neither date-int nor date-date
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000159 def __sub__(self, other):
Alexander Belopolskybb3565d2010-07-03 21:42:47 +0000160 if isinstance(other, int): # date-int
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000161 return _num2date(self.ord - other)
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000162 else:
163 return self.ord - other.ord # date-date
Guido van Rossum102abab1993-10-30 12:38:16 +0000164
Guido van Rossum2e611031994-10-09 22:36:28 +0000165 # complain about int-date
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000166 def __rsub__(self, other):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000167 raise TypeError('Can\'t subtract date from integer')
Guido van Rossum2e611031994-10-09 22:36:28 +0000168
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000169 def weekday(self):
170 return _num2day(self.ord)
Guido van Rossum102abab1993-10-30 12:38:16 +0000171
Guido van Rossum102abab1993-10-30 12:38:16 +0000172def today():
173 import time
174 local = time.localtime(time.time())
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000175 return Date(local[1], local[2], local[0])
Guido van Rossum102abab1993-10-30 12:38:16 +0000176
Benjamin Petersond7b03282008-09-13 15:58:53 +0000177class DateTestError(Exception):
178 pass
179
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000180def test(firstyear, lastyear):
Guido van Rossum102abab1993-10-30 12:38:16 +0000181 a = Date(9,30,1913)
182 b = Date(9,30,1914)
Walter Dörwald70a6b492004-02-12 17:35:32 +0000183 if repr(a) != 'Tue 30 Sep 1913':
Collin Winter6f2df4d2007-07-17 20:59:35 +0000184 raise DateTestError('__repr__ failure')
Guido van Rossum2e611031994-10-09 22:36:28 +0000185 if (not a < b) or a == b or a > b or b != b:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000186 raise DateTestError('__cmp__ failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000187 if a+365 != b or 365+a != b:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000188 raise DateTestError('__add__ failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000189 if b-a != 365 or b-365 != a:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000190 raise DateTestError('__sub__ failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000191 try:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000192 x = 1 - a
Collin Winter6f2df4d2007-07-17 20:59:35 +0000193 raise DateTestError('int-date should have failed')
Guido van Rossum102abab1993-10-30 12:38:16 +0000194 except TypeError:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000195 pass
Guido van Rossum102abab1993-10-30 12:38:16 +0000196 try:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000197 x = a + b
Collin Winter6f2df4d2007-07-17 20:59:35 +0000198 raise DateTestError('date+date should have failed')
Guido van Rossum102abab1993-10-30 12:38:16 +0000199 except TypeError:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000200 pass
Guido van Rossum102abab1993-10-30 12:38:16 +0000201 if a.weekday() != 'Tuesday':
Collin Winter6f2df4d2007-07-17 20:59:35 +0000202 raise DateTestError('weekday() failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000203 if max(a,b) is not b or min(a,b) is not a:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000204 raise DateTestError('min/max failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000205 d = {a-1:b, b:a+1}
206 if d[b-366] != b or d[a+(b-a)] != Date(10,1,1913):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000207 raise DateTestError('dictionary failure')
Guido van Rossum102abab1993-10-30 12:38:16 +0000208
209 # verify date<->number conversions for first and last days for
210 # all years in firstyear .. lastyear
211
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000212 lord = _days_before_year(firstyear)
Guido van Rossum102abab1993-10-30 12:38:16 +0000213 y = firstyear
214 while y <= lastyear:
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000215 ford = lord + 1
216 lord = ford + _days_in_year(y) - 1
217 fd, ld = Date(1,1,y), Date(12,31,y)
218 if (fd.ord,ld.ord) != (ford,lord):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000219 raise DateTestError('date->num failed', y)
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000220 fd, ld = _num2date(ford), _num2date(lord)
221 if (1,1,y,12,31,y) != \
222 (fd.month,fd.day,fd.year,ld.month,ld.day,ld.year):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000223 raise DateTestError('num->date failed', y)
Guido van Rossumdcd038f1998-09-14 15:34:45 +0000224 y = y + 1
Benjamin Petersond7b03282008-09-13 15:58:53 +0000225
226if __name__ == '__main__':
227 test(1850, 2150)