1
2
3 """
4 Parse human-readable date/time text.
5 """
6
7 __license__ = """
8 Copyright (c) 2004-2007 Mike Taylor
9 Copyright (c) 2007 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 (mth > 0 and mth <= 12) and \
375 (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
376 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
377 else:
378 self.dateFlag = 0
379 self.timeFlag = 0
380 sourceTime = time.localtime()
381
382
383 return sourceTime
384
385
386 - def parseDateText(self, dateString):
387 """
388 Parse long-form date strings::
389
390 'May 31st, 2006'
391 'Jan 1st'
392 'July 2006'
393
394 @type dateString: string
395 @param dateString: text to convert to a datetime
396
397 @rtype: struct_time
398 @return: calculated C{struct_time} value of dateString
399 """
400 yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
401
402 currentMth = mth
403 currentDy = dy
404
405 s = dateString.lower()
406 m = self.ptc.CRE_DATE3.search(s)
407 mth = m.group('mthname')
408 mth = self.ptc.MonthOffsets[mth]
409
410 if m.group('day') != None:
411 dy = int(m.group('day'))
412 else:
413 dy = 1
414
415 if m.group('year') != None:
416 yr = int(m.group('year'))
417
418
419 if yr < self.ptc.BirthdayEpoch:
420 yr += 2000
421 elif yr < 100:
422 yr += 1900
423
424 elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
425
426
427 yr += 1
428
429 if dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
430 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
431 else:
432
433 self.dateFlag = 0
434 self.timeFlag = 0
435 sourceTime = time.localtime()
436
437 return sourceTime
438
439
440 - def evalRanges(self, datetimeString, sourceTime=None):
441 """
442 Evaluate the C{datetimeString} text and determine if
443 it represents a date or time range.
444
445 @type datetimeString: string
446 @param datetimeString: datetime text to evaluate
447 @type sourceTime: struct_time
448 @param sourceTime: C{struct_time} value to use as the base
449
450 @rtype: tuple
451 @return: tuple of: start datetime, end datetime and the invalid flag
452 """
453 startTime = ''
454 endTime = ''
455 startDate = ''
456 endDate = ''
457 rangeFlag = 0
458
459 s = datetimeString.strip().lower()
460
461 m = self.ptc.CRE_TIMERNG1.search(s)
462 if m is not None:
463 rangeFlag = 1
464 else:
465 m = self.ptc.CRE_TIMERNG2.search(s)
466 if m is not None:
467 rangeFlag = 2
468 else:
469 m = self.ptc.CRE_TIMERNG3.search(s)
470 if m is not None:
471 rangeFlag = 3
472 else:
473 m = self.ptc.CRE_DATERNG1.search(s)
474 if m is not None:
475 rangeFlag = 4
476 else:
477 m = self.ptc.CRE_DATERNG2.search(s)
478 if m is not None:
479 rangeFlag = 5
480 else:
481 m = self.ptc.CRE_DATERNG3.search(s)
482 if m is not None:
483 rangeFlag = 6
484
485 if _debug:
486 print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
487
488 if m is not None:
489 if (m.group() != s):
490
491 parseStr = m.group()
492 chunk1 = s[:m.start()]
493 chunk2 = s[m.end():]
494 s = '%s %s' % (chunk1, chunk2)
495 flag = 1
496
497 sourceTime, flag = self.parse(s, sourceTime)
498
499 if flag == 0:
500 sourceTime = None
501 else:
502 parseStr = s
503
504 if rangeFlag == 1:
505 m = re.search(self.ptc.rangeSep, parseStr)
506 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
507 endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
508
509 if (eflag != 0) and (sflag != 0):
510 return (startTime, endTime, 2)
511
512 elif rangeFlag == 2:
513 m = re.search(self.ptc.rangeSep, parseStr)
514 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
515 endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
516
517 if (eflag != 0) and (sflag != 0):
518 return (startTime, endTime, 2)
519
520 elif rangeFlag == 3:
521 m = re.search(self.ptc.rangeSep, parseStr)
522
523
524 if self.ptc.usesMeridian:
525 ampm = re.search(self.ptc.am[0], parseStr)
526
527
528 if ampm is not None:
529 startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime)
530 else:
531 startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime)
532 else:
533 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
534
535 endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
536
537 if (eflag != 0) and (sflag != 0):
538 return (startTime, endTime, 2)
539
540 elif rangeFlag == 4:
541 m = re.search(self.ptc.rangeSep, parseStr)
542 startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
543 endDate, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
544
545 if (eflag != 0) and (sflag != 0):
546 return (startDate, endDate, 1)
547
548 elif rangeFlag == 5:
549 m = re.search(self.ptc.rangeSep, parseStr)
550 endDate = parseStr[(m.start() + 1):]
551
552
553 date = self.ptc.CRE_DATE3.search(endDate)
554 endYear = date.group('year')
555
556
557
558
559 if endYear is not None:
560 startDate = (parseStr[:m.start()]).strip()
561 date = self.ptc.CRE_DATE3.search(startDate)
562 startYear = date.group('year')
563
564 if startYear is None:
565 startDate = startDate + ', ' + endYear
566 else:
567 startDate = parseStr[:m.start()]
568
569 startDate, sflag = self.parse(startDate, sourceTime)
570 endDate, eflag = self.parse(endDate, sourceTime)
571
572 if (eflag != 0) and (sflag != 0):
573 return (startDate, endDate, 1)
574
575 elif rangeFlag == 6:
576 m = re.search(self.ptc.rangeSep, parseStr)
577
578 startDate = parseStr[:m.start()]
579
580
581 mth = self.ptc.CRE_DATE3.search(startDate)
582 mth = mth.group('mthname')
583
584
585 endDate = mth + parseStr[(m.start() + 1):]
586
587 startDate, sflag = self.parse(startDate, sourceTime)
588 endDate, eflag = self.parse(endDate, sourceTime)
589
590 if (eflag != 0) and (sflag != 0):
591 return (startDate, endDate, 1)
592 else:
593
594 sourceTime = time.localtime()
595
596 return (sourceTime, sourceTime, 0)
597
598
600 """
601 Based on the C{style} and C{currentDayStyle} determine what
602 day-of-week value is to be returned.
603
604 @type wd: integer
605 @param wd: day-of-week value for the current day
606 @type wkdy: integer
607 @param wkdy: day-of-week value for the parsed day
608 @type offset: integer
609 @param offset: offset direction for any modifiers (-1, 0, 1)
610 @type style: integer
611 @param style: normally the value set in C{Constants.DOWParseStyle}
612 @type currentDayStyle: integer
613 @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}
614
615 @rtype: integer
616 @return: calculated day-of-week
617 """
618 if offset == 1:
619
620
621 diff = 7 - wd + wkdy
622
623 elif offset == -1:
624
625
626 diff = wkdy - wd - 7
627
628 elif offset == 0:
629
630
631 diff = wkdy - wd
632
633 elif offset == 2:
634
635
636 if style == 1:
637
638 if currentDayStyle == True:
639 if wkdy >= wd:
640 diff = wkdy - wd
641 else:
642 diff = 7 - wd + wkdy
643 else:
644 if wkdy > wd:
645 diff = wkdy - wd
646 else:
647 diff = 7 - wd + wkdy
648
649 elif style == -1:
650
651 if currentDayStyle == True:
652 if wkdy <= wd:
653 diff = wkdy - wd
654 else:
655 diff = wkdy - wd - 7
656 else:
657 if wkdy < wd:
658 diff = wkdy - wd
659 else:
660 diff = wkdy - wd - 7
661 else:
662
663 diff = wkdy - wd
664
665 if _debug:
666 print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
667
668 return diff
669
670
672 """
673 Evaluate the C{modifier} string and following text (passed in
674 as C{chunk1} and C{chunk2}) and if they match any known modifiers
675 calculate the delta and apply it to C{sourceTime}.
676
677 @type modifier: string
678 @param modifier: modifier text to apply to sourceTime
679 @type chunk1: string
680 @param chunk1: first text chunk that followed modifier (if any)
681 @type chunk2: string
682 @param chunk2: second text chunk that followed modifier (if any)
683 @type sourceTime: struct_time
684 @param sourceTime: C{struct_time} value to use as the base
685
686 @rtype: tuple
687 @return: tuple of: remaining text and the modified sourceTime
688 """
689 offset = self.ptc.Modifiers[modifier]
690
691 if sourceTime is not None:
692 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
693 else:
694 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
695
696
697
698 m = self.ptc.CRE_REMAINING.search(chunk2)
699 if m is not None:
700 index = m.start() + 1
701 unit = chunk2[:m.start()]
702 chunk2 = chunk2[index:]
703 else:
704 unit = chunk2
705 chunk2 = ''
706
707 flag = False
708
709 if unit == 'month' or \
710 unit == 'mth' or \
711 unit == 'm':
712 if offset == 0:
713 dy = self.ptc.DaysInMonthList[mth - 1]
714 sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
715 elif offset == 2:
716
717
718 if dy == self.ptc.DaysInMonthList[mth - 1]:
719 dy = self.ptc.DaysInMonthList[mth]
720
721 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
722 target = self.inc(start, month=1)
723 sourceTime = target.timetuple()
724 else:
725 start = datetime.datetime(yr, mth, 1, 9, 0, 0)
726 target = self.inc(start, month=offset)
727 sourceTime = target.timetuple()
728
729 flag = True
730 self.dateFlag = 1
731
732 if unit == 'week' or \
733 unit == 'wk' or \
734 unit == 'w':
735 if offset == 0:
736 start = datetime.datetime(yr, mth, dy, 17, 0, 0)
737 target = start + datetime.timedelta(days=(4 - wd))
738 sourceTime = target.timetuple()
739 elif offset == 2:
740 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
741 target = start + datetime.timedelta(days=7)
742 sourceTime = target.timetuple()
743 else:
744 return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
745
746 flag = True
747 self.dateFlag = 1
748
749 if unit == 'day' or \
750 unit == 'dy' or \
751 unit == 'd':
752 if offset == 0:
753 sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
754 self.timeFlag = 2
755 elif offset == 2:
756 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
757 target = start + datetime.timedelta(days=1)
758 sourceTime = target.timetuple()
759 else:
760 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
761 target = start + datetime.timedelta(days=offset)
762 sourceTime = target.timetuple()
763
764 flag = True
765 self.dateFlag = 1
766
767 if unit == 'hour' or \
768 unit == 'hr':
769 if offset == 0:
770 sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
771 else:
772 start = datetime.datetime(yr, mth, dy, hr, 0, 0)
773 target = start + datetime.timedelta(hours=offset)
774 sourceTime = target.timetuple()
775
776 flag = True
777 self.timeFlag = 2
778
779 if unit == 'year' or \
780 unit == 'yr' or \
781 unit == 'y':
782 if offset == 0:
783 sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
784 elif offset == 2:
785 sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
786 else:
787 sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
788
789 flag = True
790 self.dateFlag = 1
791
792 if flag == False:
793 m = self.ptc.CRE_WEEKDAY.match(unit)
794 if m is not None:
795 wkdy = m.group()
796 self.dateFlag = 1
797
798 if modifier == 'eod':
799
800 self.modifierFlag = False
801 (sourceTime, _) = self.parse(wkdy, sourceTime)
802 sources = self.ptc.buildSources(sourceTime)
803 self.timeFlag = 2
804
805 if modifier in sources:
806 sourceTime = sources[modifier]
807
808 else:
809 wkdy = self.ptc.WeekdayOffsets[wkdy]
810 diff = self._CalculateDOWDelta(wd, wkdy, offset,
811 self.ptc.DOWParseStyle,
812 self.ptc.CurrentDOWParseStyle)
813 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
814 target = start + datetime.timedelta(days=diff)
815 sourceTime = target.timetuple()
816
817 flag = True
818 self.dateFlag = 1
819
820 if not flag:
821 m = self.ptc.CRE_TIME.match(unit)
822 if m is not None:
823 self.modifierFlag = False
824 (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
825
826 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
827 target = start + datetime.timedelta(days=offset)
828 sourceTime = target.timetuple()
829 flag = True
830 else:
831 self.modifierFlag = False
832
833
834
835 t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
836
837 if flag2 != 0:
838 sourceTime = t
839
840 sources = self.ptc.buildSources(sourceTime)
841
842 if modifier in sources:
843 sourceTime = sources[modifier]
844 flag = True
845 self.timeFlag = 2
846
847
848
849
850 if not flag:
851 if offset < 0:
852
853 unit = '-%s' % unit
854
855 chunk2 = '%s %s' % (unit, chunk2)
856
857 self.modifierFlag = False
858
859
860 return '%s' % chunk2, sourceTime
861
863 """
864 Evaluate the C{modifier} string and following text (passed in
865 as C{chunk1} and C{chunk2}) and if they match any known modifiers
866 calculate the delta and apply it to C{sourceTime}.
867
868 @type modifier: string
869 @param modifier: modifier text to apply to C{sourceTime}
870 @type chunk1: string
871 @param chunk1: first text chunk that followed modifier (if any)
872 @type chunk2: string
873 @param chunk2: second text chunk that followed modifier (if any)
874 @type sourceTime: struct_time
875 @param sourceTime: C{struct_time} value to use as the base
876
877 @rtype: tuple
878 @return: tuple of: remaining text and the modified sourceTime
879 """
880 offset = self.ptc.Modifiers[modifier]
881 digit = r'\d+'
882
883 self.modifier2Flag = False
884
885
886
887
888
889
890
891
892
893
894 if chunk2 != '':
895 if offset < 0:
896 m = re.match(digit, chunk2.strip())
897 if m is not None:
898 qty = int(m.group()) * -1
899 chunk2 = chunk2[m.end():]
900 chunk2 = '%d%s' % (qty, chunk2)
901
902 sourceTime, flag1 = self.parse(chunk2, sourceTime)
903 if flag1 == 0:
904 flag1 = True
905 else:
906 flag1 = False
907 flag2 = False
908 else:
909 flag1 = False
910
911 if chunk1 != '':
912 if offset < 0:
913 m = re.search(digit, chunk1.strip())
914 if m is not None:
915 qty = int(m.group()) * -1
916 chunk1 = chunk1[m.end():]
917 chunk1 = '%d%s' % (qty, chunk1)
918
919 tempDateFlag = self.dateFlag
920 tempTimeFlag = self.timeFlag
921 sourceTime2, flag2 = self.parse(chunk1, sourceTime)
922 else:
923 return sourceTime, (flag1 and flag2)
924
925
926
927 if not (flag1 == False and flag2 == 0):
928 sourceTime = sourceTime2
929 else:
930 self.timeFlag = tempTimeFlag
931 self.dateFlag = tempDateFlag
932
933 return sourceTime, (flag1 and flag2)
934
935
936 - def _evalString(self, datetimeString, sourceTime=None):
937 """
938 Calculate the datetime based on flags set by the L{parse()} routine
939
940 Examples handled::
941 RFC822, W3CDTF formatted dates
942 HH:MM[:SS][ am/pm]
943 MM/DD/YYYY
944 DD MMMM YYYY
945
946 @type datetimeString: string
947 @param datetimeString: text to try and parse as more "traditional"
948 date/time text
949 @type sourceTime: struct_time
950 @param sourceTime: C{struct_time} value to use as the base
951
952 @rtype: datetime
953 @return: calculated C{struct_time} value or current C{struct_time}
954 if not parsed
955 """
956 s = datetimeString.strip()
957 now = time.localtime()
958
959
960 if sourceTime is None:
961 sourceTime = _parse_date_rfc822(s)
962
963 if sourceTime is not None:
964 (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
965 self.dateFlag = 1
966
967 if (hr != 0) and (mn != 0) and (sec != 0):
968 self.timeFlag = 2
969
970 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
971
972
973 if sourceTime is None:
974 sourceTime = _parse_date_w3dtf(s)
975
976 if sourceTime is not None:
977 self.dateFlag = 1
978 self.timeFlag = 2
979
980 if sourceTime is None:
981 s = s.lower()
982
983
984 if self.meridianFlag:
985 if sourceTime is None:
986 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
987 else:
988 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
989
990 m = self.ptc.CRE_TIMEHMS2.search(s)
991 if m is not None:
992 dt = s[:m.start('meridian')].strip()
993 if len(dt) <= 2:
994 hr = int(dt)
995 mn = 0
996 sec = 0
997 else:
998 hr, mn, sec = _extract_time(m)
999
1000 if hr == 24:
1001 hr = 0
1002
1003 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
1004 meridian = m.group('meridian').lower()
1005
1006
1007 if (meridian in self.ptc.am) and hr == 12:
1008 sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
1009
1010
1011 if (meridian in self.ptc.pm) and hr < 12:
1012 sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst)
1013
1014
1015 if hr > 24 or mn > 59 or sec > 59:
1016 sourceTime = now
1017 self.dateFlag = 0
1018 self.timeFlag = 0
1019
1020 self.meridianFlag = False
1021
1022
1023 if self.timeStdFlag:
1024 if sourceTime is None:
1025 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
1026 else:
1027 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
1028
1029 m = self.ptc.CRE_TIMEHMS.search(s)
1030 if m is not None:
1031 hr, mn, sec = _extract_time(m)
1032 if hr == 24:
1033 hr = 0
1034
1035 if hr > 24 or mn > 59 or sec > 59:
1036
1037 sourceTime = now
1038 self.dateFlag = 0
1039 self.timeFlag = 0
1040 else:
1041 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
1042
1043 self.timeStdFlag = False
1044
1045
1046 if self.dateStdFlag:
1047 sourceTime = self.parseDate(s)
1048 self.dateStdFlag = False
1049
1050
1051 if self.dateStrFlag:
1052 sourceTime = self.parseDateText(s)
1053 self.dateStrFlag = False
1054
1055
1056 if self.weekdyFlag:
1057 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
1058
1059 start = datetime.datetime(yr, mth, dy, hr, mn, sec)
1060 wkdy = self.ptc.WeekdayOffsets[s]
1061
1062 if wkdy > wd:
1063 qty = self._CalculateDOWDelta(wd, wkdy, 2,
1064 self.ptc.DOWParseStyle,
1065 self.ptc.CurrentDOWParseStyle)
1066 else:
1067 qty = self._CalculateDOWDelta(wd, wkdy, 2,
1068 self.ptc.DOWParseStyle,
1069 self.ptc.CurrentDOWParseStyle)
1070
1071 target = start + datetime.timedelta(days=qty)
1072 wd = wkdy
1073
1074 sourceTime = target.timetuple()
1075 self.weekdyFlag = False
1076
1077
1078
1079 if self.timeStrFlag:
1080 if s in self.ptc.re_values['now']:
1081 sourceTime = now
1082 else:
1083 sources = self.ptc.buildSources(sourceTime)
1084
1085 if s in sources:
1086 sourceTime = sources[s]
1087 else:
1088 sourceTime = now
1089 self.dateFlag = 0
1090 self.timeFlag = 0
1091
1092 self.timeStrFlag = False
1093
1094
1095 if self.dayStrFlag:
1096 if sourceTime is None:
1097 sourceTime = now
1098
1099 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
1100
1101 if s in self.ptc.dayOffsets:
1102 offset = self.ptc.dayOffsets[s]
1103 else:
1104 offset = 0
1105
1106 start = datetime.datetime(yr, mth, dy, 9, 0, 0)
1107 target = start + datetime.timedelta(days=offset)
1108 sourceTime = target.timetuple()
1109
1110 self.dayStrFlag = False
1111
1112
1113 if self.unitsFlag:
1114 modifier = ''
1115
1116 if sourceTime is None:
1117 sourceTime = now
1118
1119 m = self.ptc.CRE_UNITS.search(s)
1120 if m is not None:
1121 units = m.group('units')
1122 quantity = s[:m.start('units')]
1123
1124 sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
1125 self.unitsFlag = False
1126
1127
1128 if self.qunitsFlag:
1129 modifier = ''
1130
1131 if sourceTime is None:
1132 sourceTime = now
1133
1134 m = self.ptc.CRE_QUNITS.search(s)
1135 if m is not None:
1136 units = m.group('qunits')
1137 quantity = s[:m.start('qunits')]
1138
1139 sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
1140 self.qunitsFlag = False
1141
1142
1143 if sourceTime is None:
1144 sourceTime = now
1145 self.dateFlag = 0
1146 self.timeFlag = 0
1147
1148 return sourceTime
1149
1150
1151 - def parse(self, datetimeString, sourceTime=None):
1152 """
1153 Splits the given C{datetimeString} into tokens, finds the regex
1154 patterns that match and then calculates a C{struct_time} value from
1155 the chunks.
1156
1157 If C{sourceTime} is given then the C{struct_time} value will be
1158 calculated from that value, otherwise from the current date/time.
1159
1160 If the C{datetimeString} is parsed and date/time value found then
1161 the second item of the returned tuple will be a flag to let you know
1162 what kind of C{struct_time} value is being returned::
1163
1164 0 = not parsed at all
1165 1 = parsed as a C{date}
1166 2 = parsed as a C{time}
1167 3 = parsed as a C{datetime}
1168
1169 @type datetimeString: string
1170 @param datetimeString: date/time text to evaluate
1171 @type sourceTime: struct_time
1172 @param sourceTime: C{struct_time} value to use as the base
1173
1174 @rtype: tuple
1175 @return: tuple of: modified C{sourceTime} and the result flag
1176 """
1177
1178 if sourceTime:
1179 if isinstance(sourceTime, datetime.datetime):
1180 if _debug:
1181 print 'coercing datetime to timetuple'
1182 sourceTime = sourceTime.timetuple()
1183 else:
1184 if not isinstance(sourceTime, time.struct_time) and \
1185 not isinstance(sourceTime, tuple):
1186 raise Exception('sourceTime is not a struct_time')
1187
1188 s = datetimeString.strip().lower()
1189 parseStr = ''
1190 totalTime = sourceTime
1191
1192 if s == '' :
1193 if sourceTime is not None:
1194 return (sourceTime, self.dateFlag + self.timeFlag)
1195 else:
1196 return (time.localtime(), 0)
1197
1198 self.timeFlag = 0
1199 self.dateFlag = 0
1200
1201 while len(s) > 0:
1202 flag = False
1203 chunk1 = ''
1204 chunk2 = ''
1205
1206 if _debug:
1207 print 'parse (top of loop): [%s][%s]' % (s, parseStr)
1208
1209 if parseStr == '':
1210
1211 m = self.ptc.CRE_MODIFIER.search(s)
1212 if m is not None:
1213 self.modifierFlag = True
1214 if (m.group('modifier') != s):
1215
1216 parseStr = m.group('modifier')
1217 chunk1 = s[:m.start('modifier')].strip()
1218 chunk2 = s[m.end('modifier'):].strip()
1219 flag = True
1220 else:
1221 parseStr = s
1222
1223 if parseStr == '':
1224
1225 m = self.ptc.CRE_MODIFIER2.search(s)
1226 if m is not None:
1227 self.modifier2Flag = True
1228 if (m.group('modifier') != s):
1229
1230 parseStr = m.group('modifier')
1231 chunk1 = s[:m.start('modifier')].strip()
1232 chunk2 = s[m.end('modifier'):].strip()
1233 flag = True
1234 else:
1235 parseStr = s
1236
1237 if parseStr == '':
1238
1239 m = self.ptc.CRE_DATE3.search(s)
1240 if m is not None:
1241 self.dateStrFlag = True
1242 self.dateFlag = 1
1243 if (m.group('date') != s):
1244
1245 parseStr = m.group('date')
1246 chunk1 = s[:m.start('date')]
1247 chunk2 = s[m.end('date'):]
1248 s = '%s %s' % (chunk1, chunk2)
1249 flag = True
1250 else:
1251 parseStr = s
1252
1253 if parseStr == '':
1254
1255 m = self.ptc.CRE_DATE.search(s)
1256 if m is not None:
1257 self.dateStdFlag = True
1258 self.dateFlag = 1
1259 if (m.group('date') != s):
1260
1261 parseStr = m.group('date')
1262 chunk1 = s[:m.start('date')]
1263 chunk2 = s[m.end('date'):]
1264 s = '%s %s' % (chunk1, chunk2)
1265 flag = True
1266 else:
1267 parseStr = s
1268
1269 if parseStr == '':
1270
1271 m = self.ptc.CRE_DAY.search(s)
1272 if m is not None:
1273 self.dayStrFlag = True
1274 self.dateFlag = 1
1275 if (m.group('day') != s):
1276
1277 parseStr = m.group('day')
1278 chunk1 = s[:m.start('day')]
1279 chunk2 = s[m.end('day'):]
1280 s = '%s %s' % (chunk1, chunk2)
1281 flag = True
1282 else:
1283 parseStr = s
1284
1285 if parseStr == '':
1286
1287 m = self.ptc.CRE_UNITS.search(s)
1288 if m is not None:
1289 self.unitsFlag = True
1290 if (m.group('qty') != s):
1291
1292 parseStr = m.group('qty')
1293 chunk1 = s[:m.start('qty')].strip()
1294 chunk2 = s[m.end('qty'):].strip()
1295
1296 if chunk1[-1:] == '-':
1297 parseStr = '-%s' % parseStr
1298 chunk1 = chunk1[:-1]
1299
1300 s = '%s %s' % (chunk1, chunk2)
1301 flag = True
1302 else:
1303 parseStr = s
1304
1305 if parseStr == '':
1306
1307 m = self.ptc.CRE_QUNITS.search(s)
1308 if m is not None:
1309 self.qunitsFlag = True
1310
1311 if (m.group('qty') != s):
1312
1313 parseStr = m.group('qty')
1314 chunk1 = s[:m.start('qty')].strip()
1315 chunk2 = s[m.end('qty'):].strip()
1316
1317 if chunk1[-1:] == '-':
1318 parseStr = '-%s' % parseStr
1319 chunk1 = chunk1[:-1]
1320
1321 s = '%s %s' % (chunk1, chunk2)
1322 flag = True
1323 else:
1324 parseStr = s
1325
1326 if parseStr == '':
1327
1328 m = self.ptc.CRE_WEEKDAY.search(s)
1329 if m is not None:
1330 self.weekdyFlag = True
1331 self.dateFlag = 1
1332 if (m.group('weekday') != s):
1333
1334 parseStr = m.group('weekday')
1335 chunk1 = s[:m.start('weekday')]
1336 chunk2 = s[m.end('weekday'):]
1337 s = '%s %s' % (chunk1, chunk2)
1338 flag = True
1339 else:
1340 parseStr = s
1341
1342 if parseStr == '':
1343
1344 m = self.ptc.CRE_TIME.search(s)
1345 if m is not None:
1346 self.timeStrFlag = True
1347 self.timeFlag = 2
1348 if (m.group('time') != s):
1349
1350 parseStr = m.group('time')
1351 chunk1 = s[:m.start('time')]
1352 chunk2 = s[m.end('time'):]
1353 s = '%s %s' % (chunk1, chunk2)
1354 flag = True
1355 else:
1356 parseStr = s
1357
1358 if parseStr == '':
1359
1360 m = self.ptc.CRE_TIMEHMS2.search(s)
1361 if m is not None:
1362 self.meridianFlag = True
1363 self.timeFlag = 2
1364 if m.group('minutes') is not None:
1365 if m.group('seconds') is not None:
1366 parseStr = '%s:%s:%s %s' % (m.group('hours'),
1367 m.group('minutes'),
1368 m.group('seconds'),
1369 m.group('meridian'))
1370 else:
1371 parseStr = '%s:%s %s' % (m.group('hours'),
1372 m.group('minutes'),
1373 m.group('meridian'))
1374 else:
1375 parseStr = '%s %s' % (m.group('hours'),
1376 m.group('meridian'))
1377
1378 chunk1 = s[:m.start('hours')]
1379 chunk2 = s[m.end('meridian'):]
1380
1381 s = '%s %s' % (chunk1, chunk2)
1382 flag = True
1383
1384 if parseStr == '':
1385
1386 m = self.ptc.CRE_TIMEHMS.search(s)
1387 if m is not None:
1388 self.timeStdFlag = True
1389 self.timeFlag = 2
1390 if m.group('seconds') is not None:
1391 parseStr = '%s:%s:%s' % (m.group('hours'),
1392 m.group('minutes'),
1393 m.group('seconds'))
1394 chunk1 = s[:m.start('hours')]
1395 chunk2 = s[m.end('seconds'):]
1396 else:
1397 parseStr = '%s:%s' % (m.group('hours'),
1398 m.group('minutes'))
1399 chunk1 = s[:m.start('hours')]
1400 chunk2 = s[m.end('minutes'):]
1401
1402 s = '%s %s' % (chunk1, chunk2)
1403 flag = True
1404
1405
1406
1407 if not flag:
1408 s = ''
1409
1410 if _debug:
1411 print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
1412 print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1413 (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
1414 print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
1415 (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
1416
1417
1418 if parseStr != '':
1419 if self.modifierFlag == True:
1420 t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
1421
1422
1423
1424
1425 if (t != '') and (t != None):
1426 tempDateFlag = self.dateFlag
1427 tempTimeFlag = self.timeFlag
1428 (totalTime2, flag) = self.parse(t, totalTime)
1429
1430 if flag == 0 and totalTime is not None:
1431 self.timeFlag = tempTimeFlag
1432 self.dateFlag = tempDateFlag
1433
1434 return (totalTime, self.dateFlag + self.timeFlag)
1435 else:
1436 return (totalTime2, self.dateFlag + self.timeFlag)
1437
1438 elif self.modifier2Flag == True:
1439 totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
1440
1441 if invalidFlag == True:
1442 self.dateFlag = 0
1443 self.timeFlag = 0
1444
1445 else:
1446 totalTime = self._evalString(parseStr, totalTime)
1447 parseStr = ''
1448
1449
1450 if totalTime is None or totalTime == sourceTime:
1451 totalTime = time.localtime()
1452 self.dateFlag = 0
1453 self.timeFlag = 0
1454
1455 return (totalTime, self.dateFlag + self.timeFlag)
1456
1457
1458 - def inc(self, source, month=None, year=None):
1459 """
1460 Takes the given C{source} date, or current date if none is
1461 passed, and increments it according to the values passed in
1462 by month and/or year.
1463
1464 This routine is needed because Python's C{timedelta()} function
1465 does not allow for month or year increments.
1466
1467 @type source: struct_time
1468 @param source: C{struct_time} value to increment
1469 @type month: integer
1470 @param month: optional number of months to increment
1471 @type year: integer
1472 @param year: optional number of years to increment
1473
1474 @rtype: datetime
1475 @return: C{source} incremented by the number of months and/or years
1476 """
1477 yr = source.year
1478 mth = source.month
1479 dy = source.day
1480
1481 if year:
1482 try:
1483 yi = int(year)
1484 except ValueError:
1485 yi = 0
1486
1487 yr += yi
1488
1489 if month:
1490 try:
1491 mi = int(month)
1492 except ValueError:
1493 mi = 0
1494
1495 m = abs(mi)
1496 y = m / 12
1497 m = m % 12
1498
1499 if mi < 0:
1500 mth = mth - m
1501 if mth < 1:
1502 y -= 1
1503 mth += 12
1504 else:
1505 mth = mth + m
1506 if mth > 12:
1507 y += 1
1508 mth -= 12
1509
1510 yr += y
1511
1512
1513
1514 if dy > self.ptc.DaysInMonthList[mth - 1]:
1515 dy = self.ptc.DaysInMonthList[mth - 1]
1516
1517 d = source.replace(year=yr, month=mth, day=dy)
1518
1519 return source + (d - source)
1520