1
2
3 """
4 Parse human-readable date/time text.
5 """
6
7 __license__ = """
8 Copyright (c) 2004-2008 Mike Taylor
9 Copyright (c) 2006-2008 Darshana Chhajed
10 All rights reserved.
11
12 Licensed under the Apache License, Version 2.0 (the "License");
13 you may not use this file except in compliance with the License.
14 You may obtain a copy of the License at
15
16 http://www.apache.org/licenses/LICENSE-2.0
17
18 Unless required by applicable law or agreed to in writing, software
19 distributed under the License is distributed on an "AS IS" BASIS,
20 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 See the License for the specific language governing permissions and
22 limitations under the License.
23 """
24
25 _debug = False
26
27
28 import re
29 import time
30 import datetime
31 import rfc822
32 import parsedatetime_consts
33
34
35
36
37
38
40 year = int(m.group('year'))
41 if year < 100:
42 year = 100 * int(time.gmtime()[0] / 100) + int(year)
43 if year < 1000:
44 return 0, 0, 0
45 julian = m.group('julian')
46 if julian:
47 julian = int(julian)
48 month = julian / 30 + 1
49 day = julian % 30 + 1
50 jday = None
51 while jday != julian:
52 t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
53 jday = time.gmtime(t)[-2]
54 diff = abs(jday - julian)
55 if jday > julian:
56 if diff < day:
57 day = day - diff
58 else:
59 month = month - 1
60 day = 31
61 elif jday < julian:
62 if day + diff < 28:
63 day = day + diff
64 else:
65 month = month + 1
66 return year, month, day
67 month = m.group('month')
68 day = 1
69 if month is None:
70 month = 1
71 else:
72 month = int(month)
73 day = m.group('day')
74 if day:
75 day = int(day)
76 else:
77 day = 1
78 return year, month, day
79
80
81
82
83
85 if not m:
86 return 0, 0, 0
87 hours = m.group('hours')
88 if not hours:
89 return 0, 0, 0
90 hours = int(hours)
91 minutes = int(m.group('minutes'))
92 seconds = m.group('seconds')
93 if seconds:
94 seconds = int(seconds)
95 else:
96 seconds = 0
97 return hours, minutes, seconds
98
99
100
101
102
103
104
105
106
107
108
109
111
112
113 def __extract_tzd(m):
114 '''Return the Time Zone Designator as an offset in seconds from UTC.'''
115 if not m:
116 return 0
117 tzd = m.group('tzd')
118 if not tzd:
119 return 0
120 if tzd == 'Z':
121 return 0
122 hours = int(m.group('tzdhours'))
123 minutes = m.group('tzdminutes')
124 if minutes:
125 minutes = int(minutes)
126 else:
127 minutes = 0
128 offset = (hours*60 + minutes) * 60
129 if tzd[0] == '+':
130 return -offset
131 return offset
132
133 __date_re = ('(?P<year>\d\d\d\d)'
134 '(?:(?P<dsep>-|)'
135 '(?:(?P<julian>\d\d\d)'
136 '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
137 __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
138 __tzd_rx = re.compile(__tzd_re)
139 __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
140 '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
141 + __tzd_re)
142 __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
143 __datetime_rx = re.compile(__datetime_re)
144 m = __datetime_rx.match(dateString)
145 if (m is None) or (m.group() != dateString): return
146 return _extract_date(m) + _extract_time(m) + (0, 0, 0)
147
148
149
150
151
152
153
155 '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
156 data = dateString.split()
157 if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
158 del data[0]
159 if len(data) == 4:
160 s = data[3]
161 i = s.find('+')
162 if i > 0:
163 data[3:] = [s[:i], s[i+1:]]
164 else:
165 data.append('')
166 dateString = " ".join(data)
167 if len(data) < 5:
168 dateString += ' 00:00:00 GMT'
169 return rfc822.parsedate_tz(dateString)
170
171
172
173 _additional_timezones = {'AT': -400, 'ET': -500,
174 'CT': -600, 'MT': -700,
175 'PT': -800}
176 rfc822._timezones.update(_additional_timezones)
177
178
180 """
181 A collection of routines to input, parse and manipulate date and times.
182 The text can either be 'normal' date values or it can be human readable.
183 """
184
186 """
187 Default constructor for the L{Calendar} class.
188
189 @type constants: object
190 @param constants: Instance of the class L{parsedatetime_consts.Constants}
191
192 @rtype: object
193 @return: L{Calendar} instance
194 """
195
196 if constants is None:
197 self.ptc = parsedatetime_consts.Constants()
198 else:
199 self.ptc = constants
200
201 self.weekdyFlag = False
202 self.dateStdFlag = False
203 self.dateStrFlag = False
204 self.timeStdFlag = False
205 self.meridianFlag = False
206 self.dayStrFlag = False
207 self.timeStrFlag = False
208 self.modifierFlag = False
209 self.modifier2Flag = False
210 self.unitsFlag = False
211 self.qunitsFlag = False
212
213 self.timeFlag = 0
214 self.dateFlag = 0
215
216
218 """
219 Converts text units into their number value
220
221 Five = 5
222 Twenty Five = 25
223 Two hundred twenty five = 225
224 Two thousand and twenty five = 2025
225 Two thousand twenty five = 2025
226
227 @type unitText: string
228 @param unitText: number text to convert
229
230 @rtype: integer
231 @return: numerical value of unitText
232 """
233
234 pass
235
236
237 - def _buildTime(self, source, quantity, modifier, units):
238 """
239 Take C{quantity}, C{modifier} and C{unit} strings and convert them into values.
240 After converting, calcuate the time and return the adjusted sourceTime.
241
242 @type source: time
243 @param source: time to use as the base (or source)
244 @type quantity: string
245 @param quantity: quantity string
246 @type modifier: string
247 @param modifier: how quantity and units modify the source time
248 @type units: string
249 @param units: unit of the quantity (i.e. hours, days, months, etc)
250
251 @rtype: struct_time
252 @return: C{struct_time} of the calculated time
253 """
254 if _debug:
255 print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
256
257 if source is None:
258 source = time.localtime()
259
260 if quantity is None:
261 quantity = ''
262 else:
263 quantity = quantity.strip()
264
265 if len(quantity) == 0:
266 qty = 1
267 else:
268 try:
269 qty = int(quantity)
270 except ValueError:
271 qty = 0
272
273 if modifier in self.ptc.Modifiers:
274 qty = qty * self.ptc.Modifiers[modifier]
275
276 if units is None or units == '':
277 units = 'dy'
278
279
280
281 (yr, mth, dy, hr, mn, sec, _, _, _) = source
282
283 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
284 target = start
285
286 if units.startswith('y'):
287 target = self.inc(start, year=qty)
288 self.dateFlag = 1
289 elif units.endswith('th') or units.endswith('ths'):
290 target = self.inc(start, month=qty)
291 self.dateFlag = 1
292 else:
293 if units.startswith('d'):
294 target = start + datetime.timedelta(days=qty)
295 self.dateFlag = 1
296 elif units.startswith('h'):
297 target = start + datetime.timedelta(hours=qty)
298 self.timeFlag = 2
299 elif units.startswith('m'):
300 target = start + datetime.timedelta(minutes=qty)
301 self.timeFlag = 2
302 elif units.startswith('s'):
303 target = start + datetime.timedelta(seconds=qty)
304 self.timeFlag = 2
305 elif units.startswith('w'):
306 target = start + datetime.timedelta(weeks=qty)
307 self.dateFlag = 1
308
309 return target.timetuple()
310
311
313 """
314 Parse short-form date strings::
315
316 '05/28/2006' or '04.21'
317
318 @type dateString: string
319 @param dateString: text to convert to a C{datetime}
320
321 @rtype: struct_time
322 @return: calculated C{struct_time} value of dateString
323 """
324 yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
325
326
327
328
329
330 v1 = -1
331 v2 = -1
332 v3 = -1
333
334 s = dateString
335 m = self.ptc.CRE_DATE2.search(s)
336 if m is not None:
337 index = m.start()
338 v1 = int(s[:index])
339 s = s[index + 1:]
340
341 m = self.ptc.CRE_DATE2.search(s)
342 if m is not None:
343 index = m.start()
344 v2 = int(s[:index])
345 v3 = int(s[index + 1:])
346 else:
347 v2 = int(s.strip())
348
349 v = [ v1, v2, v3 ]
350 d = { 'm': mth, 'd': dy, 'y': yr }
351
352 for i in range(0, 3):
353 n = v[i]
354 c = self.ptc.dp_order[i]
355 if n >= 0:
356 d[c] = n
357
358
359
360 if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
361 yr = d['y'] + 1
362 else:
363 yr = d['y']
364
365 mth = d['m']
366 dy = d['d']
367
368
369 if yr < self.ptc.BirthdayEpoch:
370 yr += 2000
371 elif yr < 100:
372 yr += 1900
373
374 if _debug:
375 print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr)
376
377 if (mth > 0 and mth <= 12) and \
378 (dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)):
379 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
380 else:
381 self.dateFlag = 0
382 self.timeFlag = 0
383 sourceTime = time.localtime()
384
385
386 return sourceTime
387
388
389 - def parseDateText(self, dateString):
390 """
391 Parse long-form date strings::
392
393 'May 31st, 2006'
394 'Jan 1st'
395 'July 2006'
396
397 @type dateString: string
398 @param dateString: text to convert to a datetime
399
400 @rtype: struct_time
401 @return: calculated C{struct_time} value of dateString
402 """
403 yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
404
405 currentMth = mth
406 currentDy = dy
407
408 s = dateString.lower()
409 m = self.ptc.CRE_DATE3.search(s)
410 mth = m.group('mthname')
411 mth = self.ptc.MonthOffsets[mth]
412
413 if m.group('day') != None:
414 dy = int(m.group('day'))
415 else:
416 dy = 1
417
418 if m.group('year') != None:
419 yr = int(m.group('year'))
420
421
422 if yr < self.ptc.BirthdayEpoch:
423 yr += 2000
424 elif yr < 100:
425 yr += 1900
426
427 elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
428
429
430 yr += 1
431
432 if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr):
433 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
434 else:
435
436 self.dateFlag = 0
437 self.timeFlag = 0
438 sourceTime = time.localtime()
439
440 return sourceTime
441
442
443 - def evalRanges(self, datetimeString, sourceTime=None):
444 """
445 Evaluate the C{datetimeString} text and determine if
446 it represents a date or time range.
447
448 @type datetimeString: string
449 @param datetimeString: datetime text to evaluate
450 @type sourceTime: struct_time
451 @param sourceTime: C{struct_time} value to use as the base
452
453 @rtype: tuple
454 @return: tuple of: start datetime, end datetime and the invalid flag
455 """
456 startTime = ''
457 endTime = ''
458 startDate = ''
459 endDate = ''
460 rangeFlag = 0
461
462 s = datetimeString.strip().lower()
463
464 if self.ptc.rangeSep in s:
465 s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep)
466 s = s.replace(' ', ' ')
467
468 m = self.ptc.CRE_TIMERNG1.search(s)
469 if m is not None:
470 rangeFlag = 1
471 else:
472 m = self.ptc.CRE_TIMERNG2.search(s)
473 if m is not None:
474 rangeFlag = 2
475 else:
476 m = self.ptc.CRE_TIMERNG4.search(s)
477 if m is not None:
478 rangeFlag = 7
479 else:
480 m = self.ptc.CRE_TIMERNG3.search(s)
481 if m is not None:
482 rangeFlag = 3
483 else:
484 m = self.ptc.CRE_DATERNG1.search(s)
485 if m is not None:
486 rangeFlag = 4
487 else:
488 m = self.ptc.CRE_DATERNG2.search(s)
489 if m is not None:
490 rangeFlag = 5
491 else:
492 m = self.ptc.CRE_DATERNG3.search(s)
493 if m is not None:
494 rangeFlag = 6
495
496 if _debug:
497 print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
498
499 if m is not None:
500 if (m.group() != s):
501
502 parseStr = m.group()
503 chunk1 = s[:m.start()]
504 chunk2 = s[m.end():]
505 s = '%s %s' % (chunk1, chunk2)
506 flag = 1
507
508 sourceTime, flag = self.parse(s, sourceTime)
509
510 if flag == 0:
511 sourceTime = None
512 else:
513 parseStr = s
514
515 if rangeFlag == 1:
516 m = re.search(self.ptc.rangeSep, parseStr)
517 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
518 endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
519
520 if (eflag != 0) and (sflag != 0):
521 return (startTime, endTime, 2)
522
523 elif rangeFlag == 2:
524 m = re.search(self.ptc.rangeSep, parseStr)
525 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
526 endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
527
528 if (eflag != 0) and (sflag != 0):
529 return (startTime, endTime, 2)
530
531 elif rangeFlag == 3 or rangeFlag == 7:
532 m = re.search(self.ptc.rangeSep, parseStr)
533
534 if self.ptc.usesMeridian:
535 ampm = re.search(self.ptc.am[0], parseStr)
536
537
538 if ampm is not None:
539 startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime)
540 else:
541 startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime)
542 else:
543 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
544
545 endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
546
547 if (eflag != 0) and (sflag != 0):
548 return (startTime, endTime, 2)
549
550 elif rangeFlag == 4:
551 m = re.search(self.ptc.rangeSep, parseStr)
552 startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
553 endDate, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
554
555 if (eflag != 0) and (sflag != 0):
556 return (startDate, endDate, 1)
557
558 elif rangeFlag == 5:
559 m = re.search(self.ptc.rangeSep, parseStr)
560 endDate = parseStr[(m.start() + 1):]
561
562
563 date = self.ptc.CRE_DATE3.search(endDate)
564 endYear = date.group('year')
565
566
567
568
569 if endYear is not None:
570 startDate = (parseStr[:m.start()]).strip()
571 date = self.ptc.CRE_DATE3.search(startDate)
572 startYear = date.group('year')
573
574 if startYear is None:
575 startDate = startDate + ', ' + endYear
576 else:
577 startDate = parseStr[:m.start()]
578
579 startDate, sflag = self.parse(startDate, sourceTime)
580 endDate, eflag = self.parse(endDate, sourceTime)
581
582 if (eflag != 0) and (sflag != 0):
583 return (startDate, endDate, 1)
584
585 elif rangeFlag == 6:
586 m = re.search(self.ptc.rangeSep, parseStr)
587
588 startDate = parseStr[:m.start()]
589
590
591 mth = self.ptc.CRE_DATE3.search(startDate)
592 mth = mth.group('mthname')
593
594
595 endDate = mth + parseStr[(m.start() + 1):]
596
597 startDate, sflag = self.parse(startDate, sourceTime)
598 endDate, eflag = self.parse(endDate, sourceTime)
599
600 if (eflag != 0) and (sflag != 0):
601 return (startDate, endDate, 1)
602 else:
603
604 sourceTime = time.localtime()
605
606 return (sourceTime, sourceTime, 0)
607
608
610 """
611 Based on the C{style} and C{currentDayStyle} determine what
612 day-of-week value is to be returned.
613
614 @type wd: integer
615 @param wd: day-of-week value for the current day
616 @type wkdy: integer
617 @param wkdy: day-of-week value for the parsed day
618 @type offset: integer
619 @param offset: offset direction for any modifiers (-1, 0, 1)
620 @type style: integer
621 @param style: normally the value set in C{Constants.DOWParseStyle}
622 @type currentDayStyle: integer
623 @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}
624
625 @rtype: integer
626 @return: calculated day-of-week
627 """
628 if offset == 1:
629
630
631 diff = 7 - wd + wkdy
632
633 elif offset == -1:
634
635
636 diff = wkdy - wd - 7
637
638 elif offset == 0:
639
640
641 diff = wkdy - wd
642
643 elif offset == 2:
644
645
646 if style == 1:
647
648 if currentDayStyle == True:
649 if wkdy >= wd:
650 diff = wkdy - wd
651 else:
652 diff = 7 - wd + wkdy
653 else:
654 if wkdy > wd:
655 diff = wkdy - wd
656 else:
657 diff = 7 - wd + wkdy
658
659 elif style == -1:
660
661 if currentDayStyle == True:
662 if wkdy <= wd:
663 diff = wkdy - wd
664 else:
665 diff = wkdy - wd - 7
666 else:
667 if wkdy < wd:
668 diff = wkdy - wd
669 else:
670 diff = wkdy - wd - 7
671 else:
672
673 diff = wkdy - wd
674
675 if _debug:
676 print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
677
678 return diff
679
680
682 """
683 Evaluate the C{modifier} string and following text (passed in
684 as C{chunk1} and C{chunk2}) and if they match any known modifiers
685 calculate the delta and apply it to C{sourceTime}.
686
687 @type modifier: string
688 @param modifier: modifier text to apply to sourceTime
689 @type chunk1: string
690 @param chunk1: first text chunk that followed modifier (if any)
691 @type chunk2: string
692 @param chunk2: second text chunk that followed modifier (if any)
693 @type sourceTime: struct_time
694 @param sourceTime: C{struct_time} value to use as the base
695
696 @rtype: tuple
697 @return: tuple of: remaining text and the modified sourceTime
698 """
699 offset = self.ptc.Modifiers[modifier]
700
701 if sourceTime is not None:
702 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
703 else:
704 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
705
706
707
708 m = self.ptc.CRE_REMAINING.search(chunk2)
709 if m is not None:
710 index = m.start() + 1
711 unit = chunk2[:m.start()]
712 chunk2 = chunk2[index:]
713 else:
714 unit = chunk2
715 chunk2 = ''
716
717 flag = False
718
719 if unit == 'month' or \
720 unit == 'mth' or \
721 unit == 'm':
722 if offset == 0:
723 dy = self.ptc.daysInMonth(mth, yr)
724 sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
725 elif offset == 2:
726
727
728 if dy == self.ptc.daysInMonth(mth, yr):
729 dy = self.ptc.daysInMonth(mth + 1, yr)
730
731 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
732 target = self.inc(start, month=1)
733 sourceTime = target.timetuple()
734 else:
735 start = datetime.datetime(yr, mth, 1, 9, 0, 0)
736 target = self.inc(start, month=offset)
737 sourceTime = target.timetuple()
738
739 flag = True
740 self.dateFlag = 1
741
742 if unit == 'week' or \
743 unit == 'wk' or \
744 unit == 'w':
745 if offset == 0:
746 start = datetime.datetime(yr, mth, dy, 17, 0, 0)
747 target = start + datetime.timedelta(days=(4 - wd))
748 sourceTime = target.timetuple()
749 elif offset == 2:
750 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
751 target = start + datetime.timedelta(days=7)
752 sourceTime = target.timetuple()
753 else:
754 return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
755
756 flag = True
757 self.dateFlag = 1
758
759 if unit == 'day' or \
760 unit == 'dy' or \
761 unit == 'd':
762 if offset == 0:
763 sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
764 self.timeFlag = 2
765 elif offset == 2:
766 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
767 target = start + datetime.timedelta(days=1)
768 sourceTime = target.timetuple()
769 else:
770 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
771 target = start + datetime.timedelta(days=offset)
772 sourceTime = target.timetuple()
773
774 flag = True
775 self.dateFlag = 1
776
777 if unit == 'hour' or \
778 unit == 'hr':
779 if offset == 0:
780 sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
781 else:
782 start = datetime.datetime(yr, mth, dy, hr, 0, 0)
783 target = start + datetime.timedelta(hours=offset)
784 sourceTime = target.timetuple()
785
786 flag = True
787 self.timeFlag = 2
788
789 if unit == 'year' or \
790 unit == 'yr' or \
791 unit == 'y':
792 if offset == 0:
793 sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
794 elif offset == 2:
795 sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
796 else:
797 sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
798
799 flag = True
800 self.dateFlag = 1
801
802 if flag == False:
803 m = self.ptc.CRE_WEEKDAY.match(unit)
804 if m is not None:
805 wkdy = m.group()
806 self.dateFlag = 1
807
808 if modifier == 'eod':
809
810 self.modifierFlag = False
811 (sourceTime, _) = self.parse(wkdy, sourceTime)
812 sources = self.ptc.buildSources(sourceTime)
813 self.timeFlag = 2
814
815 if modifier in sources:
816 sourceTime = sources[modifier]
817
818 else:
819 wkdy = self.ptc.WeekdayOffsets[wkdy]
820 diff = self._CalculateDOWDelta(wd, wkdy, offset,
821 self.ptc.DOWParseStyle,
822 self.ptc.CurrentDOWParseStyle)
823 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
824 target = start + datetime.timedelta(days=diff)
825 sourceTime = target.timetuple()
826
827 flag = True
828 self.dateFlag = 1
829
830 if not flag:
831 m = self.ptc.CRE_TIME.match(unit)
832 if m is not None:
833 self.modifierFlag = False
834 (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
835
836 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
837 target = start + datetime.timedelta(days=offset)
838 sourceTime = target.timetuple()
839 flag = True
840 else:
841 self.modifierFlag = False
842
843
844
845 t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
846
847 if flag2 != 0:
848 sourceTime = t
849
850 sources = self.ptc.buildSources(sourceTime)
851
852 if modifier in sources:
853 sourceTime = sources[modifier]
854 flag = True
855 self.timeFlag = 2
856
857
858
859
860 if not flag:
861 if offset < 0:
862
863 unit = '-%s' % unit
864
865 chunk2 = '%s %s' % (unit, chunk2)
866
867 self.modifierFlag = False
868
869
870 return '%s' % chunk2, sourceTime
871
873 """
874 Evaluate the C{modifier} string and following text (passed in
875 as C{chunk1} and C{chunk2}) and if they match any known modifiers
876 calculate the delta and apply it to C{sourceTime}.
877
878 @type modifier: string
879 @param modifier: modifier text to apply to C{sourceTime}
880 @type chunk1: string
881 @param chunk1: first text chunk that followed modifier (if any)
882 @type chunk2: string
883 @param chunk2: second text chunk that followed modifier (if any)
884 @type sourceTime: struct_time
885 @param sourceTime: C{struct_time} value to use as the base
886
887 @rtype: tuple
888 @return: tuple of: remaining text and the modified sourceTime
889 """
890 offset = self.ptc.Modifiers[modifier]
891 digit = r'\d+'
892
893 self.modifier2Flag = False
894
895
896
897
898
899
900
901
902
903
904 if chunk2 != '':
905 if offset < 0:
906 m = re.match(digit, chunk2.strip())
907 if m is not None:
908 qty = int(m.group()) * -1
909 chunk2 = chunk2[m.end():]
910 chunk2 = '%d%s' % (qty, chunk2)
911
912 sourceTime, flag1 = self.parse(chunk2, sourceTime)
913 if flag1 == 0:
914 flag1 = True
915 else:
916 flag1 = False
917 flag2 = False
918 else:
919 flag1 = False
920
921 if chunk1 != '':
922 if offset < 0:
923 m = re.search(digit, chunk1.strip())
924 if m is not None:
925 qty = int(m.group()) * -1
926 chunk1 = chunk1[m.end():]
927 chunk1 = '%d%s' % (qty, chunk1)
928
929 tempDateFlag = self.dateFlag
930 tempTimeFlag = self.timeFlag
931 sourceTime2, flag2 = self.parse(chunk1, sourceTime)
932 else:
933 return sourceTime, (flag1 and flag2)
934
935
936
937 if not (flag1 == False and flag2 == 0):
938 sourceTime = sourceTime2
939 else:
940 self.timeFlag = tempTimeFlag
941 self.dateFlag = tempDateFlag
942
943 return sourceTime, (flag1 and flag2)
944
945
946 - def _evalString(self, datetimeString, sourceTime=None):
947 """
948 Calculate the datetime based on flags set by the L{parse()} routine
949
950 Examples handled::
951 RFC822, W3CDTF formatted dates
952 HH:MM[:SS][ am/pm]
953 MM/DD/YYYY
954 DD MMMM YYYY
955
956 @type datetimeString: string
957 @param datetimeString: text to try and parse as more "traditional"
958 date/time text
959 @type sourceTime: struct_time
960 @param sourceTime: C{struct_time} value to use as the base
961
962 @rtype: datetime
963 @return: calculated C{struct_time} value or current C{struct_time}
964 if not parsed
965 """
966 s = datetimeString.strip()
967 now = time.localtime()
968
969
970 if sourceTime is None:
971 sourceTime = _parse_date_rfc822(s)
972
973 if sourceTime is not None:
974 (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
975 self.dateFlag = 1
976
977 if (hr != 0) and (mn != 0) and (sec != 0):
978 self.timeFlag = 2
979
980 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
981
982
983 if sourceTime is None:
984 sourceTime = _parse_date_w3dtf(s)
985
986 if sourceTime is not None:
987 self.dateFlag = 1
988 self.timeFlag = 2
989
990 if sourceTime is None:
991 s = s.lower()
992
993
994 if self.meridianFlag:
995 if sourceTime is None:
996 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
997 else:
998 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
999
1000 m = self.ptc.CRE_TIMEHMS2.search(s)
1001 if m is not None:
1002 dt = s[:m.start('meridian')].strip()
1003 if len(dt) <= 2:
1004 hr = int(dt)
1005 mn = 0
1006 sec = 0
1007 else:
1008 hr, mn, sec = _extract_time(m)
1009
1010 if hr == 24:
1011 hr = 0
1012
1013 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
1014 meridian = m.group('meridian').lower()
1015
1016
1017 if (meridian in self.ptc.am) and hr == 12:
1018 sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
1019
1020
1021 if (meridian in self.ptc.pm) and hr < 12:
1022 sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst)
1023
1024
1025 if hr > 24 or mn > 59 or sec > 59:
1026 sourceTime = now
1027 self.dateFlag = 0
1028 self.timeFlag = 0
1029
1030 self.meridianFlag = False
1031
1032
1033 if self.timeStdFlag:
1034 if sourceTime is None:
1035 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
1036 else:
1037 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
1038
1039 m = self.ptc.CRE_TIMEHMS.search(s)
1040 if m is not None:
1041 hr, mn, sec = _extract_time(m)
1042 if hr == 24:
1043 hr = 0
1044
1045 if hr > 24 or mn > 59 or sec > 59:
1046
1047 sourceTime = now
1048 self.dateFlag = 0
1049 self.timeFlag = 0
1050 else:
1051 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
1052
1053 self.timeStdFlag = False
1054
1055
1056 if self.dateStdFlag:
1057 sourceTime = self.parseDate(s)
1058 self.dateStdFlag = False
1059
1060
1061 if self.dateStrFlag:
1062 sourceTime = self.parseDateText(s)
1063 self.dateStrFlag = False
1064
1065
1066 if self.weekdyFlag:
1067 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
1068
1069 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
1070 wkdy = self.ptc.WeekdayOffsets[s]
1071
1072 if wkdy > wd:
1073 qty = self._CalculateDOWDelta(wd, wkdy, 2,
1074 self.ptc.DOWParseStyle,
1075 self.ptc.CurrentDOWParseStyle)
1076 else:
1077 qty = self._CalculateDOWDelta(wd, wkdy, 2,
1078 self.ptc.DOWParseStyle,
1079 self.ptc.CurrentDOWParseStyle)
1080
1081 target = start + datetime.timedelta(days=qty)
1082 wd = wkdy
1083
1084 sourceTime = target.timetuple()
1085 self.weekdyFlag = False
1086
1087
1088
1089 if self.timeStrFlag:
1090 if s in self.ptc.re_values['now']:
1091 sourceTime = now
1092 else:
1093 sources = self.ptc.buildSources(sourceTime)
1094
1095 if s in sources:
1096 sourceTime = sources[s]
1097 else:
1098 sourceTime = now
1099 self.dateFlag = 0
1100 self.timeFlag = 0
1101
1102 self.timeStrFlag = False
1103
1104
1105 if self.dayStrFlag:
1106 if sourceTime is None:
1107 sourceTime = now
1108
1109 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
1110
1111 if s in self.ptc.dayOffsets:
1112 offset = self.ptc.dayOffsets[s]
1113 else:
1114 offset = 0
1115
1116 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
1117 target = start + datetime.timedelta(days=offset)
1118 sourceTime = target.timetuple()
1119
1120 self.dayStrFlag = False
1121
1122
1123 if self.unitsFlag:
1124 modifier = ''
1125
1126 if sourceTime is None:
1127 sourceTime = now
1128
1129 m = self.ptc.CRE_UNITS.search(s)
1130 if m is not None:
1131 units = m.group('units')
1132 quantity = s[:m.start('units')]
1133
1134 sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
1135 self.unitsFlag = False
1136
1137
1138 if self.qunitsFlag:
1139 modifier = ''
1140
1141 if sourceTime is None:
1142 sourceTime = now
1143
1144 m = self.ptc.CRE_QUNITS.search(s)
1145 if m is not None:
1146 units = m.group('qunits')
1147 quantity = s[:m.start('qunits')]
1148
1149 sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
1150 self.qunitsFlag = False
1151
1152
1153 if sourceTime is None:
1154 sourceTime = now
1155 self.dateFlag = 0
1156 self.timeFlag = 0
1157
1158 return sourceTime
1159
1160
1161 - def parse(self, datetimeString, sourceTime=None):
1162 """
1163 Splits the given C{datetimeString} into tokens, finds the regex
1164 patterns that match and then calculates a C{struct_time} value from
1165 the chunks.
1166
1167 If C{sourceTime} is given then the C{struct_time} value will be
1168 calculated from that value, otherwise from the current date/time.
1169
1170 If the C{datetimeString} is parsed and date/time value found then
1171 the second item of the returned tuple will be a flag to let you know
1172 what kind of C{struct_time} value is being returned::
1173
1174 0 = not parsed at all
1175 1 = parsed as a C{date}
1176 2 = parsed as a C{time}
1177 3 = parsed as a C{datetime}
1178
1179 @type datetimeString: string
1180 @param datetimeString: date/time text to evaluate
1181 @type sourceTime: struct_time
1182 @param sourceTime: C{struct_time} value to use as the base
1183
1184 @rtype: tuple
1185 @return: tuple of: modified C{sourceTime} and the result flag
1186 """
1187
1188 if sourceTime:
1189 if isinstance(sourceTime, datetime.datetime):
1190 if _debug:
1191 print 'coercing datetime to timetuple'
1192 sourceTime = sourceTime.timetuple()
1193 else:
1194 if not isinstance(sourceTime, time.struct_time) and \
1195 not isinstance(sourceTime, tuple):
1196 raise Exception('sourceTime is not a struct_time')
1197
1198 s = datetimeString.strip().lower()
1199 parseStr = ''
1200 totalTime = sourceTime
1201
1202 if s == '' :
1203 if sourceTime is not None:
1204 return (sourceTime, self.dateFlag + self.timeFlag)
1205 else:
1206 return (time.localtime(), 0)
1207
1208 self.timeFlag = 0
1209 self.dateFlag = 0
1210
1211 while len(s) > 0:
1212 flag = False
1213 chunk1 = ''
1214 chunk2 = ''
1215
1216 if _debug:
1217 print 'parse (top of loop): [%s][%s]' % (s, parseStr)
1218
1219 if parseStr == '':
1220
1221 m = self.ptc.CRE_MODIFIER.search(s)
1222 if m is not None:
1223 self.modifierFlag = True
1224 if (m.group('modifier') != s):
1225
1226 parseStr = m.group('modifier')
1227 chunk1 = s[:m.start('modifier')].strip()
1228 chunk2 = s[m.end('modifier'):].strip()
1229 flag = True
1230 else:
1231 parseStr = s
1232
1233 if parseStr == '':
1234
1235 m = self.ptc.CRE_MODIFIER2.search(s)
1236 if m is not None:
1237 self.modifier2Flag = True
1238 if (m.group('modifier') != s):
1239
1240 parseStr = m.group('modifier')
1241 chunk1 = s[:m.start('modifier')].strip()
1242 chunk2 = s[m.end('modifier'):].strip()
1243 flag = True
1244 else:
1245 parseStr = s
1246
1247 if parseStr == '':
1248
1249 m = self.ptc.CRE_DATE3.search(s)
1250 if m is not None:
1251 self.dateStrFlag = True
1252 self.dateFlag = 1
1253 if (m.group('date') != s):
1254
1255 parseStr = m.group('date')
1256 chunk1 = s[:m.start('date')]
1257 chunk2 = s[m.end('date'):]
1258 s = '%s %s' % (chunk1, chunk2)
1259 flag = True
1260 else:
1261 parseStr = s
1262
1263 if parseStr == '':
1264
1265 m = self.ptc.CRE_DATE.search(s)
1266 if m is not None:
1267 self.dateStdFlag = True
1268 self.dateFlag = 1
1269 if (m.group('date') != s):
1270
1271 parseStr = m.group('date')
1272 chunk1 = s[:m.start('date')]
1273 chunk2 = s[m.end('date'):]
1274 s = '%s %s' % (chunk1, chunk2)
1275 flag = True
1276 else:
1277 parseStr = s
1278
1279 if parseStr == '':
1280
1281 m = self.ptc.CRE_DAY.search(s)
1282 if m is not None:
1283 self.dayStrFlag = True
1284 self.dateFlag = 1
1285 if (m.group('day') != s):
1286
1287 parseStr = m.group('day')
1288 chunk1 = s[:m.start('day')]
1289 chunk2 = s[m.end('day'):]
1290 s = '%s %s' % (chunk1, chunk2)
1291 flag = True
1292 else:
1293 parseStr = s
1294
1295 if parseStr == '':
1296
1297 m = self.ptc.CRE_UNITS.search(s)
1298 if m is not None:
1299 self.unitsFlag = True
1300 if (m.group('qty') != s):
1301
1302 parseStr = m.group('qty')
1303 chunk1 = s[:m.start('qty')].strip()
1304 chunk2 = s[m.end('qty'):].strip()
1305
1306 if chunk1[-1:] == '-':
1307 parseStr = '-%s' % parseStr
1308 chunk1 = chunk1[:-1]
1309
1310 s = '%s %s' % (chunk1, chunk2)
1311 flag = True
1312 else:
1313 parseStr = s
1314
1315 if parseStr == '':
1316
1317 m = self.ptc.CRE_QUNITS.search(s)
1318 if m is not None:
1319 self.qunitsFlag = True
1320
1321 if (m.group('qty') != s):
1322
1323 parseStr = m.group('qty')
1324 chunk1 = s[:m.start('qty')].strip()
1325 chunk2 = s[m.end('qty'):].strip()
1326
1327 if chunk1[-1:] == '-':
1328 parseStr = '-%s' % parseStr
1329 chunk1 = chunk1[:-1]
1330
1331 s = '%s %s' % (chunk1, chunk2)
1332 flag = True
1333 else:
1334 parseStr = s
1335
1336 if parseStr == '':
1337
1338 m = self.ptc.CRE_WEEKDAY.search(s)
1339 if m is not None:
1340 gv = m.group('weekday')
1341 if s not in self.ptc.dayOffsets:
1342 self.weekdyFlag = True
1343 self.dateFlag = 1
1344 if (gv != s):
1345
1346 parseStr = gv
1347 chunk1 = s[:m.start('weekday')]
1348 chunk2 = s[m.end('weekday'):]
1349 s = '%s %s' % (chunk1, chunk2)
1350 flag = True
1351 else:
1352 parseStr = s
1353
1354 if parseStr == '':
1355
1356 m = self.ptc.CRE_TIME.search(s)
1357 if m is not None:
1358 self.timeStrFlag = True
1359 self.timeFlag = 2
1360 if (m.group('time') != s):
1361
1362 parseStr = m.group('time')
1363 chunk1 = s[:m.start('time')]
1364 chunk2 = s[m.end('time'):]
1365 s = '%s %s' % (chunk1, chunk2)
1366 flag = True
1367 else:
1368 parseStr = s
1369
1370 if parseStr == '':
1371
1372 m = self.ptc.CRE_TIMEHMS2.search(s)
1373 if m is not None:
1374 self.meridianFlag = True
1375 self.timeFlag = 2
1376 if m.group('minutes') is not None:
1377 if m.group('seconds') is not None:
1378 parseStr = '%s:%s:%s %s' % (m.group('hours'),
1379 m.group('minutes'),
1380 m.group('seconds'),
1381 m.group('meridian'))
1382 else:
1383 parseStr = '%s:%s %s' % (m.group('hours'),
1384 m.group('minutes'),
1385 m.group('meridian'))
1386 else:
1387 parseStr = '%s %s' % (m.group('hours'),
1388 m.group('meridian'))
1389
1390 chunk1 = s[:m.start('hours')]
1391 chunk2 = s[m.end('meridian'):]
1392
1393 s = '%s %s' % (chunk1, chunk2)
1394 flag = True
1395
1396 if parseStr == '':
1397
1398 m = self.ptc.CRE_TIMEHMS.search(s)
1399 if m is not None:
1400 self.timeStdFlag = True
1401 self.timeFlag = 2
1402 if m.group('seconds') is not None:
1403 parseStr = '%s:%s:%s' % (m.group('hours'),
1404 m.group('minutes'),
1405 m.group('seconds'))
1406 chunk1 = s[:m.start('hours')]
1407 chunk2 = s[m.end('seconds'):]
1408 else:
1409 parseStr = '%s:%s' % (m.group('hours'),
1410 m.group('minutes'))
1411 chunk1 = s[:m.start('hours')]
1412 chunk2 = s[m.end('minutes'):]
1413
1414 s = '%s %s' % (chunk1, chunk2)
1415 flag = True
1416
1417
1418
1419 if not flag:
1420 s = ''
1421
1422 if _debug:
1423 print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
1424 print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1425 (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
1426 print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
1427 (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
1428
1429
1430 if parseStr != '':
1431 if self.modifierFlag == True:
1432 t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
1433
1434
1435
1436
1437 if (t != '') and (t != None):
1438 tempDateFlag = self.dateFlag
1439 tempTimeFlag = self.timeFlag
1440 (totalTime2, flag) = self.parse(t, totalTime)
1441
1442 if flag == 0 and totalTime is not None:
1443 self.timeFlag = tempTimeFlag
1444 self.dateFlag = tempDateFlag
1445
1446 return (totalTime, self.dateFlag + self.timeFlag)
1447 else:
1448 return (totalTime2, self.dateFlag + self.timeFlag)
1449
1450 elif self.modifier2Flag == True:
1451 totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
1452
1453 if invalidFlag == True:
1454 self.dateFlag = 0
1455 self.timeFlag = 0
1456
1457 else:
1458 totalTime = self._evalString(parseStr, totalTime)
1459 parseStr = ''
1460
1461
1462 if totalTime is None or totalTime == sourceTime:
1463 totalTime = time.localtime()
1464 self.dateFlag = 0
1465 self.timeFlag = 0
1466
1467 return (totalTime, self.dateFlag + self.timeFlag)
1468
1469
1470 - def inc(self, source, month=None, year=None):
1471 """
1472 Takes the given C{source} date, or current date if none is
1473 passed, and increments it according to the values passed in
1474 by month and/or year.
1475
1476 This routine is needed because Python's C{timedelta()} function
1477 does not allow for month or year increments.
1478
1479 @type source: struct_time
1480 @param source: C{struct_time} value to increment
1481 @type month: integer
1482 @param month: optional number of months to increment
1483 @type year: integer
1484 @param year: optional number of years to increment
1485
1486 @rtype: datetime
1487 @return: C{source} incremented by the number of months and/or years
1488 """
1489 yr = source.year
1490 mth = source.month
1491 dy = source.day
1492
1493 if year:
1494 try:
1495 yi = int(year)
1496 except ValueError:
1497 yi = 0
1498
1499 yr += yi
1500
1501 if month:
1502 try:
1503 mi = int(month)
1504 except ValueError:
1505 mi = 0
1506
1507 m = abs(mi)
1508 y = m / 12
1509 m = m % 12
1510
1511 if mi < 0:
1512 mth = mth - m
1513 if mth < 1:
1514 y -= 1
1515 mth += 12
1516 else:
1517 mth = mth + m
1518 if mth > 12:
1519 y += 1
1520 mth -= 12
1521
1522 yr += y
1523
1524
1525
1526 if dy > self.ptc.daysInMonth(mth, yr):
1527 dy = self.ptc.daysInMonth(mth, yr)
1528
1529 d = source.replace(year=yr, month=mth, day=dy)
1530
1531 return source + (d - source)
1532