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


UCC Code Repository

Contents of /pyBB/modules/dateutils.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show 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 # 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