1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Date manipulation helper functions."""
19
20
21 __docformat__ = "restructuredtext en"
22
23 import math
24 import re
25 import sys
26 from locale import getlocale, LC_TIME
27 from datetime import date, time, datetime, timedelta
28 from time import strptime as time_strptime
29 from calendar import monthrange, timegm
30
31 try:
32 from mx.DateTime import RelativeDateTime, Date, DateTimeType
33 except ImportError:
34 endOfMonth = None
35 DateTimeType = datetime
36 else:
37 endOfMonth = RelativeDateTime(months=1, day=-1)
38
39
40
41
42 FRENCH_FIXED_HOLIDAYS = {
43 'jour_an': '%s-01-01',
44 'fete_travail': '%s-05-01',
45 'armistice1945': '%s-05-08',
46 'fete_nat': '%s-07-14',
47 'assomption': '%s-08-15',
48 'toussaint': '%s-11-01',
49 'armistice1918': '%s-11-11',
50 'noel': '%s-12-25',
51 }
52
53 FRENCH_MOBILE_HOLIDAYS = {
54 'paques2004': '2004-04-12',
55 'ascension2004': '2004-05-20',
56 'pentecote2004': '2004-05-31',
57
58 'paques2005': '2005-03-28',
59 'ascension2005': '2005-05-05',
60 'pentecote2005': '2005-05-16',
61
62 'paques2006': '2006-04-17',
63 'ascension2006': '2006-05-25',
64 'pentecote2006': '2006-06-05',
65
66 'paques2007': '2007-04-09',
67 'ascension2007': '2007-05-17',
68 'pentecote2007': '2007-05-28',
69
70 'paques2008': '2008-03-24',
71 'ascension2008': '2008-05-01',
72 'pentecote2008': '2008-05-12',
73
74 'paques2009': '2009-04-13',
75 'ascension2009': '2009-05-21',
76 'pentecote2009': '2009-06-01',
77
78 'paques2010': '2010-04-05',
79 'ascension2010': '2010-05-13',
80 'pentecote2010': '2010-05-24',
81
82 'paques2011': '2011-04-25',
83 'ascension2011': '2011-06-02',
84 'pentecote2011': '2011-06-13',
85
86 'paques2012': '2012-04-09',
87 'ascension2012': '2012-05-17',
88 'pentecote2012': '2012-05-28',
89 }
90
91
92
94
95 if isinstance(dateobj, date):
96 return ONEDAY * nbdays
97 return nbdays
98
100
101 if isinstance(sampledate, datetime):
102 return datetime(year, month, day)
103 if isinstance(sampledate, date):
104 return date(year, month, day)
105 return Date(year, month, day)
106
108
109 if isinstance(dateobj, date):
110 return dateobj.weekday()
111 return dateobj.day_of_week
112
114
115 year, month, day = [int(chunk) for chunk in datestr.split('-')]
116 return datefactory(year, month, day, sampledate)
117
119 if isinstance(start, date):
120 delta = end - start
121
122 if delta.seconds:
123 return delta.days + 1
124 return delta.days
125 else:
126 return int(math.ceil((end - start).days))
127
129 """return french national days off between begin and end"""
130 begin = datefactory(begin.year, begin.month, begin.day, begin)
131 end = datefactory(end.year, end.month, end.day, end)
132 holidays = [str2date(datestr, begin)
133 for datestr in list(FRENCH_MOBILE_HOLIDAYS.values())]
134 for year in range(begin.year, end.year+1):
135 for datestr in list(FRENCH_FIXED_HOLIDAYS.values()):
136 date = str2date(datestr % year, begin)
137 if date not in holidays:
138 holidays.append(date)
139 return [day for day in holidays if begin <= day < end]
140
142 """adds date but try to only take days worked into account"""
143 step = get_step(start)
144 weeks, plus = divmod(days, 5)
145 end = start + ((weeks * 7) + plus) * step
146 if weekday(end) >= 5:
147 end += (2 * step)
148 end += len([x for x in get_national_holidays(start, end + step)
149 if weekday(x) < 5]) * step
150 if weekday(end) >= 5:
151 end += (2 * step)
152 return end
153
170
171 -def date_range(begin, end, incday=None, incmonth=None):
172 """yields each date between begin and end
173
174 :param begin: the start date
175 :param end: the end date
176 :param incr: the step to use to iterate over dates. Default is
177 one day.
178 :param include: None (means no exclusion) or a function taking a
179 date as parameter, and returning True if the date
180 should be included.
181
182 When using mx datetime, you should *NOT* use incmonth argument, use instead
183 oneDay, oneHour, oneMinute, oneSecond, oneWeek or endOfMonth (to enumerate
184 months) as `incday` argument
185 """
186 assert not (incday and incmonth)
187 begin = todate(begin)
188 end = todate(end)
189 if incmonth:
190 while begin < end:
191 begin = next_month(begin, incmonth)
192 yield begin
193 else:
194 incr = get_step(begin, incday or 1)
195 while begin < end:
196 yield begin
197 begin += incr
198
199
200
201 ONEDAY = timedelta(days=1)
202 ONEWEEK = timedelta(days=7)
203
204 try:
205 strptime = datetime.strptime
206 except AttributeError:
207 from time import strptime as time_strptime
209 return datetime(*time_strptime(value, format)[:6])
210
212 return time(*time_strptime(value, format)[3:6])
213
215 """return a date from a date (leaving unchanged) or a datetime"""
216 if isinstance(somedate, datetime):
217 return date(somedate.year, somedate.month, somedate.day)
218 assert isinstance(somedate, (date, DateTimeType)), repr(somedate)
219 return somedate
220
222 """return a time from a time (leaving unchanged), date or datetime"""
223
224 if not isinstance(somedate, time):
225 return time(somedate.hour, somedate.minute, somedate.second)
226 assert isinstance(somedate, (time)), repr(somedate)
227 return somedate
228
230 """return a date from a date (leaving unchanged) or a datetime"""
231
232 if isinstance(somedate, datetime):
233 return somedate
234 assert isinstance(somedate, (date, DateTimeType)), repr(somedate)
235 return datetime(somedate.year, somedate.month, somedate.day)
236
238 return timegm(somedate.timetuple()) * 1000
239
241 miliseconds, microseconds = divmod(ticks, 1000)
242 try:
243 return datetime.fromtimestamp(miliseconds)
244 except (ValueError, OverflowError):
245 epoch = datetime.fromtimestamp(0)
246 nb_days, seconds = divmod(int(miliseconds), 86400)
247 delta = timedelta(nb_days, seconds=seconds, microseconds=microseconds)
248 try:
249 return epoch + delta
250 except (ValueError, OverflowError):
251 raise
252
254 return monthrange(somedate.year, somedate.month)[1]
255
257 feb = date(somedate.year, 2, 1)
258 if days_in_month(feb) == 29:
259 return 366
260 else:
261 return 365
262
264 while nbmonth:
265 somedate = first_day(somedate) - ONEDAY
266 nbmonth -= 1
267 return somedate
268
270 while nbmonth:
271 somedate = last_day(somedate) + ONEDAY
272 nbmonth -= 1
273 return somedate
274
276 return date(somedate.year, somedate.month, 1)
277
280
282 """like strftime, but returns a unicode string instead of an encoded
283 string which may be problematic with localized date.
284 """
285 if sys.version_info >= (3, 3):
286
287 return somedate.strftime(fmt)
288 else:
289 try:
290 if sys.version_info < (3, 0):
291 encoding = getlocale(LC_TIME)[1] or 'ascii'
292 return str(somedate.strftime(str(fmt)), encoding)
293 else:
294 return somedate.strftime(fmt)
295 except ValueError as exc:
296 if somedate.year >= 1900:
297 raise
298
299
300
301 fields = {'Y': somedate.year,
302 'm': somedate.month,
303 'd': somedate.day,
304 }
305 if isinstance(somedate, datetime):
306 fields.update({'H': somedate.hour,
307 'M': somedate.minute,
308 'S': somedate.second})
309 fmt = re.sub('%([YmdHMS])', r'%(\1)02d', fmt)
310 return str(fmt) % fields
311
313 if dt.tzinfo is None:
314 return dt
315 return datetime(*dt.utctimetuple()[:7])
316
318 if dt.tzinfo is None:
319 return dt
320 return (dt + dt.utcoffset() + dt.dst()).replace(tzinfo=None)
321
323 """return the number of seconds since the begining of the day for that date
324 """
325 return date.second+60*date.minute + 3600*date.hour
326
328 """return the time delta as a number of seconds"""
329 return delta.days + delta.seconds / (3600*24)
330
332 """return the time delta as a fraction of days"""
333 return delta.days*(3600*24) + delta.seconds
334