/[theodore]/bunnyblog/modules/dateutils.py


UCC Code Repository

Contents of /bunnyblog/modules/dateutils.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (hide annotations) (download) (as text)
Tue Jan 29 14:32:01 2008 UTC (12 years, 2 months ago) by svn-admin
File MIME type: text/x-python
File size: 24921 byte(s)
Re-import of repository after repository database corruption.

1 svn-admin 1 # 01-02-04
2     #v1.0.2
3    
4     #
5     # Date Utils
6     # By Fuzzyman see www.voidspace.org.uk/atlantibots/pythonutils.html
7     # Written for the Victory Day program for Jesus Fellowship Church
8     # www.jesus.org.uk
9    
10     # These are various functions for dealing with dates (including leap years and so on)
11     # Useful especially for situations where you have to arrange appointments.
12     # (e.g. second Tuesday of the month etc...)
13    
14     # None of these functions are designed to handle BC dates.........
15     # They will also only work with dates from the Gregorian (modern) calender.
16     # They usually assume that given dates are *possible* dates.
17     # (Although there is a function to explicitly check a date).
18    
19     # Help and inspiration was taken from :
20     # http://users.aol.com/s6sj7gt/mikecal.htm and
21     # http://mathforum.org/library/drmath/view/62338.html
22    
23     # If you have any bug reports or suggestions please contact me.
24     # If you would like to be notified of bug fixes / updates then please contact me.
25    
26     # E-mail fuzzyman AT atlantibots DOT org DOT uk (or michael AT foord DOT me DOT uk )
27     # Code maintained at http://www.voidspace.org.uk/atlantibots/pythonutils.html
28    
29     # Copyright Michael Foord
30     # Not for use in commercial projects without permission.
31     # If you use them in a non-commercial project then please credit me and include a link back.
32     # If you release the project non-commercially then let me know (and include this message with my code !)
33    
34     # No warranty express or implied for the accuracy, fitness to purpose or otherwise for this code....
35     # Use at your own risk !!!
36    
37     from time import localtime
38    
39     ##############################
40    
41     # First set up some useful values
42    
43     monthslower = [ 'january', 'february', 'march', 'april', 'may', 'june', 'july',
44     'august', 'september', 'october', 'november', 'december' ]
45    
46     dayslower =[ 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
47     'saturday' ]
48    
49     monthdict = { 'january' : 31, 'february' : 28, 'march' : 31, 'april' : 30, 'may' : 31,
50     'june' : 30, 'july' : 31, 'august' : 31, 'september' : 30, 'october' : 31,
51     'november' : 30, 'december' : 31 }
52    
53     monthdictleap = { 'january' : 31, 'february' : 29, 'march' : 31, 'april' : 30, 'may' : 31,
54     'june' : 30, 'july' : 31, 'august' : 31, 'september' : 30, 'october' : 31,
55     'november' : 30, 'december' : 31 }
56    
57     monthlist = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
58    
59     monthlistleap = [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
60    
61     days =[ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
62     'Saturday' ]
63    
64     months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
65     'August', 'September', 'October', 'November', 'December' ]
66    
67     #############################
68    
69     # Next the functions
70    
71     """
72     There are various useful 'constants' defined in dateutils :
73    
74     monthslower, dayslower = lowercase lists of the days and months
75     monthdict, monthdictleap = dictionaries keyed by month - value is the number of days in the month (monthdictleap is for a leap year)
76     monthlist, monthlistleap = a list of the number of days in the month (monthlistleap is for a leapyear)
77     days, months = capitalised lists of the days and months
78     dateformcon = a dictionary with the standard config settings for the formatted date function.
79    
80     The Following functions are defined in dateutils :
81    
82     (Some of the functions depend on each other - so it's better to import the ones you want rather than cut and paste :-)
83    
84     realdate(day, month, year):
85     Returns true if the supplied date is a possible date
86     and False if it isn't :-) (Note - it only tests that the *year* is greater than zero).
87    
88     isleapyear(year):
89     Given a year as an integer (e.g. 2004) it returns True if the year is a leap year,
90     and False if it isn't.
91    
92     daysinmonth(year, month):
93     Given a year and a month it returns how many days are in that month.
94    
95     datetoday(day, month, year):
96     Passed in a date, in integers, it returns the day of the week.
97     Output is expressed as an integer from 0-6.
98     0 is Sunday, 1 is Monday.......
99    
100     datestringtoints(datestring):
101     Passed in a datestring - in the form 'yyyymmdd'
102     (e.g. 20040122 being 22nd January 2004) -
103     it returns an integer tuple ( yyyy, mm, dd ).
104     If the datestring is of the wrong length it returns None.
105     (It assumes a four figure year).
106    
107     intstodatestring(day, month, year):
108     Given three integers for day, month and year
109     it returns a datestring 'yyyymmdd' (for easy storage).
110    
111     returndate():
112     Returns the local date using the localtime function
113     from the time module.
114     Returns integers - ( yyyy, mm, dd ).
115    
116     nearestday(day, month, year, dayofweek = 2, afteronly = 0):
117     Given a date as three integers (year, month and day) it returns the nearest
118     date that is 'dayofweek'. (dayofweek should be an integer from 0 - 6. 0 is Sunday, 1 Monday etc..)
119     If afteronly is set to 1 then it finds the nearest date of that day, on or *after* the specified.
120     Returns integers - ( yyyy, mm, dd ).
121     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.
122     This is used for : e.g find the nearest Tuesday to a given date, or find the nearest Tuesday *after* a given date !
123    
124     addnumdays(day, month, year, modifier):
125     Given a date as three integers (year, month and day) and a number of days to add or subtract
126     to that date (the integer modifier, positive or negative value) - it returns the correct date
127     as a tuple of integers - ( yyyy, mm, dd ).
128    
129     incdate(day, month, year):
130     Given a date it adds one day to the date and returns the new date.
131    
132     decdate(day, month, year):
133     Given a date it subtracts one day from the date and returns the new date.
134    
135     adddate(day1, month1, year1, day2, month2, year2):
136     Given a date as three integers (year1, month1 and day1) and another number of days (day2), months (month2)
137     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 ).
138     Note :
139     Feb 28th + 1 month = March 31st
140     Feb 29th + 1 month = March 31st
141     January 29th to 31st + 1 month = feb 28th/29th
142     August 31st + 1 month = September 30th
143     We add the years together, then the months, then correct for the 'end of month' (e.g. we change Sep 31st to Sep 30th)
144     Finally we add any extra days on.
145    
146     daycount(year, month, day)
147     This is an implementation of the Julian Day system. This
148     is a continuous count of days from January 1, 4713 B.C.
149     Given a date in in integers it returns an integer value for the date
150     This represents it's Julian Day number as above.
151     This only works for dates represented using the the Gregorian
152     calendar which was adopted in the US/UK on Oct. 15, 1582 - but
153     at different times elsewhere (so historical dates may not be in this system....).
154    
155     counttodate(daycount)
156     Given the number for a date using the Julian Day System,
157     it returns that date as integer tuple (year, month, day).
158    
159     daysbetween(day1, month1, year1, day2, month2, year2)
160     Given two dates it returns the number of days between them.
161     If date1 is earlier than date2 then the result will be positive.
162    
163     def dayfinish(day)
164     Takes an integer day and returns the correct finish for it
165     1 = 'st', 2 = 'nd', 3 = 'rd', 4-10 = 'th' etc....
166    
167     def formatteddate(day, month, year, configdict = {}, **configs)
168     Given a date in in integers, it returns the date as a nicely formatted string :
169     e.g. 24th January 1997 or 2nd February 1948
170     configs accepts the following keywords :
171     dayofweek, addzero, addcom, fullstop, monthfirst
172     e.g. print formatteddate(12, 8, 1974, dayofweek=1, addzero=0, addcom=1, fullstop=1, monthfirst=0)
173     Monday 12th August, 1974.
174     If dayofweek is set to 1 then the day of the week will also be printed :
175     e.g. Monday 24th January 1997
176     If addzero is set to 1 then days 1-9 will have an additional zero :
177     e.g. 02nd February 1948
178     If addcom is set to 1 then there will be a comma between the month and the year :
179     e.g. 24th January, 1997
180     If fullstop is set to 1 then there will be a fullstop after the year :
181     e.g. 24th January 1997.
182     If monthfirst is set to 1 then then the month will be put before the day :
183     e.g. January 24th 1997
184     If the year is set to zero then it will be missed off.
185     (and the dayofweek will be treated as 0 in this case as well).
186     There is a dictionary called dateformcon defined in the dateutils module with all the config values
187     defined and some good standard settings :-)
188     This dictionary can be passed in instead of the individual settings.
189     """
190    
191     #############################
192    
193    
194     def realdate(day, month, year):
195     """Returns true if the supplied date is a possible date
196     and False if it isn't :-) (Note - it *only* tests that the year is greater than zero)."""
197     if month > 12 or year < 1 or day < 1 or month < 1:
198     return False
199     elif month == 2: # if it's february we need to know if it's a leap year
200     if isleapyear(year):
201     numdays = 29
202     else:
203     numdays = 28
204     else:
205     numdays = monthlist[ month-1 ] # -1 because in the list January is 0
206     if day > numdays:
207     return False
208     else:
209     return True
210    
211    
212     def isleapyear(year):
213     """Given a year as an integer (e.g. 2004) it returns True if the year is a leap year,
214     and False if it isn't."""
215     if year%4 != 0:
216     return False
217     elif year%100 !=0:
218     return True
219     elif year%400 == 0:
220     return True
221     else:
222     return False
223    
224    
225     def daysinmonth(year, month):
226     """Given a year and a month it returns how many days are in that month."""
227     if month == 2: # if it's february we need to know if it's a leap year
228     if isleapyear(year):
229     numdays = 29
230     else:
231     numdays = 28
232     else:
233     numdays = monthlist[ month-1 ] # -1 because in the list January is 0
234     return numdays
235    
236    
237     def datetoday(day, month, year):
238     """Passed in a date, in integers, it returns the day of the week.
239     Output is expressed as an integer from 0-6.
240     0 is Sunday, 1 is Monday....... """
241     # dayofweek would have been a better name for this function :-(
242     d = day
243     m = month
244     y = year
245     if m < 3:
246     z = y-1
247     else:
248     z = y
249     dayofweek = ( 23*m//9 + d + 4 + y + z//4 - z//100 + z//400 )
250     if m >= 3:
251     dayofweek -= 2
252     dayofweek = dayofweek%7
253     return dayofweek
254    
255     def datestringtoints(datestring):
256     """Passed in a datestring - in the form 'yyyymmdd'
257     (e.g. 20040122 being 22nd January 2004) -
258     it returns an integer tuple ( yyyy, mm, dd ).
259     If the datestring is of the wrong length it returns None.
260     (It assumes a four figure year)."""
261     if len(datestring) != 8: # badly formed datestring
262     return None
263     return (int(datestring[:4]), int(datestring[4:6]), int(datestring[6:8]))
264    
265     def intstodatestring(day, month, year):
266     """Given three integers for day, month and year
267     it returns a datestring 'yyyymmdd' (for easy storage)."""
268     y = str(year)
269     while len(y) < 4:
270     y = '0' + y
271     m = str(month)
272     d = str(day)
273     if len(m) < 2:
274     m = '0' + m
275     if len(d) < 2:
276     d = '0' + d
277     return y+m+d
278    
279    
280     def returndate():
281     """Returns the local date using the localtime function
282     from the time module.
283     Returns integers - ( yyyy, mm, dd )."""
284     try: # because this function doesn't work on some platforms
285     datetuple = localtime()
286     except:
287     return (2004, 1, 31)
288     return ( datetuple[0], datetuple[1], datetuple[2] )
289    
290     def nearestday(day, month, year, dayofweek = 2, afteronly = 0):
291     """Given a date as three integers (year, month and day) it returns the nearest
292     date that is 'dayofweek'. (dayofweek should be an integer from 0 - 6. 0 is Sunday, 1 Monday etc..)
293     If afteronly is set to 1 then it finds the nearest date of that day, on or *after* the specified.
294     Returns integers - ( yyyy, mm, dd ).
295     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.
296     This is used for : e.g find the nearest Tuesday to a given date, or find the nearest Tuesday *after* a given date !"""
297     thisday = datetoday(day, month, year)
298     if thisday == dayofweek:
299     return (year, month, day)
300    
301     if thisday < dayofweek: # this 'if else test' tells us the number of days between the two days of the week
302     forward = dayofweek - thisday
303     backward = 7 - forward
304     else:
305     backward = thisday - dayofweek
306     forward = 7 - backward
307     if afteronly or forward < backward:
308     difference = forward
309     else:
310     difference = -backward
311    
312     return addnumdays(day, month, year, difference)
313    
314    
315     def addnumdays(day, month, year, modifier):
316     """Given a date as three integers (year, month and day) and a number of days to add or subtract
317     to that date (the integer modifier, positive or negative value) - it returns the correct date
318     as a tuple of integers - ( yyyy, mm, dd )."""
319     if modifier > 0: # damn - different rules for negative modifiers and hard to make generic
320     if month == 2 and isleapyear(year) and day == 29: # special case
321     modifier -= 1
322     month = 3
323     day = 1
324     while modifier >= 365: # add any years on
325     if month <= 2 and isleapyear(year) or month > 2 and isleapyear(year+1):
326     numdays = 366
327     else:
328     numdays = 365
329     if modifier >= numdays:
330     year += 1
331     modifier -= numdays
332     else:
333     break
334    
335     while modifier >= 28: #add any full months on
336     if month == 2: # if it's february we need to know if it's a leap year
337     if isleapyear(year):
338     numdays = 29
339     else:
340     numdays = 28
341     else:
342     numdays = monthlist[ month-1 ] # -1 because in the list January is 0
343     if modifier >= numdays:
344     modifier -= numdays
345     if month != 12:
346     month += 1
347     else:
348     month = 1
349     year += 1
350     else:
351     break
352     # now we need to correct if the new 'day' value is greater than the number of days in the new month......
353     if month == 2: # if it's february we need to know if it's a leap year
354     if isleapyear(year):
355     numdays = 29
356     else:
357     numdays = 28
358     else:
359     numdays = monthlist[ month-1 ] # -1 because in the list January is 0
360     if day > numdays:
361     if month != 12:
362     month += 1
363     else:
364     month = 1
365     year += 1
366     day = day - numdays
367    
368     while modifier > 0:
369     year, month, day = incdate(day, month, year)
370     modifier -= 1
371    
372     elif modifier < 0: # we have to subtract days
373     modifier = -modifier # easier to deal with positive numbers :-)
374     if month == 2 and isleapyear(year) and day == 29: # special case
375     modifier -= 1
376     day = 28
377     while modifier >= 365: # take any years off
378     if month > 2 and isleapyear(year) or month <= 2 and isleapyear(year-1):
379     numdays = 366
380     else:
381     numdays = 365
382     if modifier >= numdays:
383     year -= 1
384     modifier -= numdays
385     else:
386     break
387    
388     while modifier >= 28: # subtract any full months on
389     if month == 2:
390     if isleapyear(year):
391     numdays = 29
392     else:
393     numdays = 28
394     else:
395     numdays = monthlist[month-1]
396     adjuster = numdays - day # how many days before the end of the month is it
397     if day > numdays:
398     modifier -= numdays
399     if month != 1:
400     month -=1
401     else:
402     month = 12
403     year -= 1
404     if month == 2:
405     if isleapyear(year):
406     numdays = 29
407     else:
408     numdays = 28
409     else:
410     numdays = monthlist[month-1]
411     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
412     else:
413     break
414    
415     while modifier > 0:
416     year, month, day = decdate(day, month, year)
417     modifier -= 1
418    
419     return ( year, month, day )
420    
421    
422     def incdate(day, month, year):
423     """Given a date it adds one day to the date and returns the new date."""
424     if month == 2: # if it's february we need to know if it's a leap year
425     if isleapyear(year):
426     numdays = 29
427     else:
428     numdays = 28
429     else:
430     numdays = monthlist[ month-1 ] # -1 because in the list January is 0
431     if day < numdays:
432     day += 1
433     else: # of course, here day should equal numdays or the date is invalid :-)
434     if month == 12:
435     month = 1
436     year +=1
437     day = 1
438     else:
439     month += 1
440     day = 1
441     return ( year, month, day )
442    
443     def decdate(day, month, year):
444     """Given a date it subtracts one day from the date and returns the new date."""
445     if day > 1:
446     day -= 1
447     elif month == 1: # 1st January
448     year -=1
449     day = 31
450     month = 12
451     elif month == 3: # 1st March
452     if isleapyear(year):
453     day = 29
454     else:
455     day = 28
456     month = 2
457     else:
458     day = monthlist[ month-2 ]
459     month -= 1
460     return ( year, month, day )
461    
462     def adddate(day1, month1, year1, day2, month2, year2):
463     """Given a date as three integers (year1, month1 and day1) and another number of days (day2), months (month2)
464     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 ).
465     Note :
466     Feb 28th + 1 month = March 31st
467     Feb 29th + 1 month = March 31st
468     January 29th to 31st + 1 month = feb 28th/29th
469     August 31st + 1 month = September 30th
470     We add the years together, then the months, then correct for the 'end of month' (e.g. we change Sep 31st to Sep 30th)
471     Finally we add any extra days on."""
472     year = year1 + year2
473     month = month1 + month2
474     while month < 1:
475     year -= 1
476     month += 12
477     while month > 12:
478     year += 1
479     month -=12
480     numdays = daysinmonth(year, month)
481     if day1 > numdays:
482     day1 = numdays
483     if day2 < 0:
484     day2 = -day2
485     thisfunc = decdate
486     else:
487     thisfunc = incdate
488     while day2 > 0:
489     year, month, day1 = thisfunc(day1, month, year)
490     day2 -= 1
491     return year, month, day1
492    
493     def daycount(year, month, day):
494     """"This is an implementation of the Julian Day system. This
495     is a continuous count of days from January 1, 4713 B.C.
496     Given a date in in integers it returns an integer value for the date
497     This represents it's Julian Day number as above.
498     This only works for dates represented using the the Gregorian
499     calendar which was adopted in the US/UK on Oct. 15, 1582 - but
500     at different times elsewhere (so historical dates may not be in this system....)."""
501     if month < 3:
502     year = year - 1
503     month = month + 13
504     else:
505     month = month + 1
506     A = int(year/100)
507     B = 2 - A + int(A/4)
508     return int(365.25*year) + int(30.6001*month) + B + day + 1720995
509    
510     def counttodate(daycount):
511     """Given the number for a date using the Julian Day System,
512     it returns that date as integer tuple (year, month, day)."""
513     # note - slow and badly implemented... but fast enough :-)
514     daycount = daycount - 2453030
515     return addnumdays(25, 1, 2004, daycount)
516    
517     def daysbetween(day1, month1, year1, day2, month2, year2):
518     """Given two dates it returns the number of days between them.
519     If date1 is earlier than date2 then the result will be positive."""
520     return daycount(year2, month2, day2) - daycount(year1, month1, day1)
521    
522     def dayfinish(day):
523     """Takes an integer day and returns the correct finish for it
524     1 = 'st', 2 = 'nd', 3 = 'rd', 4-10 = 'th' etc...."""
525     if day > 3 and day < 21:
526     return 'th' # special cases
527     daystr = str(day)
528     if len(daystr) > 1:
529     daystr = daystr[-1]
530     if daystr == '1':
531     return 'st'
532     elif daystr == '2':
533     return 'nd'
534     elif daystr == '3':
535     return 'rd'
536     else:
537     return 'th'
538    
539     def formatteddate(day, month, year, configdict = {}, **configs):
540     """Given a date in in integers, it returns the date as a nicely formatted string :
541     e.g. 24th January 1997 or 2nd February 1948
542     configs accepts the following keywords :
543     dayofweek, addzero, addcom, fullstop, monthfirst
544     e.g. print formatteddate(12, 8, 1974, dayofweek=1, addzero=0, addcom=1, fullstop=1, monthfirst=0)
545     Monday 12th August, 1974.
546     If dayofweek is set to 1 then the day of the week will also be printed :
547     e.g. Monday 24th January 1997
548     If addzero is set to 1 then days 1-9 will have an additional zero :
549     e.g. 02nd February 1948
550     If addcom is set to 1 then there will be a comma between the month and the year :
551     e.g. 24th January, 1997
552     If fullstop is set to 1 then there will be a fullstop after the year :
553     e.g. 24th January 1997.
554     If monthfirst is set to 1 then then the month will be put before the day :
555     e.g. January 24th 1997
556     If the year is set to zero then it will be missed off.
557     (and the dayofweek will be treated as 0 in this case as well).
558     There is a dictionary called dateformcon defined in the dateutils module with all the config values
559     defined and some good standard settings :-)
560     This dictionary can be passed in instead of the individual settings.
561     """
562     keywordlist = ['dayofweek', 'addzero', 'addcom', 'fullstop', 'monthfirst']
563     if configdict != {} and isinstance(configdict, dict):
564     configs = configdict
565     for member in keywordlist:
566     if not configs.has_key(member):
567     configs[member] = 0
568     outstring = ''
569    
570     if configs['dayofweek'] and year:
571     outstring = days[datetoday(day, month, year)] +' '
572     if day < 10 and configs['addzero']:
573     daystr = '0' + str(day)
574     else:
575     daystr = str(day)
576     if not configs['monthfirst']:
577     outstring += daystr + dayfinish(day) + ' ' + months[month-1]
578     else:
579     outstring += months[month-1] + ' ' + daystr + dayfinish(day)
580     if configs['addcom'] and year:
581     outstring += ','
582     if year:
583     outstring += ' ' + str(year)
584     if configs['fullstop']:
585     outstring += '.'
586     return outstring
587    
588    
589     dateformcon = { 'dayofweek' : 1, 'addzero' : 0, 'addcom' : 1, 'fullstop' : 1, 'monthfirst' : 0 }
590    
591     ############################################################
592    
593     if __name__ == "__main__":
594     print returndate()
595     year, month, day = returndate()
596     test = daycount(year, month, day)
597     print test
598     print counttodate(test)
599     while True:
600     x = raw_input("Enter Year of date (Enter to quit) >> ")
601     if x=='':
602     break
603     y = raw_input("Enter Month >> ")
604     z = raw_input("Enter Day >> ")
605     test = daycount(int(x), int(y), int(z))
606     print test
607     print counttodate(test)
608    
609    
610    
611     print realdate(32, 1, 2004)
612     while True:
613     x = raw_input("Enter Modifier (0 to quit) >> ")
614     if x=='0':
615     break
616     print addnumdays(31, 3, 2004, -int(x) )
617    
618     while True:
619     x = raw_input("Enter Day of Week 0-6 (7 to quit) >> ")
620     if x=='7':
621     break
622     print nearestday(24, 1, 2004, int(x))
623    
624     while True:
625     x = raw_input("Enter Years to Add (Enter to quit) >> ")
626     if x=='':
627     break
628     y = raw_input("Enter Months to Add >> ")
629     z = raw_input("Enter Days To Add >> ")
630     print adddate(24, 1, 2004, int(z), int(y), int(x))
631     year, month , day = adddate(24, 1, 2004, int(z), int(y), int(x))
632     print "The nearest Tuesday after that date is ", nearestday(day, month, year)
633    
634    
635     """
636    
637     Versionlog
638    
639     01-02-04 Version 1.0.2
640     Corrected bug in intstodatestring.
641     Created lowercase day list and capitalised month list.
642     Added formatteddate and dayfinish function.
643     Put a try: except: catch in returndate - mainly so I can test on the pocketpc.
644    
645     """

Managed by UCC Webmasters ViewVC Help
Powered by ViewVC 1.1.26