# 01-02-04 #v1.0.2 # # Date Utils # By Fuzzyman see www.voidspace.org.uk/atlantibots/pythonutils.html # Written for the Victory Day program for Jesus Fellowship Church # www.jesus.org.uk # These are various functions for dealing with dates (including leap years and so on) # Useful especially for situations where you have to arrange appointments. # (e.g. second Tuesday of the month etc...) # None of these functions are designed to handle BC dates......... # They will also only work with dates from the Gregorian (modern) calender. # They usually assume that given dates are *possible* dates. # (Although there is a function to explicitly check a date). # Help and inspiration was taken from : # http://users.aol.com/s6sj7gt/mikecal.htm and # http://mathforum.org/library/drmath/view/62338.html # If you have any bug reports or suggestions please contact me. # If you would like to be notified of bug fixes / updates then please contact me. # E-mail fuzzyman AT atlantibots DOT org DOT uk (or michael AT foord DOT me DOT uk ) # Code maintained at http://www.voidspace.org.uk/atlantibots/pythonutils.html # Copyright Michael Foord # Not for use in commercial projects without permission. # If you use them in a non-commercial project then please credit me and include a link back. # If you release the project non-commercially then let me know (and include this message with my code !) # No warranty express or implied for the accuracy, fitness to purpose or otherwise for this code.... # Use at your own risk !!! from time import localtime ############################## # First set up some useful values monthslower = [ 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december' ] dayslower =[ 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' ] monthdict = { 'january' : 31, 'february' : 28, 'march' : 31, 'april' : 30, 'may' : 31, 'june' : 30, 'july' : 31, 'august' : 31, 'september' : 30, 'october' : 31, 'november' : 30, 'december' : 31 } monthdictleap = { 'january' : 31, 'february' : 29, 'march' : 31, 'april' : 30, 'may' : 31, 'june' : 30, 'july' : 31, 'august' : 31, 'september' : 30, 'october' : 31, 'november' : 30, 'december' : 31 } monthlist = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] monthlistleap = [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] days =[ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ] months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] ############################# # Next the functions """ There are various useful 'constants' defined in dateutils : monthslower, dayslower = lowercase lists of the days and months monthdict, monthdictleap = dictionaries keyed by month - value is the number of days in the month (monthdictleap is for a leap year) monthlist, monthlistleap = a list of the number of days in the month (monthlistleap is for a leapyear) days, months = capitalised lists of the days and months dateformcon = a dictionary with the standard config settings for the formatted date function. The Following functions are defined in dateutils : (Some of the functions depend on each other - so it's better to import the ones you want rather than cut and paste :-) realdate(day, month, year): Returns true if the supplied date is a possible date and False if it isn't :-) (Note - it only tests that the *year* is greater than zero). isleapyear(year): Given a year as an integer (e.g. 2004) it returns True if the year is a leap year, and False if it isn't. daysinmonth(year, month): Given a year and a month it returns how many days are in that month. datetoday(day, month, year): Passed in a date, in integers, it returns the day of the week. Output is expressed as an integer from 0-6. 0 is Sunday, 1 is Monday....... datestringtoints(datestring): Passed in a datestring - in the form 'yyyymmdd' (e.g. 20040122 being 22nd January 2004) - it returns an integer tuple ( yyyy, mm, dd ). If the datestring is of the wrong length it returns None. (It assumes a four figure year). intstodatestring(day, month, year): Given three integers for day, month and year it returns a datestring 'yyyymmdd' (for easy storage). returndate(): Returns the local date using the localtime function from the time module. Returns integers - ( yyyy, mm, dd ). nearestday(day, month, year, dayofweek = 2, afteronly = 0): Given a date as three integers (year, month and day) it returns the nearest date that is 'dayofweek'. (dayofweek should be an integer from 0 - 6. 0 is Sunday, 1 Monday etc..) If afteronly is set to 1 then it finds the nearest date of that day, on or *after* the specified. Returns integers - ( yyyy, mm, dd ). dayofweek defaults to Tuesday (2) and afteronly defaults to 0 as they are the defaults I'm using for the Victory Day program this is written for. This is used for : e.g find the nearest Tuesday to a given date, or find the nearest Tuesday *after* a given date ! addnumdays(day, month, year, modifier): Given a date as three integers (year, month and day) and a number of days to add or subtract to that date (the integer modifier, positive or negative value) - it returns the correct date as a tuple of integers - ( yyyy, mm, dd ). incdate(day, month, year): Given a date it adds one day to the date and returns the new date. decdate(day, month, year): Given a date it subtracts one day from the date and returns the new date. adddate(day1, month1, year1, day2, month2, year2): Given a date as three integers (year1, month1 and day1) and another number of days (day2), months (month2) and years (year2) to add to that date (or subtract from it) - it returns the new date as a tuple of integers - ( yyyy, mm, dd ). Note : Feb 28th + 1 month = March 31st Feb 29th + 1 month = March 31st January 29th to 31st + 1 month = feb 28th/29th August 31st + 1 month = September 30th We add the years together, then the months, then correct for the 'end of month' (e.g. we change Sep 31st to Sep 30th) Finally we add any extra days on. daycount(year, month, day) This is an implementation of the Julian Day system. This is a continuous count of days from January 1, 4713 B.C. Given a date in in integers it returns an integer value for the date This represents it's Julian Day number as above. This only works for dates represented using the the Gregorian calendar which was adopted in the US/UK on Oct. 15, 1582 - but at different times elsewhere (so historical dates may not be in this system....). counttodate(daycount) Given the number for a date using the Julian Day System, it returns that date as integer tuple (year, month, day). daysbetween(day1, month1, year1, day2, month2, year2) Given two dates it returns the number of days between them. If date1 is earlier than date2 then the result will be positive. def dayfinish(day) Takes an integer day and returns the correct finish for it 1 = 'st', 2 = 'nd', 3 = 'rd', 4-10 = 'th' etc.... def formatteddate(day, month, year, configdict = {}, **configs) Given a date in in integers, it returns the date as a nicely formatted string : e.g. 24th January 1997 or 2nd February 1948 configs accepts the following keywords : dayofweek, addzero, addcom, fullstop, monthfirst e.g. print formatteddate(12, 8, 1974, dayofweek=1, addzero=0, addcom=1, fullstop=1, monthfirst=0) Monday 12th August, 1974. If dayofweek is set to 1 then the day of the week will also be printed : e.g. Monday 24th January 1997 If addzero is set to 1 then days 1-9 will have an additional zero : e.g. 02nd February 1948 If addcom is set to 1 then there will be a comma between the month and the year : e.g. 24th January, 1997 If fullstop is set to 1 then there will be a fullstop after the year : e.g. 24th January 1997. If monthfirst is set to 1 then then the month will be put before the day : e.g. January 24th 1997 If the year is set to zero then it will be missed off. (and the dayofweek will be treated as 0 in this case as well). There is a dictionary called dateformcon defined in the dateutils module with all the config values defined and some good standard settings :-) This dictionary can be passed in instead of the individual settings. """ ############################# def realdate(day, month, year): """Returns true if the supplied date is a possible date and False if it isn't :-) (Note - it *only* tests that the year is greater than zero).""" if month > 12 or year < 1 or day < 1 or month < 1: return False elif month == 2: # if it's february we need to know if it's a leap year if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[ month-1 ] # -1 because in the list January is 0 if day > numdays: return False else: return True def isleapyear(year): """Given a year as an integer (e.g. 2004) it returns True if the year is a leap year, and False if it isn't.""" if year%4 != 0: return False elif year%100 !=0: return True elif year%400 == 0: return True else: return False def daysinmonth(year, month): """Given a year and a month it returns how many days are in that month.""" if month == 2: # if it's february we need to know if it's a leap year if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[ month-1 ] # -1 because in the list January is 0 return numdays def datetoday(day, month, year): """Passed in a date, in integers, it returns the day of the week. Output is expressed as an integer from 0-6. 0 is Sunday, 1 is Monday....... """ # dayofweek would have been a better name for this function :-( d = day m = month y = year if m < 3: z = y-1 else: z = y dayofweek = ( 23*m//9 + d + 4 + y + z//4 - z//100 + z//400 ) if m >= 3: dayofweek -= 2 dayofweek = dayofweek%7 return dayofweek def datestringtoints(datestring): """Passed in a datestring - in the form 'yyyymmdd' (e.g. 20040122 being 22nd January 2004) - it returns an integer tuple ( yyyy, mm, dd ). If the datestring is of the wrong length it returns None. (It assumes a four figure year).""" if len(datestring) != 8: # badly formed datestring return None return (int(datestring[:4]), int(datestring[4:6]), int(datestring[6:8])) def intstodatestring(day, month, year): """Given three integers for day, month and year it returns a datestring 'yyyymmdd' (for easy storage).""" y = str(year) while len(y) < 4: y = '0' + y m = str(month) d = str(day) if len(m) < 2: m = '0' + m if len(d) < 2: d = '0' + d return y+m+d def returndate(): """Returns the local date using the localtime function from the time module. Returns integers - ( yyyy, mm, dd ).""" try: # because this function doesn't work on some platforms datetuple = localtime() except: return (2004, 1, 31) return ( datetuple[0], datetuple[1], datetuple[2] ) def nearestday(day, month, year, dayofweek = 2, afteronly = 0): """Given a date as three integers (year, month and day) it returns the nearest date that is 'dayofweek'. (dayofweek should be an integer from 0 - 6. 0 is Sunday, 1 Monday etc..) If afteronly is set to 1 then it finds the nearest date of that day, on or *after* the specified. Returns integers - ( yyyy, mm, dd ). dayofweek defaults to Tuesday (2) and afteronly defaults to 0 as they are the defaults I'm using for the Victory Day program this is written for. This is used for : e.g find the nearest Tuesday to a given date, or find the nearest Tuesday *after* a given date !""" thisday = datetoday(day, month, year) if thisday == dayofweek: return (year, month, day) if thisday < dayofweek: # this 'if else test' tells us the number of days between the two days of the week forward = dayofweek - thisday backward = 7 - forward else: backward = thisday - dayofweek forward = 7 - backward if afteronly or forward < backward: difference = forward else: difference = -backward return addnumdays(day, month, year, difference) def addnumdays(day, month, year, modifier): """Given a date as three integers (year, month and day) and a number of days to add or subtract to that date (the integer modifier, positive or negative value) - it returns the correct date as a tuple of integers - ( yyyy, mm, dd ).""" if modifier > 0: # damn - different rules for negative modifiers and hard to make generic if month == 2 and isleapyear(year) and day == 29: # special case modifier -= 1 month = 3 day = 1 while modifier >= 365: # add any years on if month <= 2 and isleapyear(year) or month > 2 and isleapyear(year+1): numdays = 366 else: numdays = 365 if modifier >= numdays: year += 1 modifier -= numdays else: break while modifier >= 28: #add any full months on if month == 2: # if it's february we need to know if it's a leap year if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[ month-1 ] # -1 because in the list January is 0 if modifier >= numdays: modifier -= numdays if month != 12: month += 1 else: month = 1 year += 1 else: break # now we need to correct if the new 'day' value is greater than the number of days in the new month...... if month == 2: # if it's february we need to know if it's a leap year if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[ month-1 ] # -1 because in the list January is 0 if day > numdays: if month != 12: month += 1 else: month = 1 year += 1 day = day - numdays while modifier > 0: year, month, day = incdate(day, month, year) modifier -= 1 elif modifier < 0: # we have to subtract days modifier = -modifier # easier to deal with positive numbers :-) if month == 2 and isleapyear(year) and day == 29: # special case modifier -= 1 day = 28 while modifier >= 365: # take any years off if month > 2 and isleapyear(year) or month <= 2 and isleapyear(year-1): numdays = 366 else: numdays = 365 if modifier >= numdays: year -= 1 modifier -= numdays else: break while modifier >= 28: # subtract any full months on if month == 2: if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[month-1] adjuster = numdays - day # how many days before the end of the month is it if day > numdays: modifier -= numdays if month != 1: month -=1 else: month = 12 year -= 1 if month == 2: if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[month-1] day = numdays - adjuster # if we've gone back a whole month it's now the smae numebr of days before the end of the month else: break while modifier > 0: year, month, day = decdate(day, month, year) modifier -= 1 return ( year, month, day ) def incdate(day, month, year): """Given a date it adds one day to the date and returns the new date.""" if month == 2: # if it's february we need to know if it's a leap year if isleapyear(year): numdays = 29 else: numdays = 28 else: numdays = monthlist[ month-1 ] # -1 because in the list January is 0 if day < numdays: day += 1 else: # of course, here day should equal numdays or the date is invalid :-) if month == 12: month = 1 year +=1 day = 1 else: month += 1 day = 1 return ( year, month, day ) def decdate(day, month, year): """Given a date it subtracts one day from the date and returns the new date.""" if day > 1: day -= 1 elif month == 1: # 1st January year -=1 day = 31 month = 12 elif month == 3: # 1st March if isleapyear(year): day = 29 else: day = 28 month = 2 else: day = monthlist[ month-2 ] month -= 1 return ( year, month, day ) def adddate(day1, month1, year1, day2, month2, year2): """Given a date as three integers (year1, month1 and day1) and another number of days (day2), months (month2) and years (year2) to add to that date (or subtract from it) - it returns the new date as a tuple of integers - ( yyyy, mm, dd ). Note : Feb 28th + 1 month = March 31st Feb 29th + 1 month = March 31st January 29th to 31st + 1 month = feb 28th/29th August 31st + 1 month = September 30th We add the years together, then the months, then correct for the 'end of month' (e.g. we change Sep 31st to Sep 30th) Finally we add any extra days on.""" year = year1 + year2 month = month1 + month2 while month < 1: year -= 1 month += 12 while month > 12: year += 1 month -=12 numdays = daysinmonth(year, month) if day1 > numdays: day1 = numdays if day2 < 0: day2 = -day2 thisfunc = decdate else: thisfunc = incdate while day2 > 0: year, month, day1 = thisfunc(day1, month, year) day2 -= 1 return year, month, day1 def daycount(year, month, day): """"This is an implementation of the Julian Day system. This is a continuous count of days from January 1, 4713 B.C. Given a date in in integers it returns an integer value for the date This represents it's Julian Day number as above. This only works for dates represented using the the Gregorian calendar which was adopted in the US/UK on Oct. 15, 1582 - but at different times elsewhere (so historical dates may not be in this system....).""" if month < 3: year = year - 1 month = month + 13 else: month = month + 1 A = int(year/100) B = 2 - A + int(A/4) return int(365.25*year) + int(30.6001*month) + B + day + 1720995 def counttodate(daycount): """Given the number for a date using the Julian Day System, it returns that date as integer tuple (year, month, day).""" # note - slow and badly implemented... but fast enough :-) daycount = daycount - 2453030 return addnumdays(25, 1, 2004, daycount) def daysbetween(day1, month1, year1, day2, month2, year2): """Given two dates it returns the number of days between them. If date1 is earlier than date2 then the result will be positive.""" return daycount(year2, month2, day2) - daycount(year1, month1, day1) def dayfinish(day): """Takes an integer day and returns the correct finish for it 1 = 'st', 2 = 'nd', 3 = 'rd', 4-10 = 'th' etc....""" if day > 3 and day < 21: return 'th' # special cases daystr = str(day) if len(daystr) > 1: daystr = daystr[-1] if daystr == '1': return 'st' elif daystr == '2': return 'nd' elif daystr == '3': return 'rd' else: return 'th' def formatteddate(day, month, year, configdict = {}, **configs): """Given a date in in integers, it returns the date as a nicely formatted string : e.g. 24th January 1997 or 2nd February 1948 configs accepts the following keywords : dayofweek, addzero, addcom, fullstop, monthfirst e.g. print formatteddate(12, 8, 1974, dayofweek=1, addzero=0, addcom=1, fullstop=1, monthfirst=0) Monday 12th August, 1974. If dayofweek is set to 1 then the day of the week will also be printed : e.g. Monday 24th January 1997 If addzero is set to 1 then days 1-9 will have an additional zero : e.g. 02nd February 1948 If addcom is set to 1 then there will be a comma between the month and the year : e.g. 24th January, 1997 If fullstop is set to 1 then there will be a fullstop after the year : e.g. 24th January 1997. If monthfirst is set to 1 then then the month will be put before the day : e.g. January 24th 1997 If the year is set to zero then it will be missed off. (and the dayofweek will be treated as 0 in this case as well). There is a dictionary called dateformcon defined in the dateutils module with all the config values defined and some good standard settings :-) This dictionary can be passed in instead of the individual settings. """ keywordlist = ['dayofweek', 'addzero', 'addcom', 'fullstop', 'monthfirst'] if configdict != {} and isinstance(configdict, dict): configs = configdict for member in keywordlist: if not configs.has_key(member): configs[member] = 0 outstring = '' if configs['dayofweek'] and year: outstring = days[datetoday(day, month, year)] +' ' if day < 10 and configs['addzero']: daystr = '0' + str(day) else: daystr = str(day) if not configs['monthfirst']: outstring += daystr + dayfinish(day) + ' ' + months[month-1] else: outstring += months[month-1] + ' ' + daystr + dayfinish(day) if configs['addcom'] and year: outstring += ',' if year: outstring += ' ' + str(year) if configs['fullstop']: outstring += '.' return outstring dateformcon = { 'dayofweek' : 1, 'addzero' : 0, 'addcom' : 1, 'fullstop' : 1, 'monthfirst' : 0 } ############################################################ if __name__ == "__main__": print returndate() year, month, day = returndate() test = daycount(year, month, day) print test print counttodate(test) while True: x = raw_input("Enter Year of date (Enter to quit) >> ") if x=='': break y = raw_input("Enter Month >> ") z = raw_input("Enter Day >> ") test = daycount(int(x), int(y), int(z)) print test print counttodate(test) print realdate(32, 1, 2004) while True: x = raw_input("Enter Modifier (0 to quit) >> ") if x=='0': break print addnumdays(31, 3, 2004, -int(x) ) while True: x = raw_input("Enter Day of Week 0-6 (7 to quit) >> ") if x=='7': break print nearestday(24, 1, 2004, int(x)) while True: x = raw_input("Enter Years to Add (Enter to quit) >> ") if x=='': break y = raw_input("Enter Months to Add >> ") z = raw_input("Enter Days To Add >> ") print adddate(24, 1, 2004, int(z), int(y), int(x)) year, month , day = adddate(24, 1, 2004, int(z), int(y), int(x)) print "The nearest Tuesday after that date is ", nearestday(day, month, year) """ Versionlog 01-02-04 Version 1.0.2 Corrected bug in intstodatestring. Created lowercase day list and capitalised month list. Added formatteddate and dayfinish function. Put a try: except: catch in returndate - mainly so I can test on the pocketpc. """