# Date arithmetic module
#
# Utility module for doing date arithmetic, eg. "The month after this
# one", "This same day next year", "Is this (year, month) pair later in
# time and that (year, month) pair?", etc. Day-related methods will
# not work prior to the Gregorian reformation. It is unlikely that
# this will pose a problem.
#
# I think some of this code duplicates functionality present in Python
# itself, but I haven't looked into it.
#
# This file is licensed under the same terms as is NewsBruiser itself.
#The _ function is, by convention, used for I18N. This library will
#plug into whatever I18N framework you have in place, and will set up
#a null framework if you don't have one.
import __builtin__
if not __builtin__.__dict__.get('_'):
__builtin__.__dict__['_'] = lambda(x): x
MONTHS = [_('January'), _('February'), _('March'), _('April'), _('May'),
_('June'), _('July'), _('August'), _('September'), _('October'),
_('November'), _('December')]
DAY_ABBR = [_('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat'),
_('Sun')]
DAY_ORDINAL_NAMES = [ '', _('First'), _('Second'), _('Third'), _('Fourth'),
_('Fifth'), _('Sixth'), _('Seventh'), _('Eighth'),
_('Ninth'), _('Tenth'), _('Eleventh'), _('Twelfth'),
_('Thirteenth'), _('Fourteenth'), _('Fifteenth'),
_('Sixteenth'), _('Seventeenth'), _('Eighteenth'),
_('Nineteenth'), _('Twentieth'), _('Twenty-first'),
_('Twenty-second'), _('Twenty-third'),
_('Twenty-fourth'), _('Twenty-fifth'),
_('Twenty-sixth'), _('Twenty-seventh'),
_('Twenty-eighth'), _('Twenty-ninth'), _('Thirtieth'),
_('Thirty-first') ]
def dayOrdinal(num):
s = str(num)
a = num % 10
if a == 1 and num != 11:
s = s + _('st')
elif a == 2 and num != 12:
s = s + _('nd')
elif a == 3 and num != 13:
s = s + _('rd')
else:
s = s + _('th')
return s
def today():
"Returns the current (year, month, day)"
import time
now = time.localtime(time.time())
return now[:3]
###Date comparisons
def dayIsBefore(ymd1, ymd2):
"Returns 1 iff the first (year, month, day) comes chronologically before the second one."
return (compareDates(ymd1, ymd2) == -1)
def monthIsBefore(ym1, ym2):
"Returns 1 iff the first (year, month) comes chronologically before the second one."
return (compareMonths(ym1, ym2) == -1)
def dayIsAfter(ymd1, ymd2):
"Returns 1 iff the first (year, month, day) comes chronologically after the second one."
return (compareDates(ymd1, ymd2) == 1)
def monthIsAfter(ym1, ym2):
"Returns 1 iff the first (year, month) comes chronologically after the second one."
return (compareMonths(ym1, ym2) == 1)
def compareDates(yearMonthDay1, yearMonthDay2):
"A comparison method for (year, month, day) tuples."
cmp = 0
first = map(int, yearMonthDay1)
second = map(int, yearMonthDay2)
for i in range(0,len(first)):
if cmp == 0:
unit1 = first[i]
unit2 = second[i]
if unit1 > unit2:
cmp = 1
elif unit2 > unit1:
cmp = -1
return cmp
def compareMonths(yearMonth1, yearMonth2):
"A comparison method for (year, month) tuples."
(y1, m1) = map(int, yearMonth1)
(y2, m2) = map(int, yearMonth2)
return compareDates((y1,m1,0), (y2,m2,0))
###Date manipulations
def previousYear(year):
"Returns the year immediately prior to the given year."
return int(year)-1
def nextYear(year):
"Returns the year immediately following the given year."
return int(year)+1
def previousMonth(yearMonth):
"Returns the (year, month) immediately prior to the given (year, month)."
(year, month) = map(int, yearMonth)
if month == 1:
month = 12
year = year - 1
else:
month = month - 1
return (year,month)
def nextMonth(yearMonth):
"Returns the (year, month) immediately following the given (year, month)."
(year, month) = map(int, yearMonth)
if month == 12:
month = 1
year = year + 1
else:
month = month + 1
newYearMonth = [year,month]
return newYearMonth
def previousDay(yearMonthDay):
"Returns the (year,month,day) immediately prior to the given (year,month,day)."
(year, month, day) = map(int, yearMonthDay)
newYear = year
newMonth = month
newDay = day-1
if (newDay == 0):
newMonth = month-1
if (newMonth == 0):
newMonth = 12
newYear = year-1
newDay = daysInMonth(newYear, newMonth)
return (newYear, newMonth, newDay)
def nextDay(yearMonthDay):
"Returns the (year,month,day) after the given (year,month,day)."
(year, month, day) = map(int, yearMonthDay)
newYear = year
newMonth = month
newDay = day+1
if newDay > daysInMonth(year, month):
newMonth = month+1
if (newMonth > 12):
newMonth = 1
newYear = year+1
newDay = 1
newYearMonthDay = (newYear, newMonth, newDay)
return newYearMonthDay
def thisMonthLastYear(ym):
"Takes a (year, month) and jumps back one year."
(y,m) = map(int, ym)
return (y-1,m)
def thisMonthNextYear(ym):
"Takes a (year, month) and jumps forward one year."
(y,m) = map(int, ym)
return (y+1,m)
def thisDayLastYear(ymd):
"Takes a (year, month, day) and jumps back one year. If called on February 29th of a leap year, will return February 28th of the previous year."
(y,m,d) = map(int, ymd)
return thisDayInYear((m,d), y-1)
def thisDayNextYear(ymd):
"Takes a (year, month, day) and jumps forward one year. If called on February 29th of a leap year, will return February 28th of the next year."
(y,m,d) = map(int, ymd)
return thisDayInYear((m,d), y+1)
def thisDayNextMonth(ymd):
"Takes a (year, month, day) and jumps forward one month. If called on the last day of a month whose successor has fewer days, returns the last day of the next month (eg. May 31 -> June 30)"
(y,m,d) = map(int,ymd)
return thisDayInMonth(ymd, nextMonth((y,m)))
def thisDayLastMonth(ymd):
"Takes a (year, month, day) and jumps back one month. If called on the last day of a month whose predecessor has fewer days, returns the last day of the previous month (eg. July 31 -> June 30)"
(y,m,d) = map(int,ymd)
return thisDayInMonth(ymd, previousMonth((y,m)))
def thisDayInMonth(ymd, ym):
"Returns the equivalent of the given (year, month, day) in the given (year, month)"
(thisY,thisM,thisD) = map(int,ymd)
(newY, newM) = map(int, ym)
y = newY
m = newM
d = thisD
if d > daysInMonth(y, m):
d = daysInMonth(y, newM)
return (y,m,d)
def thisDayInYear(md, y):
"Returns the equivalent of the given (month, day) in the given year."
(m,d) = map(int, md)
if (m==2 and d==29 and not isLeapYear(y)):
d = 28
return (y,m,d)
def daysInMonth(year, month):
"Returns the number of days in the given year and month."
days=31
if (month == 2):
days = 28
if (isLeapYear(year)):
days = 29
if (month == 4 or month == 6 or month == 9 or month == 11):
days=30
return days
def isLeapYear(year):
"Returns true iff the given year is a leap year."
isLeap = 0;
if year % 4 == 0:
isLeap = 1
if year % 100 == 0:
isLeap = 0
if year % 400 == 0:
isLeap = 1
return isLeap
#### Testing methods below -- not I18Ned
def runTest(year, month, day):
"A basic self-test."
print " Year before %s: %s" % (year, previousYear(year))
print " Year after %s: %s" % (year, nextYear(year))
(prevY, prevM) = previousMonth((year, month))
print " Month before %s/%s: %s/%s" % (year, month, prevY, prevM)
(nextY, nextM) = nextMonth((year, month))
print " Month after %s/%s: %s/%s" % (year, month, nextY, nextM)
(prevY, prevM, prevD) = previousDay((year, month, day))
print " Day before %s/%s/%s: %s/%s/%s" % (year, month, day, prevY, prevM, prevD)
(nextY, nextM, nextD) = nextDay((year, month, day))
print " Day after %s/%s/%s: %s/%s/%s" % (year, month, day, nextY, nextM, nextD)
print
def runLeapYearTest(year):
s = ""
if not isLeapYear(year):
s = "not "
print " %s is %sa leap year." % (year, s)
print " Day after 02/28: %s/%s/%s" % nextDay((year,2,28))
print " Day before 03/01: %s/%s/%s" % previousDay((year,3,1))
print
def selfTest():
(year, month, day) = today()
print "Testing months:"
for i in range(1,len(MONTHS)+1):
print " %s days hath %s/%s" % (daysInMonth(year, i), MONTHS[i-1], year)
print
print "Testing today:"
runTest(year, month, day)
print "Testing end of year:"
runTest(year,12,31)
print "Testing beginning of year:"
runTest(year,1,1)
print "Testing non-leap years:"
runLeapYearTest(1999)
runLeapYearTest(2100)
print "Testing leap years:"
runLeapYearTest(2000)
runLeapYearTest(2004)
if __name__=='__main__': selfTest()