Package logilab :: Package common :: Module date
[frames] | no frames]

Source Code for Module logilab.common.date

  1  """Date manipulation helper functions. 
  2   
  3  :copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
  4  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr 
  5  :license: General Public License version 2 - http://www.gnu.org/licenses 
  6  """ 
  7  __docformat__ = "restructuredtext en" 
  8   
  9  import math 
 10   
 11  from datetime import date, datetime, timedelta 
 12  try: 
 13      from mx.DateTime import RelativeDateTime, Date 
 14  except ImportError: 
 15      from warnings import warn 
 16      warn("mxDateTime not found, endsOfMonth won't be available") 
 17      from datetime import date, timedelta 
18 - def weekday(date):
19 return date.weekday()
20 endOfMonth = None 21 else: 22 endOfMonth = RelativeDateTime(months=1, day=-1) 23 24 # NOTE: should we implement a compatibility layer between date representations 25 # as we have in lgc.db ? 26 27 PYDATE_STEP = timedelta(days=1) 28 29 FRENCH_FIXED_HOLIDAYS = { 30 'jour_an' : '%s-01-01', 31 'fete_travail' : '%s-05-01', 32 'armistice1945' : '%s-05-08', 33 'fete_nat' : '%s-07-14', 34 'assomption' : '%s-08-15', 35 'toussaint' : '%s-11-01', 36 'armistice1918' : '%s-11-11', 37 'noel' : '%s-12-25', 38 } 39 40 FRENCH_MOBILE_HOLIDAYS = { 41 'paques2004' : '2004-04-12', 42 'ascension2004' : '2004-05-20', 43 'pentecote2004' : '2004-05-31', 44 45 'paques2005' : '2005-03-28', 46 'ascension2005' : '2005-05-05', 47 'pentecote2005' : '2005-05-16', 48 49 'paques2006' : '2006-04-17', 50 'ascension2006' : '2006-05-25', 51 'pentecote2006' : '2006-06-05', 52 53 'paques2007' : '2007-04-09', 54 'ascension2007' : '2007-05-17', 55 'pentecote2007' : '2007-05-28', 56 57 'paques2008' : '2008-03-24', 58 'ascension2008' : '2008-05-01', 59 'pentecote2008' : '2008-05-12', 60 61 'paques2009' : '2009-04-13', 62 'ascension2009' : '2009-05-21', 63 'pentecote2009' : '2009-06-01', 64 65 'paques2010' : '2010-04-05', 66 'ascension2010' : '2010-05-13', 67 'pentecote2010' : '2010-05-24', 68 69 'paques2011' : '2011-04-25', 70 'ascension2011' : '2011-06-02', 71 'pentecote2011' : '2011-06-13', 72 73 'paques2012' : '2012-04-09', 74 'ascension2012' : '2012-05-17', 75 'pentecote2012' : '2012-05-28', 76 } 77 78 # this implementation cries for multimethod dispatching 79
80 -def get_step(dateobj):
81 # assume date is either a python datetime or a mx.DateTime object 82 if isinstance(dateobj, date): 83 return PYDATE_STEP 84 return 1 # mx.DateTime is ok with integers
85
86 -def datefactory(year, month, day, sampledate):
87 # assume date is either a python datetime or a mx.DateTime object 88 if isinstance(sampledate, datetime): 89 return datetime(year, month, day) 90 if isinstance(sampledate, date): 91 return date(year, month, day) 92 return Date(year, month, day)
93
94 -def weekday(dateobj):
95 # assume date is either a python datetime or a mx.DateTime object 96 if isinstance(dateobj, date): 97 return dateobj.weekday() 98 return dateobj.day_of_week
99
100 -def str2date(datestr, sampledate):
101 # NOTE: datetime.strptime is not an option until we drop py2.4 compat 102 year, month, day = [int(chunk) for chunk in datestr.split('-')] 103 return datefactory(year, month, day, sampledate)
104
105 -def days_between(start, end):
106 if isinstance(start, date): 107 delta = end - start 108 # datetime.timedelta.days is always an integer (floored) 109 if delta.seconds: 110 return delta.days + 1 111 return delta.days 112 else: 113 return int(math.ceil((end - start).days))
114
115 -def get_national_holidays(begin, end):
116 """return french national days off between begin and end""" 117 begin = datefactory(begin.year, begin.month, begin.day, begin) 118 end = datefactory(end.year, end.month, end.day, end) 119 holidays = [str2date(datestr, begin) 120 for datestr in FRENCH_MOBILE_HOLIDAYS.values()] 121 for year in xrange(begin.year, end.year+1): 122 for datestr in FRENCH_FIXED_HOLIDAYS.values(): 123 date = str2date(datestr % year, begin) 124 if date not in holidays: 125 holidays.append(date) 126 return [day for day in holidays if begin <= day < end]
127
128 -def add_days_worked(start, days):
129 """adds date but try to only take days worked into account""" 130 step = get_step(start) 131 weeks, plus = divmod(days, 5) 132 end = start + ((weeks * 7) + plus) * step 133 if weekday(end) >= 5: # saturday or sunday 134 end += (2 * step) 135 end += len([x for x in get_national_holidays(start, end + step) 136 if weekday(x) < 5]) * step 137 if weekday(end) >= 5: # saturday or sunday 138 end += (2 * step) 139 return end
140
141 -def nb_open_days(start, end):
142 assert start <= end 143 step = get_step(start) 144 days = days_between(start, end) 145 weeks, plus = divmod(days, 7) 146 if weekday(start) > weekday(end): 147 plus -= 2 148 elif weekday(end) == 6: 149 plus -= 1 150 open_days = weeks * 5 + plus 151 nb_week_holidays = len([x for x in get_national_holidays(start, end+step) 152 if weekday(x) < 5 and x < end]) 153 return open_days - nb_week_holidays
154
155 -def date_range(begin, end, step=None):
156 """ 157 enumerate dates between begin and end dates. 158 159 step can either be oneDay, oneHour, oneMinute, oneSecond, oneWeek 160 use endOfMonth to enumerate months 161 """ 162 if step is None: 163 step = get_step(begin) 164 date = begin 165 while date < end : 166 yield date 167 date += step
168