|
1 """ Python part of the low-level DateTime[Delta] type implementation. |
|
2 |
|
3 Copyright (c) 1998-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com |
|
4 Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com |
|
5 See the documentation for further information on copyrights, |
|
6 or contact the author. All Rights Reserved. |
|
7 """ |
|
8 # Import the python implementation module |
|
9 from mxDateTime_python import * |
|
10 from mxDateTime_python import __version__ |
|
11 |
|
12 # Singletons |
|
13 oneSecond = DateTimeDelta(0,0,0,1) |
|
14 oneMinute = DateTimeDelta(0,0,1) |
|
15 oneHour = DateTimeDelta(0,1) |
|
16 oneDay = DateTimeDelta(1) |
|
17 oneWeek = DateTimeDelta(7) |
|
18 Epoch = DateTimeFromAbsDateTime(1,0) |
|
19 |
|
20 # Shortcuts for pickle; for backward compatibility only (they are now |
|
21 # defined in __init__.py to further reduce the pickles length) |
|
22 def _DT(absdate,abstime): |
|
23 return DateTimeFromAbsDateTime(absdate,abstime) |
|
24 def _DTD(seconds): |
|
25 return DateTimeDeltaFromSeconds(seconds) |
|
26 |
|
27 # Module init |
|
28 class modinit: |
|
29 |
|
30 global _time,_string,_math,_types |
|
31 import time,string,math,types |
|
32 _time = time |
|
33 _string = string |
|
34 _math = math |
|
35 _types = types |
|
36 |
|
37 del modinit |
|
38 |
|
39 ### Helpers |
|
40 |
|
41 def _isstring(arg, |
|
42 |
|
43 isinstance=isinstance, types=_types): |
|
44 |
|
45 if isinstance(arg, types.StringType): |
|
46 return 1 |
|
47 try: |
|
48 if isinstance(arg, types.UnicodeType): |
|
49 return 1 |
|
50 except AttributeError: |
|
51 pass |
|
52 return 0 |
|
53 |
|
54 ### Compatibility APIs |
|
55 |
|
56 # Aliases and functions to make 'from mx.DateTime import *' work much |
|
57 # like 'from time import *' |
|
58 |
|
59 def localtime(ticks=None, |
|
60 # Locals: |
|
61 time=_time.time,float=float,localtime=_time.localtime, |
|
62 round=round,int=int,DateTime=DateTime,floor=_math.floor): |
|
63 |
|
64 """localtime(ticks=None) |
|
65 |
|
66 Construct a DateTime instance using local time from ticks. If |
|
67 ticks are not given, it defaults to the current time. The |
|
68 result is similar to time.localtime(). Fractions of a second |
|
69 are rounded to the nearest micro-second. |
|
70 |
|
71 """ |
|
72 if ticks is None: |
|
73 ticks = time() |
|
74 else: |
|
75 ticks = float(ticks) |
|
76 ticks = round(ticks, 6) |
|
77 fticks = floor(ticks) |
|
78 Y,M,D,h,m,s = localtime(fticks)[:6] |
|
79 s = s + (ticks - fticks) |
|
80 return DateTime(Y,M,D,h,m,s) |
|
81 |
|
82 def gmtime(ticks=None, |
|
83 # Locals: |
|
84 time=_time.time,float=float,gmtime=_time.gmtime, |
|
85 round=round,int=int,DateTime=DateTime,floor=_math.floor): |
|
86 |
|
87 """gmtime(ticks=None) |
|
88 |
|
89 Construct a DateTime instance using UTC time from ticks. If |
|
90 ticks are not given, it defaults to the current time. The |
|
91 result is similar to time.gmtime(). Fractions of a second are |
|
92 rounded to the nearest micro-second. |
|
93 |
|
94 """ |
|
95 if ticks is None: |
|
96 ticks = time() |
|
97 else: |
|
98 ticks = float(ticks) |
|
99 ticks = round(ticks, 6) |
|
100 fticks = floor(ticks) |
|
101 Y,M,D,h,m,s = gmtime(ticks)[:6] |
|
102 s = s + (ticks - fticks) |
|
103 return DateTime(Y,M,D,h,m,s) |
|
104 |
|
105 def mktime((year,month,day,hour,minute,second,dow,doy,dst), |
|
106 # Locals: |
|
107 DateTime=DateTime): |
|
108 |
|
109 """mktime((year,month,day,hour,minute,second,dow,doy,dst)) |
|
110 |
|
111 Same as the DateTime() constructor accept that the interface |
|
112 used is compatible to the similar time.mktime() API. |
|
113 |
|
114 Note that the tuple elements dow, doy and dst are not used in |
|
115 any way. |
|
116 |
|
117 """ |
|
118 return DateTime(year,month,day,hour,minute,second) |
|
119 |
|
120 def ctime(datetime): |
|
121 |
|
122 """ctime(datetime) |
|
123 |
|
124 Returns a string representation of the given DateTime instance |
|
125 using the current locale's default settings. |
|
126 |
|
127 """ |
|
128 return datetime.strftime('%c') |
|
129 |
|
130 def today(hour=0,minute=0,second=0.0, |
|
131 # Locals: |
|
132 localtime=_time.localtime,time=_time.time,DateTime=DateTime): |
|
133 |
|
134 """today(hour=0,minute=0,second=0.0) |
|
135 |
|
136 Returns a DateTime instance for today (in local time) at the |
|
137 given time (defaults to midnight). |
|
138 |
|
139 """ |
|
140 Y,M,D = localtime(time())[:3] |
|
141 return DateTime(Y,M,D,hour,minute,second) |
|
142 |
|
143 def TimeDelta(hours=0.0,minutes=0.0,seconds=0.0, |
|
144 # Locals: |
|
145 DateTimeDelta=DateTimeDelta): |
|
146 |
|
147 """TimeDelta(hours=0.0,minutes=0.0,seconds=0.0) |
|
148 |
|
149 Returns a DateTimeDelta-object reflecting the given time |
|
150 delta. Seconds can be given as float to indicate fractions. |
|
151 |
|
152 """ |
|
153 return DateTimeDelta(0,hours,minutes,seconds) |
|
154 |
|
155 def gm2local(datetime): |
|
156 |
|
157 """ gm2local(datetime) |
|
158 |
|
159 Convert a DateTime instance holding UTC time to a DateTime |
|
160 instance using local time. |
|
161 |
|
162 """ |
|
163 return localtime(datetime.gmticks()) |
|
164 |
|
165 def local2gm(datetime): |
|
166 |
|
167 """ local2gm(datetime) |
|
168 |
|
169 Convert a DateTime instance holding local time to a DateTime |
|
170 instance using UTC time. |
|
171 |
|
172 """ |
|
173 return gmtime(datetime.ticks()) |
|
174 |
|
175 # Alias |
|
176 gmt = utc |
|
177 |
|
178 # Default value for DateTimeFromTJD's tjd_myriad parameter |
|
179 current_myriad = localtime().tjd_myriad |
|
180 |
|
181 def DateTimeFromTJD(tjd,tjd_myriad=current_myriad): |
|
182 |
|
183 """ DateTimeFromTJD(tjd[,myriad]) |
|
184 |
|
185 Return a DateTime instance for the given Truncated Julian Day. |
|
186 myriad defaults to the TJD myriad current at package import |
|
187 time. |
|
188 |
|
189 Note that this version of Truncated Julian Day number does |
|
190 real truncation of important information. It's use is |
|
191 discouraged and unsupported. |
|
192 |
|
193 """ |
|
194 return DateTimeFromAbsDays(tjd + tjd_myriad * 10000.0 - 1721425.0) |
|
195 |
|
196 def DateTimeFromJDN(jdn): |
|
197 |
|
198 """ DateTimeFromJDN(jdn) |
|
199 |
|
200 Return a DateTime instance for the given Julian Day Number. |
|
201 |
|
202 References: |
|
203 ----------- |
|
204 Gregorian 2000-01-01 12:00:00 corresponds to JDN 2451545.0. |
|
205 Gregorian 1858-11-17 00:00:00.00 corresponds to JDN 2400000.5; MJD 0.0. |
|
206 Julian -4712-01-01 12:00:00.00 corresponds to JDN 0.0. |
|
207 Gregorian -4713-11-24 12:00:00.00 corresponds to JDN 0.0. |
|
208 |
|
209 """ |
|
210 return DateTimeFromAbsDays(jdn - 1721425.5) |
|
211 |
|
212 def DateTimeFromMJD(mjd): |
|
213 |
|
214 """ DateTimeFromMJD(mjd) |
|
215 |
|
216 Return a DateTime instance for the given Modified Julian Day |
|
217 (MJD). The MJD is calculated the same way as the JDN except |
|
218 that 1858-11-17 00:00:00.00 is taken as origin of the scale. |
|
219 |
|
220 """ |
|
221 return DateTimeFromAbsDays(mjd + 678575.0) |
|
222 |
|
223 def DateTimeFrom(*args, **kws): |
|
224 |
|
225 """ DateTimeFrom(*args, **kws) |
|
226 |
|
227 Generic DateTime instance constructor. Can handle parsing |
|
228 strings, numbers and keywords. |
|
229 |
|
230 XXX Add support for Unicode. |
|
231 |
|
232 """ |
|
233 if len(args) == 1: |
|
234 # Single argument |
|
235 arg = args[0] |
|
236 argtype = type(arg) |
|
237 if _isstring(arg): |
|
238 import Parser |
|
239 return apply(Parser.DateTimeFromString, args, kws) |
|
240 elif argtype is DateTimeType: |
|
241 return arg |
|
242 elif argtype is DateTimeDeltaType: |
|
243 raise TypeError,'cannot convert DateTimeDelta to DateTime' |
|
244 else: |
|
245 try: |
|
246 value = float(arg) |
|
247 except (TypeError, ValueError): |
|
248 value = int(arg) |
|
249 assert not kws |
|
250 return DateTimeFromTicks(value) |
|
251 |
|
252 elif len(args) > 1: |
|
253 # More than one argument |
|
254 if len(args) == 2 and _isstring(args[0]) and _isstring(args[1]): |
|
255 # interpret as date and time string |
|
256 import Parser |
|
257 return apply(Parser.DateTimeFromString, |
|
258 (args[0] + ' ' + args[1],), |
|
259 kws) |
|
260 |
|
261 # Assume the arguments are the same as for DateTime() |
|
262 return apply(DateTime, args, kws) |
|
263 |
|
264 elif len(kws) > 0: |
|
265 # Keyword arguments; add defaults... today at 0:00:00 |
|
266 hour = kws.get('hour',0) |
|
267 minute = kws.get('minute',0) |
|
268 second = kws.get('second',0) |
|
269 today = now() |
|
270 day = kws.get('day',today.day) |
|
271 month = kws.get('month',today.month) |
|
272 year = kws.get('year',today.year) |
|
273 return DateTime(year,month,day,hour,minute,second) |
|
274 |
|
275 else: |
|
276 raise TypeError,'cannot convert arguments to DateTime' |
|
277 |
|
278 def DateTimeDeltaFrom(*args, **kws): |
|
279 |
|
280 """ DateTimeDeltaFrom(*args, **kws) |
|
281 |
|
282 Generic DateTimeDelta instance constructor. Can handle parsing |
|
283 strings, numbers and keywords. |
|
284 |
|
285 XXX Add support for Unicode. |
|
286 |
|
287 """ |
|
288 if len(args) == 1: |
|
289 # Single argument |
|
290 arg = args[0] |
|
291 if _isstring(arg): |
|
292 import Parser |
|
293 return apply(Parser.DateTimeDeltaFromString, args, kws) |
|
294 elif type(arg) is DateTimeDeltaType: |
|
295 return arg |
|
296 elif type(arg) is DateTimeType: |
|
297 raise TypeError,'cannot convert DateTime to DateTimeDelta' |
|
298 else: |
|
299 try: |
|
300 value = float(arg) |
|
301 except TypeError: |
|
302 value = int(arg) |
|
303 assert not kws |
|
304 return DateTimeDeltaFromSeconds(value) |
|
305 |
|
306 elif len(args) > 1: |
|
307 # Assume the arguments are the same as for DateTimeDelta() |
|
308 return apply(DateTimeDelta, args, kws) |
|
309 |
|
310 elif len(kws) > 0: |
|
311 # Keyword arguments; default: 00:00:00:00.00 |
|
312 hours = kws.get('hours',0) |
|
313 minutes = kws.get('minutes',0) |
|
314 seconds = kws.get('seconds',0.0) |
|
315 days = kws.get('days',0) |
|
316 return DateTimeDelta(days,hours,minutes,seconds) |
|
317 |
|
318 else: |
|
319 raise TypeError,'cannot convert arguments to DateTimeDelta' |
|
320 |
|
321 def TimeDeltaFrom(*args, **kws): |
|
322 |
|
323 """ TimeDeltaFrom(*args, **kws) |
|
324 |
|
325 Generic TimeDelta instance constructor. Can handle parsing |
|
326 strings, numbers and keywords. |
|
327 |
|
328 XXX Add support for Unicode. |
|
329 |
|
330 """ |
|
331 if len(args) > 1: |
|
332 # Assume the arguments are the same as for TimeDelta(): without |
|
333 # days part ! |
|
334 return apply(DateTimeDelta, (0,)+args, kws) |
|
335 else: |
|
336 # Otherwise treat the arguments just like for DateTimeDelta |
|
337 # instances. |
|
338 return apply(DateTimeDeltaFrom, args, kws) |
|
339 |
|
340 def DateFromTicks(ticks, |
|
341 # Locals: |
|
342 DateTime=DateTime,localtime=_time.localtime): |
|
343 |
|
344 """ DateFromTicks(ticks) |
|
345 |
|
346 Constructs a DateTime instance pointing to the local time date |
|
347 at 00:00:00.00 (midnight) indicated by the given ticks value. |
|
348 The time part is ignored. |
|
349 |
|
350 """ |
|
351 return apply(DateTime, localtime(ticks)[:3]) |
|
352 |
|
353 def TimestampFromTicks(ticks, |
|
354 # Locals: |
|
355 DateTime=DateTime,localtime=_time.localtime): |
|
356 |
|
357 """ TimestampFromTicks(ticks) |
|
358 |
|
359 Constructs a DateTime instance pointing to the local date and |
|
360 time indicated by the given ticks value. |
|
361 |
|
362 """ |
|
363 return apply(DateTime, localtime(ticks)[:6]) |
|
364 |
|
365 def TimeFromTicks(ticks, |
|
366 # Locals: |
|
367 DateTimeDelta=DateTimeDelta,localtime=_time.localtime): |
|
368 |
|
369 """ TimeFromTicks(ticks) |
|
370 |
|
371 Constructs a DateTimeDelta instance pointing to the local time |
|
372 indicated by the given ticks value. The date part is ignored. |
|
373 |
|
374 """ |
|
375 return apply(DateTimeDelta, (0,) + localtime(ticks)[3:6]) |
|
376 |
|
377 # Aliases |
|
378 utctime = gmtime |
|
379 utc2local = gm2local |
|
380 local2utc = local2gm |
|
381 DateTimeFromTicks = localtime |
|
382 Date = DateTime |
|
383 Time = TimeDelta |
|
384 Timestamp = DateTime |
|
385 DateFrom = DateTimeFrom # XXX should only parse the date part ! |
|
386 TimeFrom = TimeDeltaFrom |
|
387 TimestampFrom = DateTimeFrom |
|
388 GregorianDateTime = DateTime |
|
389 GregorianDate = Date |
|
390 JulianDate = JulianDateTime |
|
391 |
|
392 |
|
393 ### For backward compatibility (these are depreciated): |
|
394 |
|
395 def gmticks(datetime): |
|
396 |
|
397 """gmticks(datetime) |
|
398 |
|
399 [DEPRECIATED: use the .gmticks() method] |
|
400 |
|
401 Returns a ticks value based on the values stored in |
|
402 datetime under the assumption that they are given in UTC, |
|
403 rather than local time. |
|
404 |
|
405 """ |
|
406 return datetime.gmticks() |
|
407 |
|
408 # Alias |
|
409 utcticks = gmticks |
|
410 |
|
411 def tz_offset(datetime, |
|
412 # Locals: |
|
413 oneSecond=oneSecond): |
|
414 |
|
415 """tz_offset(datetime) |
|
416 |
|
417 [DEPRECIATED: use the .gmtoffset() method] |
|
418 |
|
419 Returns a DateTimeDelta instance representing the UTC |
|
420 offset for datetime assuming that the stored values refer |
|
421 to local time. If you subtract this value from datetime, |
|
422 you'll get UTC time. |
|
423 |
|
424 """ |
|
425 return datetime.gmtoffset() |
|
426 |
|
427 ### Constants (only English; see Locale.py for other languages) |
|
428 |
|
429 # Weekdays |
|
430 Monday = 0 |
|
431 Tuesday = 1 |
|
432 Wednesday = 2 |
|
433 Thursday = 3 |
|
434 Friday = 4 |
|
435 Saturday = 5 |
|
436 Sunday = 6 |
|
437 # as mapping |
|
438 Weekday = {'Saturday': 5, 6: 'Sunday', 'Sunday': 6, 'Thursday': 3, |
|
439 'Wednesday': 2, 'Friday': 4, 'Tuesday': 1, 'Monday': 0, |
|
440 5: 'Saturday', 4: 'Friday', 3: 'Thursday', 2: 'Wednesday', |
|
441 1: 'Tuesday', 0: 'Monday'} |
|
442 |
|
443 # Months |
|
444 January = 1 |
|
445 February = 2 |
|
446 March = 3 |
|
447 April = 4 |
|
448 May = 5 |
|
449 June = 6 |
|
450 July = 7 |
|
451 August = 8 |
|
452 September = 9 |
|
453 October = 10 |
|
454 November = 11 |
|
455 December = 12 |
|
456 # as mapping |
|
457 Month = {2: 'February', 3: 'March', None: 0, 'July': 7, 11: 'November', |
|
458 'December': 12, 'June': 6, 'January': 1, 'September': 9, 'August': |
|
459 8, 'March': 3, 'November': 11, 'April': 4, 12: 'December', 'May': |
|
460 5, 10: 'October', 9: 'September', 8: 'August', 7: 'July', 6: |
|
461 'June', 5: 'May', 4: 'April', 'October': 10, 'February': 2, 1: |
|
462 'January', 0: None} |
|
463 |
|
464 # Limits (see also the range checks in mxDateTime.c) |
|
465 MaxDateTime = DateTime(5867440,12,31) |
|
466 MinDateTime = DateTime(-5851455,1,1) |
|
467 MaxDateTimeDelta = DateTimeDeltaFromSeconds(2147483647 * 86400.0) |
|
468 MinDateTimeDelta = -MaxDateTimeDelta |
|
469 |
|
470 ### |
|
471 |
|
472 class RelativeDateTime: |
|
473 |
|
474 """RelativeDateTime(years=0,months=0,days=0, |
|
475 hours=0,minutes=0,seconds=0, |
|
476 year=0,month=0,day=0, |
|
477 hour=None,minute=None,second=None, |
|
478 weekday=None,weeks=None) |
|
479 |
|
480 Returns a RelativeDateTime instance for the specified relative |
|
481 time. The constructor handles keywords, so you'll only have to |
|
482 give those parameters which should be changed when you add the |
|
483 relative to an absolute DateTime instance. |
|
484 |
|
485 Adding RelativeDateTime instances is supported with the |
|
486 following rules: deltas will be added together, right side |
|
487 absolute values override left side ones. |
|
488 |
|
489 Adding RelativeDateTime instances to DateTime instances will |
|
490 return DateTime instances with the appropriate calculations |
|
491 applied, e.g. to get a DateTime instance for the first of next |
|
492 month, you'd call now() + RelativeDateTime(months=+1,day=1). |
|
493 |
|
494 """ |
|
495 years = 0 |
|
496 months = 0 |
|
497 days = 0 |
|
498 year = None |
|
499 month = 0 |
|
500 day = 0 |
|
501 hours = 0 |
|
502 minutes = 0 |
|
503 seconds = 0 |
|
504 hour = None |
|
505 minute = None |
|
506 second = None |
|
507 weekday = None |
|
508 |
|
509 # cached hash value |
|
510 _hash = None |
|
511 |
|
512 # For Zope security: |
|
513 __roles__ = None |
|
514 __allow_access_to_unprotected_subobjects__ = 1 |
|
515 |
|
516 def __init__(self, |
|
517 years=0,months=0,days=0, |
|
518 hours=0,minutes=0,seconds=0, |
|
519 year=None,month=None,day=None, |
|
520 hour=None,minute=None,second=None, |
|
521 weekday=None,weeks=0): |
|
522 |
|
523 self.years = years |
|
524 self.months = months |
|
525 self.days = days + weeks*7 |
|
526 self.year = year |
|
527 self.month = month |
|
528 self.day = day |
|
529 self.hours = hours |
|
530 self.minutes = minutes |
|
531 self.seconds = seconds |
|
532 self.hour = hour |
|
533 self.minute = minute |
|
534 self.second = second |
|
535 if weekday is not None: |
|
536 # Make sure we've got a 2-tuple |
|
537 assert len(weekday) == 2 |
|
538 self.weekday = weekday |
|
539 |
|
540 def __add__(self,other, |
|
541 # Locals: |
|
542 isinstance=isinstance): |
|
543 |
|
544 if isinstance(other,RelativeDateTime): |
|
545 # RelativeDateTime (self) + RelativeDateTime (other) |
|
546 |
|
547 r = RelativeDateTime() |
|
548 # date deltas |
|
549 r.years = self.years + other.years |
|
550 r.months = self.months + other.months |
|
551 r.days = self.days + other.days |
|
552 # absolute entries of other override those in self, if given |
|
553 r.year = other.year or self.year |
|
554 r.month = other.month or self.month |
|
555 r.day = other.day or self.day |
|
556 r.weekday = other.weekday or self.weekday |
|
557 # time deltas |
|
558 r.hours = self.hours + other.hours |
|
559 r.minutes = self.minutes + other.minutes |
|
560 r.seconds = self.seconds + other.seconds |
|
561 # absolute entries of other override those in self, if given |
|
562 r.hour = other.hour or self.hour |
|
563 r.minute = other.minute or self.minute |
|
564 r.second = other.second or self.second |
|
565 return r |
|
566 |
|
567 else: |
|
568 raise TypeError,"can't add the two types" |
|
569 |
|
570 def __radd__(self,other, |
|
571 # Locals: |
|
572 isinstance=isinstance,DateTimeType=DateTimeType, |
|
573 DateTime=DateTime,DateTimeDelta=DateTimeDelta): |
|
574 |
|
575 if isinstance(other,DateTimeType): |
|
576 # DateTime (other) + RelativeDateTime (self) |
|
577 |
|
578 # date |
|
579 if self.year is None: |
|
580 year = other.year + self.years |
|
581 else: |
|
582 year = self.year + self.years |
|
583 if self.month is None: |
|
584 month = other.month + self.months |
|
585 else: |
|
586 month = self.month + self.months |
|
587 if self.day is None: |
|
588 day = other.day |
|
589 else: |
|
590 day = self.day |
|
591 if day < 0: |
|
592 # fix negative day values |
|
593 month = month + 1 |
|
594 day = day + 1 |
|
595 day = day + self.days |
|
596 # time |
|
597 if self.hour is None: |
|
598 hour = other.hour + self.hours |
|
599 else: |
|
600 hour = self.hour + self.hours |
|
601 if self.minute is None: |
|
602 minute = other.minute + self.minutes |
|
603 else: |
|
604 minute = self.minute + self.minutes |
|
605 if self.second is None: |
|
606 second = other.second + self.seconds |
|
607 else: |
|
608 second = self.second + self.seconds |
|
609 |
|
610 # Refit into proper ranges: |
|
611 if month < 1 or month > 12: |
|
612 month = month - 1 |
|
613 yeardelta, monthdelta = divmod(month, 12) |
|
614 year = year + yeardelta |
|
615 month = monthdelta + 1 |
|
616 |
|
617 # Make sure we have integers |
|
618 year = int(year) |
|
619 month = int(month) |
|
620 day = int(day) |
|
621 |
|
622 if self.weekday is None: |
|
623 return DateTime(year, month, 1) + \ |
|
624 DateTimeDelta(day-1,hour,minute,second) |
|
625 |
|
626 # Adjust to the correct weekday |
|
627 day_of_week,index = self.weekday |
|
628 d = DateTime(year, month, 1) + \ |
|
629 DateTimeDelta(day-1,hour,minute,second) |
|
630 if index == 0: |
|
631 # 0 index: next weekday if no match |
|
632 return d + (day_of_week - d.day_of_week) |
|
633 elif index > 0: |
|
634 # positive index (1 == first weekday of month) |
|
635 first = d - (d.day - 1) |
|
636 diff = day_of_week - first.day_of_week |
|
637 if diff >= 0: |
|
638 return first + (diff + (index-1) * 7) |
|
639 else: |
|
640 return first + (diff + index * 7) |
|
641 else: |
|
642 # negative index (-1 == last weekday of month) |
|
643 last = d + (d.days_in_month - d.day) |
|
644 diff = day_of_week - last.day_of_week |
|
645 if diff <= 0: |
|
646 return last + (diff + (index+1) * 7) |
|
647 else: |
|
648 return last + (diff + index * 7) |
|
649 |
|
650 else: |
|
651 raise TypeError,"can't add the two types" |
|
652 |
|
653 def __sub__(self,other): |
|
654 |
|
655 if isinstance(other,RelativeDateTime): |
|
656 # RelativeDateTime (self) - RelativeDateTime (other) |
|
657 |
|
658 r = RelativeDateTime() |
|
659 # date deltas |
|
660 r.years = self.years - other.years |
|
661 r.months = self.months - other.months |
|
662 r.days = self.days - other.days |
|
663 # absolute entries of other override those in self, if given |
|
664 r.year = other.year or self.year |
|
665 r.month = other.month or self.month |
|
666 r.day = other.day or self.day |
|
667 r.weekday = other.weekday or self.weekday |
|
668 # time deltas |
|
669 r.hours = self.hours - other.hours |
|
670 r.minutes = self.minutes - other.minutes |
|
671 r.seconds = self.seconds - other.seconds |
|
672 # absolute entries of other override those in self, if given |
|
673 r.hour = other.hour or self.hour |
|
674 r.minute = other.minute or self.minute |
|
675 r.second = other.second or self.second |
|
676 |
|
677 return r |
|
678 |
|
679 else: |
|
680 raise TypeError,"can't subtract the two types" |
|
681 |
|
682 def __rsub__(self,other, |
|
683 # Locals: |
|
684 isinstance=isinstance,DateTimeType=DateTimeType): |
|
685 |
|
686 if isinstance(other,DateTimeType): |
|
687 # DateTime (other) - RelativeDateTime (self) |
|
688 return other + self.__neg__() |
|
689 |
|
690 else: |
|
691 raise TypeError,"can't subtract the two types" |
|
692 |
|
693 def __neg__(self): |
|
694 |
|
695 # - RelativeDateTime(self) |
|
696 |
|
697 r = RelativeDateTime() |
|
698 # negate date deltas |
|
699 r.years = - self.years |
|
700 r.months = - self.months |
|
701 r.days = - self.days |
|
702 # absolute entries don't change |
|
703 r.year = self.year |
|
704 r.month = self.month |
|
705 r.day = self.day |
|
706 r.weekday = self.weekday |
|
707 # negate time deltas |
|
708 r.hours = - self.hours |
|
709 r.minutes = - self.minutes |
|
710 r.seconds = - self.seconds |
|
711 # absolute entries don't change |
|
712 r.hour = self.hour |
|
713 r.minute = self.minute |
|
714 r.second = self.second |
|
715 |
|
716 return r |
|
717 |
|
718 def __nonzero__(self): |
|
719 |
|
720 # RelativeDateTime instances are considered false in case |
|
721 # they do not define any alterations |
|
722 if (self.year is None and |
|
723 self.years == 0 and |
|
724 self.month is None and |
|
725 self.months == 0 and |
|
726 self.day is None and |
|
727 self.weekday is None and |
|
728 self.days == 0 and |
|
729 self.hour is None and |
|
730 self.hours == 0 and |
|
731 self.minute is None and |
|
732 self.minutes == 0 and |
|
733 self.second is None and |
|
734 self.seconds == 0): |
|
735 return 0 |
|
736 else: |
|
737 return 1 |
|
738 |
|
739 def __mul__(self,other): |
|
740 |
|
741 # RelativeDateTime (self) * Number (other) |
|
742 factor = float(other) |
|
743 |
|
744 r = RelativeDateTime() |
|
745 # date deltas |
|
746 r.years = factor * self.years |
|
747 r.months = factor * self.months |
|
748 r.days = factor * self.days |
|
749 # time deltas |
|
750 r.hours = factor * self.hours |
|
751 r.minutes = factor * self.minutes |
|
752 r.seconds = factor * self.seconds |
|
753 return r |
|
754 |
|
755 __rmul__ = __mul__ |
|
756 |
|
757 def __div__(self,other): |
|
758 |
|
759 # RelativeDateTime (self) / Number (other) |
|
760 return self.__mul__(1/float(other)) |
|
761 |
|
762 def __eq__(self, other): |
|
763 |
|
764 if isinstance(self, RelativeDateTime) and \ |
|
765 isinstance(other, RelativeDateTime): |
|
766 # RelativeDateTime (self) == RelativeDateTime (other) |
|
767 if (self.years == other.years and |
|
768 self.months == other.months and |
|
769 self.days == other.days and |
|
770 self.year == other.year and |
|
771 self.day == other.day and |
|
772 self.hours == other.hours and |
|
773 self.minutes == other.minutes and |
|
774 self.seconds == other.seconds and |
|
775 self.hour == other.hour and |
|
776 self.minute == other.minute and |
|
777 self.second == other.second and |
|
778 self.weekday == other.weekday): |
|
779 return 1 |
|
780 else: |
|
781 return 0 |
|
782 else: |
|
783 raise TypeError,"can't compare the two types" |
|
784 |
|
785 def __hash__(self): |
|
786 |
|
787 if self._hash is not None: |
|
788 return self._hash |
|
789 x = 1234 |
|
790 for value in (self.years, self.months, self.days, |
|
791 self.year, self.day, |
|
792 self.hours, self.minutes, self.seconds, |
|
793 self.hour, self.minute, self.second, |
|
794 self.weekday): |
|
795 if value is None: |
|
796 x = 135051820 ^ x |
|
797 else: |
|
798 x = hash(value) ^ x |
|
799 self._hash = x |
|
800 return x |
|
801 |
|
802 def __str__(self, |
|
803 |
|
804 join=_string.join): |
|
805 |
|
806 l = [] |
|
807 append = l.append |
|
808 |
|
809 # Format date part |
|
810 if self.year is not None: |
|
811 append('%04i-' % self.year) |
|
812 elif self.years: |
|
813 append('(%0+5i)-' % self.years) |
|
814 else: |
|
815 append('YYYY-') |
|
816 if self.month is not None: |
|
817 append('%02i-' % self.month) |
|
818 elif self.months: |
|
819 append('(%0+3i)-' % self.months) |
|
820 else: |
|
821 append('MM-') |
|
822 if self.day is not None: |
|
823 append('%02i' % self.day) |
|
824 elif self.days: |
|
825 append('(%0+3i)' % self.days) |
|
826 else: |
|
827 append('DD') |
|
828 if self.weekday: |
|
829 append(' %s:%i' % (Weekday[self.weekday[0]][:3],self.weekday[1])) |
|
830 append(' ') |
|
831 |
|
832 # Normalize relative time values to avoid fractions |
|
833 hours = self.hours |
|
834 minutes = self.minutes |
|
835 seconds = self.seconds |
|
836 hours_fraction = hours - int(hours) |
|
837 minutes = minutes + hours_fraction * 60.0 |
|
838 minutes_fraction = minutes - int(minutes) |
|
839 seconds = seconds + minutes_fraction * 6.0 |
|
840 seconds_fraction = seconds - int(seconds) |
|
841 |
|
842 if 0: |
|
843 # Normalize to standard time ranges |
|
844 if seconds > 60.0: |
|
845 extra_minutes, seconds = divmod(seconds, 60.0) |
|
846 minutes = minutes + extra_minutes |
|
847 elif seconds < -60.0: |
|
848 extra_minutes, seconds = divmod(seconds, -60.0) |
|
849 minutes = minutes - extra_minutes |
|
850 if minutes >= 60.0: |
|
851 extra_hours, minutes = divmod(minutes, 60.0) |
|
852 hours = hours + extra_hours |
|
853 elif minutes <= -60.0: |
|
854 extra_hours, minutes = divmod(minutes, -60.0) |
|
855 hours = hours - extra_hours |
|
856 |
|
857 # Format time part |
|
858 if self.hour is not None: |
|
859 append('%02i:' % self.hour) |
|
860 elif hours: |
|
861 append('(%0+3i):' % hours) |
|
862 else: |
|
863 append('HH:') |
|
864 if self.minute is not None: |
|
865 append('%02i:' % self.minute) |
|
866 elif minutes: |
|
867 append('(%0+3i):' % minutes) |
|
868 else: |
|
869 append('MM:') |
|
870 if self.second is not None: |
|
871 append('%02i' % self.second) |
|
872 elif seconds: |
|
873 append('(%0+3i)' % seconds) |
|
874 else: |
|
875 append('SS') |
|
876 |
|
877 return join(l,'') |
|
878 |
|
879 def __repr__(self): |
|
880 |
|
881 return "<%s instance for '%s' at 0x%x>" % ( |
|
882 self.__class__.__name__, |
|
883 self.__str__(), |
|
884 id(self)) |
|
885 |
|
886 # Alias |
|
887 RelativeDate = RelativeDateTime |
|
888 |
|
889 def RelativeDateTimeFrom(*args, **kws): |
|
890 |
|
891 """ RelativeDateTimeFrom(*args, **kws) |
|
892 |
|
893 Generic RelativeDateTime instance constructor. Can handle |
|
894 parsing strings and keywords. |
|
895 |
|
896 """ |
|
897 if len(args) == 1: |
|
898 # Single argument |
|
899 arg = args[0] |
|
900 if _isstring(arg): |
|
901 import Parser |
|
902 return apply(Parser.RelativeDateTimeFromString, args, kws) |
|
903 elif isinstance(arg, RelativeDateTime): |
|
904 return arg |
|
905 else: |
|
906 raise TypeError,\ |
|
907 'cannot convert argument to RelativeDateTime' |
|
908 |
|
909 else: |
|
910 return apply(RelativeDateTime,args,kws) |
|
911 |
|
912 def RelativeDateTimeDiff(date1,date2, |
|
913 |
|
914 floor=_math.floor,int=int,divmod=divmod, |
|
915 RelativeDateTime=RelativeDateTime): |
|
916 |
|
917 """ RelativeDateTimeDiff(date1,date2) |
|
918 |
|
919 Returns a RelativeDateTime instance representing the difference |
|
920 between date1 and date2 in relative terms. |
|
921 |
|
922 The following should hold: |
|
923 |
|
924 date2 + RelativeDateDiff(date1,date2) == date1 |
|
925 |
|
926 for all dates date1 and date2. |
|
927 |
|
928 Note that due to the algorithm used by this function, not the |
|
929 whole range of DateTime instances is supported; there could |
|
930 also be a loss of precision. |
|
931 |
|
932 XXX There are still some problems left (thanks to Carel |
|
933 Fellinger for pointing these out): |
|
934 |
|
935 29 1 1901 -> 1 3 1901 = 1 month |
|
936 29 1 1901 -> 1 3 1900 = -10 month and -28 days, but |
|
937 29 1 1901 -> 28 2 1900 = -11 month and -1 day |
|
938 |
|
939 and even worse: |
|
940 |
|
941 >>> print RelativeDateDiff(Date(1900,3,1),Date(1901,2,1)) |
|
942 YYYY-(-11)-DD HH:MM:SS |
|
943 |
|
944 with: |
|
945 |
|
946 >>> print Date(1901,1,29) + RelativeDateTime(months=-11) |
|
947 1900-03-01 00:00:00.00 |
|
948 >>> print Date(1901,2,1) + RelativeDateTime(months=-11) |
|
949 1900-03-01 00:00:00.00 |
|
950 |
|
951 """ |
|
952 diff = date1 - date2 |
|
953 if diff.days == 0: |
|
954 return RelativeDateTime() |
|
955 date1months = date1.year * 12 + (date1.month - 1) |
|
956 date2months = date2.year * 12 + (date2.month - 1) |
|
957 #print 'months',date1months,date2months |
|
958 |
|
959 # Calculate the months difference |
|
960 diffmonths = date1months - date2months |
|
961 #print 'diffmonths',diffmonths |
|
962 if diff.days > 0: |
|
963 years,months = divmod(diffmonths,12) |
|
964 else: |
|
965 years,months = divmod(diffmonths,-12) |
|
966 years = -years |
|
967 date3 = date2 + RelativeDateTime(years=years,months=months) |
|
968 diff3 = date1 - date3 |
|
969 days = date1.absdays - date3.absdays |
|
970 #print 'date3',date3,'diff3',diff3,'days',days |
|
971 |
|
972 # Correction to ensure that all relative parts have the same sign |
|
973 while days * diff.days < 0: |
|
974 if diff.days > 0: |
|
975 diffmonths = diffmonths - 1 |
|
976 years,months = divmod(diffmonths,12) |
|
977 else: |
|
978 diffmonths = diffmonths + 1 |
|
979 years,months = divmod(diffmonths,-12) |
|
980 years = -years |
|
981 #print 'diffmonths',diffmonths |
|
982 date3 = date2 + RelativeDateTime(years=years,months=months) |
|
983 diff3 = date1 - date3 |
|
984 days = date1.absdays - date3.absdays |
|
985 #print 'date3',date3,'diff3',diff3,'days',days |
|
986 |
|
987 # Drop the fraction part of days |
|
988 if days > 0: |
|
989 days = int(floor(days)) |
|
990 else: |
|
991 days = int(-floor(-days)) |
|
992 |
|
993 return RelativeDateTime(years=years, |
|
994 months=months, |
|
995 days=days, |
|
996 hours=diff3.hour, |
|
997 minutes=diff3.minute, |
|
998 seconds=diff3.second) |
|
999 |
|
1000 # Aliases |
|
1001 RelativeDateDiff = RelativeDateTimeDiff |
|
1002 Age = RelativeDateTimeDiff |
|
1003 |
|
1004 ### |
|
1005 |
|
1006 _current_year = now().year |
|
1007 _current_century, _current_year_in_century = divmod(_current_year, 100) |
|
1008 _current_century = _current_century * 100 |
|
1009 |
|
1010 def add_century(year, |
|
1011 |
|
1012 current_year=_current_year, |
|
1013 current_century=_current_century): |
|
1014 |
|
1015 """ Sliding window approach to the Y2K problem: adds a suitable |
|
1016 century to the given year and returns it as integer. |
|
1017 |
|
1018 The window used depends on the current year (at import time). |
|
1019 If adding the current century to the given year gives a year |
|
1020 within the range current_year-70...current_year+30 [both |
|
1021 inclusive], then the current century is added. Otherwise the |
|
1022 century (current + 1 or - 1) producing the least difference is |
|
1023 chosen. |
|
1024 |
|
1025 """ |
|
1026 if year > 99: |
|
1027 # Take it as-is |
|
1028 return year |
|
1029 year = year + current_century |
|
1030 diff = year - current_year |
|
1031 if diff >= -70 and diff <= 30: |
|
1032 return year |
|
1033 elif diff < -70: |
|
1034 return year + 100 |
|
1035 else: |
|
1036 return year - 100 |
|
1037 |
|
1038 # Reference formulas for JDN taken from the Calendar FAQ: |
|
1039 |
|
1040 def gregorian_jdn(year,month,day): |
|
1041 |
|
1042 # XXX These require proper integer division. |
|
1043 a = (14-month)/12 |
|
1044 y = year+4800-a |
|
1045 m = month + 12*a - 3 |
|
1046 return day + (306*m+5)/10 + y*365 + y/4 - y/100 + y/400 - 32045 |
|
1047 |
|
1048 def julian_jdn(year,month,day): |
|
1049 |
|
1050 # XXX These require proper integer division. |
|
1051 a = (14-month)/12 |
|
1052 y = year+4800-a |
|
1053 m = month + 12*a - 3 |
|
1054 return day + (306*m+5)/10 + y*365 + y/4 - 32083 |