96
100
# Copied from feedparser.py
97
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
101
# Universal Feedparser
102
# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
98
103
# Modified to return a tuple instead of mktime
100
105
# Original comment:
101
# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
102
# Drake and licensed under the Python license. Removed all range checking
103
# for month, day, hour, minute, and second, since mktime will normalize
106
# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
107
# Drake and licensed under the Python license. Removed all range checking
108
# for month, day, hour, minute, and second, since mktime will normalize
105
110
def _parse_date_w3dtf(dateString):
106
111
# the __extract_date and __extract_time methods were
107
112
# copied-out so they could be used by my code --bear
191
199
self.ptc = constants
193
self.CRE_SPECIAL = re.compile(self.ptc.RE_SPECIAL, re.IGNORECASE)
194
self.CRE_UNITS = re.compile(self.ptc.RE_UNITS, re.IGNORECASE)
195
self.CRE_QUNITS = re.compile(self.ptc.RE_QUNITS, re.IGNORECASE)
196
self.CRE_MODIFIER = re.compile(self.ptc.RE_MODIFIER, re.IGNORECASE)
197
self.CRE_MODIFIER2 = re.compile(self.ptc.RE_MODIFIER2, re.IGNORECASE)
198
self.CRE_TIMEHMS = re.compile(self.ptc.RE_TIMEHMS, re.IGNORECASE)
199
self.CRE_TIMEHMS2 = re.compile(self.ptc.RE_TIMEHMS2, re.IGNORECASE)
200
self.CRE_DATE = re.compile(self.ptc.RE_DATE, re.IGNORECASE)
201
self.CRE_DATE2 = re.compile(self.ptc.RE_DATE2, re.IGNORECASE)
202
self.CRE_DATE3 = re.compile(self.ptc.RE_DATE3, re.IGNORECASE)
203
self.CRE_MONTH = re.compile(self.ptc.RE_MONTH, re.IGNORECASE)
204
self.CRE_WEEKDAY = re.compile(self.ptc.RE_WEEKDAY, re.IGNORECASE)
205
self.CRE_DAY = re.compile(self.ptc.RE_DAY, re.IGNORECASE)
206
self.CRE_TIME = re.compile(self.ptc.RE_TIME, re.IGNORECASE)
207
self.CRE_REMAINING = re.compile(self.ptc.RE_REMAINING, re.IGNORECASE)
209
#regex for date/time ranges
210
self.CRE_RTIMEHMS = re.compile(self.ptc.RE_RTIMEHMS, re.IGNORECASE)
211
self.CRE_RTIMEHMS2 = re.compile(self.ptc.RE_RTIMEHMS2, re.IGNORECASE)
212
self.CRE_RDATE = re.compile(self.ptc.RE_RDATE, re.IGNORECASE)
213
self.CRE_RDATE3 = re.compile(self.ptc.RE_RDATE3, re.IGNORECASE)
215
self.CRE_TIMERNG1 = re.compile(self.ptc.TIMERNG1, re.IGNORECASE)
216
self.CRE_TIMERNG2 = re.compile(self.ptc.TIMERNG2, re.IGNORECASE)
217
self.CRE_TIMERNG3 = re.compile(self.ptc.TIMERNG3, re.IGNORECASE)
218
self.CRE_DATERNG1 = re.compile(self.ptc.DATERNG1, re.IGNORECASE)
219
self.CRE_DATERNG2 = re.compile(self.ptc.DATERNG2, re.IGNORECASE)
220
self.CRE_DATERNG3 = re.compile(self.ptc.DATERNG3, re.IGNORECASE)
222
self.invalidFlag = False # Is set if the datetime string entered cannot be parsed at all
223
201
self.weekdyFlag = False # monday/tuesday/...
224
202
self.dateStdFlag = False # 07/21/06
225
203
self.dateStrFlag = False # July 21st, 2006
226
self.timeFlag = False # 5:50
204
self.timeStdFlag = False # 5:50
227
205
self.meridianFlag = False # am/pm
228
206
self.dayStrFlag = False # tomorrow/yesterday/today/..
229
207
self.timeStrFlag = False # lunch/noon/breakfast/...
298
279
# plurals are handled by regex's (could be a bug tho)
300
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
281
(yr, mth, dy, hr, mn, sec, _, _, _) = source
302
283
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
305
286
if units.startswith('y'):
306
target = self.inc(start, year=qty)
287
target = self.inc(start, year=qty)
307
289
elif units.endswith('th') or units.endswith('ths'):
308
target = self.inc(start, month=qty)
290
target = self.inc(start, month=qty)
310
293
if units.startswith('d'):
311
target = start + datetime.timedelta(days=qty)
294
target = start + datetime.timedelta(days=qty)
312
296
elif units.startswith('h'):
313
target = start + datetime.timedelta(hours=qty)
297
target = start + datetime.timedelta(hours=qty)
314
299
elif units.startswith('m'):
315
target = start + datetime.timedelta(minutes=qty)
300
target = start + datetime.timedelta(minutes=qty)
316
302
elif units.startswith('s'):
317
target = start + datetime.timedelta(seconds=qty)
303
target = start + datetime.timedelta(seconds=qty)
318
305
elif units.startswith('w'):
319
target = start + datetime.timedelta(weeks=qty)
322
self.invalidFlag = False
306
target = start + datetime.timedelta(weeks=qty)
324
309
return target.timetuple()
327
312
def parseDate(self, dateString):
329
Parses strings like 05/28/200 or 04.21
314
Parse short-form date strings::
316
'05/28/2006' or '04.21'
331
318
@type dateString: string
332
@param dateString: text to convert to a datetime
319
@param dateString: text to convert to a C{datetime}
335
@return: calculated datetime value of dateString
322
@return: calculated C{struct_time} value of dateString
337
324
yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
326
# values pulled from regex's will be stored here and later
327
# assigned to mth, dy, yr based on information from the locale
328
# -1 is used as the marker value because we want zero values
329
# to be passed thru so they can be flagged as errors later
340
m = self.CRE_DATE2.search(s)
335
m = self.ptc.CRE_DATE2.search(s)
341
336
if m is not None:
342
337
index = m.start()
344
339
s = s[index + 1:]
346
m = self.CRE_DATE2.search(s)
341
m = self.ptc.CRE_DATE2.search(s)
347
342
if m is not None:
348
343
index = m.start()
350
yr = int(s[index + 1:])
351
# TODO should this have a birthday epoch constraint?
355
dy = int(string.strip(s))
357
if (mth > 0 and mth <= 12) and (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
345
v3 = int(s[index + 1:])
350
d = { 'm': mth, 'd': dy, 'y': yr }
352
for i in range(0, 3):
354
c = self.ptc.dp_order[i]
358
# if the year is not specified and the date has already
359
# passed, increment the year
360
if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
368
# birthday epoch constraint
369
if yr < self.ptc.BirthdayEpoch:
375
print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr)
377
if (mth > 0 and mth <= 12) and \
378
(dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)):
358
379
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
360
self.invalidFlag = True
361
sourceTime = time.localtime() #return current time if date string is invalid
383
sourceTime = time.localtime() # return current time if date
363
386
return sourceTime
366
389
def parseDateText(self, dateString):
368
Parses strings like "May 31st, 2006" or "Jan 1st" or "July 2006"
391
Parse long-form date strings::
370
397
@type dateString: string
371
398
@param dateString: text to convert to a datetime
374
@return: calculated datetime value of dateString
401
@return: calculated C{struct_time} value of dateString
376
403
yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
391
418
if m.group('year') != None:
392
419
yr = int(m.group('year'))
421
# birthday epoch constraint
422
if yr < self.ptc.BirthdayEpoch:
393
427
elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
394
428
# if that day and month have already passed in this year,
395
429
# then increment the year by 1
398
if dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
399
sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
432
if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr):
433
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
401
# Return current time if date string is invalid
402
self.invalidFlag = True
403
sourceTime = time.localtime()
435
# Return current time if date string is invalid
438
sourceTime = time.localtime()
405
440
return sourceTime
408
443
def evalRanges(self, datetimeString, sourceTime=None):
410
Evaluates the strings with time or date ranges
445
Evaluate the C{datetimeString} text and determine if
446
it represents a date or time range.
412
448
@type datetimeString: string
413
449
@param datetimeString: datetime text to evaluate
414
@type sourceTime: datetime
415
@param sourceTime: datetime value to use as the base
450
@type sourceTime: struct_time
451
@param sourceTime: C{struct_time} value to use as the base
418
@return: tuple of the start datetime, end datetime and the invalid flag
454
@return: tuple of: start datetime, end datetime and the invalid flag
426
s = string.strip(datetimeString.lower())
428
m = self.CRE_TIMERNG1.search(s)
462
s = datetimeString.strip().lower()
464
if self.ptc.rangeSep in s:
465
s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep)
466
s = s.replace(' ', ' ')
468
m = self.ptc.CRE_TIMERNG1.search(s)
429
469
if m is not None:
432
m = self.CRE_TIMERNG2.search(s)
472
m = self.ptc.CRE_TIMERNG2.search(s)
433
473
if m is not None:
436
m = self.CRE_TIMERNG3.search(s)
476
m = self.ptc.CRE_TIMERNG4.search(s)
437
477
if m is not None:
440
m = self.CRE_DATERNG1.search(s)
480
m = self.ptc.CRE_TIMERNG3.search(s)
441
481
if m is not None:
444
m = self.CRE_DATERNG2.search(s)
484
m = self.ptc.CRE_DATERNG1.search(s)
445
485
if m is not None:
448
m = self.CRE_DATERNG3.search(s)
488
m = self.ptc.CRE_DATERNG2.search(s)
449
489
if m is not None:
492
m = self.ptc.CRE_DATERNG3.search(s)
453
497
print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
464
508
sourceTime, flag = self.parse(s, sourceTime)
467
511
sourceTime = None
471
515
if rangeFlag == 1:
472
# FIXME hardcoded seperator
473
m = re.search('-', parseStr)
516
m = re.search(self.ptc.rangeSep, parseStr)
474
517
startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
475
518
endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
477
if eflag is False and sflag is False:
478
return (startTime, endTime, False)
520
if (eflag != 0) and (sflag != 0):
521
return (startTime, endTime, 2)
480
523
elif rangeFlag == 2:
481
# FIXME hardcoded seperator
482
m = re.search('-', parseStr)
483
startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
524
m = re.search(self.ptc.rangeSep, parseStr)
525
startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
484
526
endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
486
if eflag is False and sflag is False:
487
return (startTime, endTime, False)
490
# FIXME hardcoded seperator
491
m = re.search('-', parseStr)
528
if (eflag != 0) and (sflag != 0):
529
return (startTime, endTime, 2)
531
elif rangeFlag == 3 or rangeFlag == 7:
532
m = re.search(self.ptc.rangeSep, parseStr)
493
533
# capturing the meridian from the end time
494
# FIXME hardcoded meridian
495
534
if self.ptc.usesMeridian:
496
ampm = re.search('a', parseStr)
535
ampm = re.search(self.ptc.am[0], parseStr)
498
537
# appending the meridian to the start time
499
538
if ampm is not None:
506
545
endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
508
if eflag is False and sflag is False:
509
return (startTime, endTime, False)
547
if (eflag != 0) and (sflag != 0):
548
return (startTime, endTime, 2)
511
550
elif rangeFlag == 4:
512
# FIXME hardcoded seperator
513
m = re.search('-', parseStr)
551
m = re.search(self.ptc.rangeSep, parseStr)
514
552
startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
515
553
endDate, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime)
517
if eflag is False and sflag is False:
518
return (startDate, endDate, False)
555
if (eflag != 0) and (sflag != 0):
556
return (startDate, endDate, 1)
520
558
elif rangeFlag == 5:
521
# FIXME hardcoded seperator
522
m = re.search('-', parseStr)
559
m = re.search(self.ptc.rangeSep, parseStr)
523
560
endDate = parseStr[(m.start() + 1):]
525
562
# capturing the year from the end date
526
date = self.CRE_DATE3.search(endDate)
563
date = self.ptc.CRE_DATE3.search(endDate)
527
564
endYear = date.group('year')
529
566
# appending the year to the start date if the start date
530
567
# does not have year information and the end date does.
531
568
# eg : "Aug 21 - Sep 4, 2007"
532
569
if endYear is not None:
533
startDate = parseStr[:m.start()]
534
date = self.CRE_DATE3.search(startDate)
570
startDate = (parseStr[:m.start()]).strip()
571
date = self.ptc.CRE_DATE3.search(startDate)
535
572
startYear = date.group('year')
537
574
if startYear is None:
575
startDate = startDate + ', ' + endYear
540
577
startDate = parseStr[:m.start()]
542
579
startDate, sflag = self.parse(startDate, sourceTime)
543
580
endDate, eflag = self.parse(endDate, sourceTime)
545
if eflag is False and sflag is False:
546
return (startDate, endDate, False)
582
if (eflag != 0) and (sflag != 0):
583
return (startDate, endDate, 1)
548
585
elif rangeFlag == 6:
549
# FIXME hardcoded seperator
550
m = re.search('-', parseStr)
586
m = re.search(self.ptc.rangeSep, parseStr)
552
588
startDate = parseStr[:m.start()]
554
590
# capturing the month from the start date
555
mth = self.CRE_DATE3.search(startDate)
591
mth = self.ptc.CRE_DATE3.search(startDate)
556
592
mth = mth.group('mthname')
558
594
# appending the month name to the end date
561
597
startDate, sflag = self.parse(startDate, sourceTime)
562
598
endDate, eflag = self.parse(endDate, sourceTime)
564
if eflag is False and sflag is False:
565
return (startDate, endDate, False)
600
if (eflag != 0) and (sflag != 0):
601
return (startDate, endDate, 1)
567
603
# if range is not found
568
604
sourceTime = time.localtime()
570
return (sourceTime, sourceTime, True)
606
return (sourceTime, sourceTime, 0)
609
def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle):
611
Based on the C{style} and C{currentDayStyle} determine what
612
day-of-week value is to be returned.
615
@param wd: day-of-week value for the current day
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)
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}
626
@return: calculated day-of-week
629
# modifier is indicating future week eg: "next".
630
# DOW is calculated as DOW of next week
634
# modifier is indicating past week eg: "last","previous"
635
# DOW is calculated as DOW of previous week
639
# modifier is indiacting current week eg: "this"
640
# DOW is calculated as DOW of this week
644
# no modifier is present.
645
# i.e. string to be parsed is just DOW
647
# next occurance of the DOW is calculated
648
if currentDayStyle == True:
660
# last occurance of the DOW is calculated
661
if currentDayStyle == True:
672
# occurance of the DOW in the current week is calculated
676
print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
573
681
def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
575
Evaluate the modifier string and following text (passed in
576
as chunk1 and chunk2) and if they match any known modifiers
577
calculate the delta and apply it to sourceTime
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}.
579
687
@type modifier: string
580
688
@param modifier: modifier text to apply to sourceTime
610
719
if unit == 'month' or \
613
dy = self.ptc.DaysInMonthList[mth - 1]
723
dy = self.ptc.daysInMonth(mth, yr)
614
724
sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
615
725
elif offset == 2:
616
# if day is the last day of the month, calculate the last day of the next month
617
if dy == self.ptc.DaysInMonthList[mth - 1]:
618
dy = self.ptc.DaysInMonthList[mth]
726
# if day is the last day of the month, calculate the last day
728
if dy == self.ptc.daysInMonth(mth, yr):
729
dy = self.ptc.daysInMonth(mth + 1, yr)
620
731
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
621
732
target = self.inc(start, month=1)
642
754
return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
646
759
if unit == 'day' or \
647
760
unit == 'dy' or \
650
sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
763
sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
651
765
elif offset == 2:
652
766
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
653
767
target = start + datetime.timedelta(days=1)
681
797
sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
685
802
if flag == False:
686
m = self.CRE_WEEKDAY.match(unit)
803
m = self.ptc.CRE_WEEKDAY.match(unit)
687
804
if m is not None:
689
wkdy = self.ptc.WeekdayOffsets[wkdy]
808
if modifier == 'eod':
809
# Calculate the upcoming weekday
810
self.modifierFlag = False
811
(sourceTime, _) = self.parse(wkdy, sourceTime)
812
sources = self.ptc.buildSources(sourceTime)
815
if modifier in sources:
816
sourceTime = sources[modifier]
819
wkdy = self.ptc.WeekdayOffsets[wkdy]
820
diff = self._CalculateDOWDelta(wd, wkdy, offset,
821
self.ptc.DOWParseStyle,
822
self.ptc.CurrentDOWParseStyle)
693
823
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
694
824
target = start + datetime.timedelta(days=diff)
695
825
sourceTime = target.timetuple()
698
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
699
target = start + datetime.timedelta(days=diff + 7 * offset)
700
sourceTime = target.timetuple()
705
m = self.CRE_TIME.match(unit)
831
m = self.ptc.CRE_TIME.match(unit)
706
832
if m is not None:
707
(yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
833
self.modifierFlag = False
834
(yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
708
836
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
709
837
target = start + datetime.timedelta(days=offset)
710
838
sourceTime = target.timetuple()
713
841
self.modifierFlag = False
715
# if the word after next is a number, the string is likely
716
# to be something like "next 4 hrs" for which we have to
717
# combine the units with the rest of the string
843
# check if the remaining text is parsable and if so,
844
# use it as the base time for the modifier source time
845
t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
850
sources = self.ptc.buildSources(sourceTime)
852
if modifier in sources:
853
sourceTime = sources[modifier]
857
# if the word after next is a number, the string is more than likely
858
# to be "next 4 hrs" which we will have to combine the units with the
720
862
# if offset is negative, the unit has to be made negative
725
867
self.modifierFlag = False
727
return '%s %s' % (chunk1, chunk2), sourceTime
869
#return '%s %s' % (chunk1, chunk2), sourceTime
870
return '%s' % chunk2, sourceTime
730
872
def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
732
Evaluate the modifier string and following text (passed in
733
as chunk1 and chunk2) and if they match any known modifiers
734
calculate the delta and apply it to sourceTime
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}.
736
878
@type modifier: string
737
@param modifier: modifier text to apply to sourceTime
879
@param modifier: modifier text to apply to C{sourceTime}
738
880
@type chunk1: string
739
881
@param chunk1: first text chunk that followed modifier (if any)
740
882
@type chunk2: string
741
883
@param chunk2: second text chunk that followed modifier (if any)
742
@type sourceTime: datetime
743
@param sourceTime: datetime value to use as the base
884
@type sourceTime: struct_time
885
@param sourceTime: C{struct_time} value to use as the base
746
@return: tuple of any remaining text and the modified sourceTime
888
@return: tuple of: remaining text and the modified sourceTime
748
890
offset = self.ptc.Modifiers[modifier]
751
if sourceTime is not None:
752
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
754
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
756
893
self.modifier2Flag = False
758
# If the string after the negative modifier starts with
759
# digits, then it is likely that the string is similar to
760
# " before 3 days" or 'evening prior to 3 days'.
761
# In this case, the total time is calculated by subtracting
762
# '3 days' from the current date.
763
# So, we have to identify the quantity and negate it before
764
# parsing the string.
765
# This is not required for strings not starting with digits
766
# since the string is enough to calculate the sourceTime
768
m = re.match(digit, string.strip(chunk2))
770
qty = int(m.group()) * -1
771
chunk2 = chunk2[m.end():]
772
chunk2 = '%d%s' % (qty, chunk2)
895
# If the string after the negative modifier starts with digits,
896
# then it is likely that the string is similar to ' before 3 days'
897
# or 'evening prior to 3 days'.
898
# In this case, the total time is calculated by subtracting '3 days'
899
# from the current date.
900
# So, we have to identify the quantity and negate it before parsing
902
# This is not required for strings not starting with digits since the
903
# string is enough to calculate the sourceTime
906
m = re.match(digit, chunk2.strip())
908
qty = int(m.group()) * -1
909
chunk2 = chunk2[m.end():]
910
chunk2 = '%d%s' % (qty, chunk2)
774
sourceTime, flag = self.parse(chunk2, sourceTime)
912
sourceTime, flag1 = self.parse(chunk2, sourceTime)
778
m = re.match(digit, string.strip(chunk1))
923
m = re.search(digit, chunk1.strip())
779
924
if m is not None:
780
925
qty = int(m.group()) * -1
781
926
chunk1 = chunk1[m.end():]
782
927
chunk1 = '%d%s' % (qty, chunk1)
784
sourceTime, flag = self.parse(chunk1, sourceTime)
786
return '', sourceTime
929
tempDateFlag = self.dateFlag
930
tempTimeFlag = self.timeFlag
931
sourceTime2, flag2 = self.parse(chunk1, sourceTime)
933
return sourceTime, (flag1 and flag2)
935
# if chunk1 is not a datetime and chunk2 is then do not use datetime
936
# value returned by parsing chunk1
937
if not (flag1 == False and flag2 == 0):
938
sourceTime = sourceTime2
940
self.timeFlag = tempTimeFlag
941
self.dateFlag = tempDateFlag
943
return sourceTime, (flag1 and flag2)
789
946
def _evalString(self, datetimeString, sourceTime=None):
799
956
@type datetimeString: string
800
@param datetimeString: text to try and parse as more "traditional" date/time text
801
@type sourceTime: datetime
802
@param sourceTime: datetime value to use as the base
957
@param datetimeString: text to try and parse as more "traditional"
959
@type sourceTime: struct_time
960
@param sourceTime: C{struct_time} value to use as the base
805
@return: calculated datetime value or current datetime if not parsed
963
@return: calculated C{struct_time} value or current C{struct_time}
807
s = string.strip(datetimeString)
966
s = datetimeString.strip()
808
967
now = time.localtime()
810
# Given string date is a RFC822 date
969
# Given string date is a RFC822 date
811
970
if sourceTime is None:
812
971
sourceTime = _parse_date_rfc822(s)
814
# Given string date is a W3CDTF date
973
if sourceTime is not None:
974
(yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
977
if (hr != 0) and (mn != 0) and (sec != 0):
980
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
982
# Given string date is a W3CDTF date
815
983
if sourceTime is None:
816
984
sourceTime = _parse_date_w3dtf(s)
986
if sourceTime is not None:
818
990
if sourceTime is None:
821
# Given string is in the format HH:MM(:SS)(am/pm)
993
# Given string is in the format HH:MM(:SS)(am/pm)
822
994
if self.meridianFlag:
823
995
if sourceTime is None:
824
996
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
826
998
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
828
m = self.CRE_TIMEHMS2.search(s)
1000
m = self.ptc.CRE_TIMEHMS2.search(s)
829
1001
if m is not None:
830
1002
dt = s[:m.start('meridian')].strip()
831
1003
if len(dt) <= 2:
853
1025
if hr > 24 or mn > 59 or sec > 59:
855
self.invalidFlag = True
857
1030
self.meridianFlag = False
859
1032
# Given string is in the format HH:MM(:SS)
1033
if self.timeStdFlag:
861
1034
if sourceTime is None:
862
1035
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
864
1037
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
866
m = self.CRE_TIMEHMS.search(s)
1039
m = self.ptc.CRE_TIMEHMS.search(s)
867
1040
if m is not None:
868
1041
hr, mn, sec = _extract_time(m)
872
1045
if hr > 24 or mn > 59 or sec > 59:
875
self.invalidFlag = True
877
1051
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
879
self.timeFlag = False
1053
self.timeStdFlag = False
881
# Given string is in the format 07/21/2006
1055
# Given string is in the format 07/21/2006
882
1056
if self.dateStdFlag:
883
1057
sourceTime = self.parseDate(s)
884
1058
self.dateStdFlag = False
886
# Given string is in the format "May 23rd, 2005"
1060
# Given string is in the format "May 23rd, 2005"
887
1061
if self.dateStrFlag:
888
1062
sourceTime = self.parseDateText(s)
889
1063
self.dateStrFlag = False
891
# Given string is a weekday
1065
# Given string is a weekday
892
1066
if self.weekdyFlag:
893
1067
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
895
1069
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
896
wkDy = self.ptc.WeekdayOffsets[s]
1070
wkdy = self.ptc.WeekdayOffsets[s]
900
target = start + datetime.timedelta(days=qty)
1073
qty = self._CalculateDOWDelta(wd, wkdy, 2,
1074
self.ptc.DOWParseStyle,
1075
self.ptc.CurrentDOWParseStyle)
903
qty = 6 - wd + wkDy + 1
904
target = start + datetime.timedelta(days=qty)
1077
qty = self._CalculateDOWDelta(wd, wkdy, 2,
1078
self.ptc.DOWParseStyle,
1079
self.ptc.CurrentDOWParseStyle)
1081
target = start + datetime.timedelta(days=qty)
907
1084
sourceTime = target.timetuple()
908
1085
self.weekdyFlag = False
910
# Given string is a natural language time string like lunch, midnight, etc
1087
# Given string is a natural language time string like
1088
# lunch, midnight, etc
911
1089
if self.timeStrFlag:
912
1090
if s in self.ptc.re_values['now']:
913
1091
sourceTime = now
915
sources = self.ptc.buildSources(now)
1093
sources = self.ptc.buildSources(sourceTime)
917
1095
if s in sources:
918
1096
sourceTime = sources[s]
921
self.invalidFlag = True
923
1102
self.timeStrFlag = False
925
# Given string is a natural language date string like today, tomorrow..
1104
# Given string is a natural language date string like today, tomorrow..
926
1105
if self.dayStrFlag:
927
1106
if sourceTime is None:
928
1107
sourceTime = now
973
1152
# Given string does not match anything
974
1153
if sourceTime is None:
976
self.invalidFlag = True
978
1158
return sourceTime
981
1161
def parse(self, datetimeString, sourceTime=None):
983
Splits the L{datetimeString} into tokens, finds the regex patters
984
that match and then calculates a datetime value from the chunks
986
if L{sourceTime} is given then the datetime value will be calcualted
987
from that datetime, otherwise from the current datetime.
1163
Splits the given C{datetimeString} into tokens, finds the regex
1164
patterns that match and then calculates a C{struct_time} value from
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.
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::
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}
989
1179
@type datetimeString: string
990
@param datetimeString: datetime text to evaluate
991
@type sourceTime: datetime
992
@param sourceTime: datetime value to use as the base
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
995
@return: tuple of any remaining text and the modified sourceTime
1185
@return: tuple of: modified C{sourceTime} and the result flag
997
s = string.strip(datetimeString.lower())
1189
if isinstance(sourceTime, datetime.datetime):
1191
print 'coercing datetime to timetuple'
1192
sourceTime = sourceTime.timetuple()
1194
if not isinstance(sourceTime, time.struct_time) and \
1195
not isinstance(sourceTime, tuple):
1196
raise Exception('sourceTime is not a struct_time')
1198
s = datetimeString.strip().lower()
1000
1200
totalTime = sourceTime
1002
self.invalidFlag = False
1005
1203
if sourceTime is not None:
1006
return (sourceTime, False)
1204
return (sourceTime, self.dateFlag + self.timeFlag)
1008
return (time.localtime(), True)
1206
return (time.localtime(), 0)
1010
1211
while len(s) > 0:
1018
1219
if parseStr == '':
1019
1220
# Modifier like next\prev..
1020
m = self.CRE_MODIFIER.search(s)
1221
m = self.ptc.CRE_MODIFIER.search(s)
1021
1222
if m is not None:
1022
1223
self.modifierFlag = True
1023
1224
if (m.group('modifier') != s):
1024
1225
# capture remaining string
1025
1226
parseStr = m.group('modifier')
1026
chunk1 = string.strip(s[:m.start('modifier')])
1027
chunk2 = string.strip(s[m.end('modifier'):])
1227
chunk1 = s[:m.start('modifier')].strip()
1228
chunk2 = s[m.end('modifier'):].strip()
1032
1233
if parseStr == '':
1033
1234
# Modifier like from\after\prior..
1034
m = self.CRE_MODIFIER2.search(s)
1235
m = self.ptc.CRE_MODIFIER2.search(s)
1035
1236
if m is not None:
1036
1237
self.modifier2Flag = True
1037
1238
if (m.group('modifier') != s):
1038
1239
# capture remaining string
1039
1240
parseStr = m.group('modifier')
1040
chunk1 = string.strip(s[:m.start('modifier')])
1041
chunk2 = string.strip(s[m.end('modifier'):])
1241
chunk1 = s[:m.start('modifier')].strip()
1242
chunk2 = s[m.end('modifier'):].strip()
1046
1247
if parseStr == '':
1249
for match in self.ptc.CRE_DATE3.finditer(s):
1250
# to prevent "HH:MM(:SS) time strings" expressions from triggering
1251
# this regex, we checks if the month field exists in the searched
1252
# expression, if it doesn't exist, the date field is not valid
1253
if match.group('mthname'):
1254
m = self.ptc.CRE_DATE3.search(s, match.start())
1047
1258
# String date format
1048
m = self.CRE_DATE3.search(s)
1050
1260
self.dateStrFlag = True
1051
1262
if (m.group('date') != s):
1052
1263
# capture remaining string
1053
1264
parseStr = m.group('date')
1131
1345
if parseStr == '':
1133
m = self.CRE_WEEKDAY.search(s)
1347
m = self.ptc.CRE_WEEKDAY.search(s)
1134
1348
if m is not None:
1135
self.weekdyFlag = True
1136
if (m.group('weekday') != s):
1137
# capture remaining string
1138
parseStr = m.group()
1139
chunk1 = s[:m.start('weekday')]
1140
chunk2 = s[m.end('weekday'):]
1141
s = '%s %s' % (chunk1, chunk2)
1349
gv = m.group('weekday')
1350
if s not in self.ptc.dayOffsets:
1351
self.weekdyFlag = True
1354
# capture remaining string
1356
chunk1 = s[:m.start('weekday')]
1357
chunk2 = s[m.end('weekday'):]
1358
s = '%s %s' % (chunk1, chunk2)
1146
1363
if parseStr == '':
1147
1364
# Natural language time strings
1148
m = self.CRE_TIME.search(s)
1365
m = self.ptc.CRE_TIME.search(s)
1149
1366
if m is not None:
1150
1367
self.timeStrFlag = True
1151
1369
if (m.group('time') != s):
1152
1370
# capture remaining string
1153
1371
parseStr = m.group('time')
1161
1379
if parseStr == '':
1162
1380
# HH:MM(:SS) am/pm time strings
1163
m = self.CRE_TIMEHMS2.search(s)
1381
m = self.ptc.CRE_TIMEHMS2.search(s)
1164
1382
if m is not None:
1165
1383
self.meridianFlag = True
1166
1385
if m.group('minutes') is not None:
1167
1386
if m.group('seconds') is not None:
1168
parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
1387
parseStr = '%s:%s:%s %s' % (m.group('hours'),
1390
m.group('meridian'))
1170
parseStr = '%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('meridian'))
1392
parseStr = '%s:%s %s' % (m.group('hours'),
1394
m.group('meridian'))
1172
parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
1396
parseStr = '%s %s' % (m.group('hours'),
1397
m.group('meridian'))
1174
1399
chunk1 = s[:m.start('hours')]
1175
1400
chunk2 = s[m.end('meridian'):]
1180
1405
if parseStr == '':
1181
1406
# HH:MM(:SS) time strings
1182
m = self.CRE_TIMEHMS.search(s)
1407
m = self.ptc.CRE_TIMEHMS.search(s)
1183
1408
if m is not None:
1184
self.timeFlag = True
1409
self.timeStdFlag = True
1185
1411
if m.group('seconds') is not None:
1186
parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
1412
parseStr = '%s:%s:%s' % (m.group('hours'),
1187
1415
chunk1 = s[:m.start('hours')]
1188
1416
chunk2 = s[m.end('seconds'):]
1190
parseStr = '%s:%s' % (m.group('hours'), m.group('minutes'))
1418
parseStr = '%s:%s' % (m.group('hours'),
1191
1420
chunk1 = s[:m.start('hours')]
1192
1421
chunk2 = s[m.end('minutes'):]
1194
1423
s = '%s %s' % (chunk1, chunk2)
1197
# if string does not match any regex, empty string to come out of the while loop
1426
# if string does not match any regex, empty string to
1427
# come out of the while loop
1202
1432
print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
1203
print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1204
(self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
1433
print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1434
(self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
1205
1435
print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
1206
1436
(self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
1209
1439
if parseStr != '':
1210
1440
if self.modifierFlag == True:
1211
1441
t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
1213
return self.parse(t, totalTime)
1442
# t is the unparsed part of the chunks.
1443
# If it is not date/time, return current
1444
# totalTime as it is; else return the output
1446
if (t != '') and (t != None):
1447
tempDateFlag = self.dateFlag
1448
tempTimeFlag = self.timeFlag
1449
(totalTime2, flag) = self.parse(t, totalTime)
1451
if flag == 0 and totalTime is not None:
1452
self.timeFlag = tempTimeFlag
1453
self.dateFlag = tempDateFlag
1455
return (totalTime, self.dateFlag + self.timeFlag)
1457
return (totalTime2, self.dateFlag + self.timeFlag)
1215
1459
elif self.modifier2Flag == True:
1216
s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
1460
totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
1462
if invalidFlag == True:
1218
1467
totalTime = self._evalString(parseStr, totalTime)
1221
1470
# String is not parsed at all
1222
1471
if totalTime is None or totalTime == sourceTime:
1223
totalTime = time.localtime()
1224
self.invalidFlag = True
1472
totalTime = time.localtime()
1226
return (totalTime, self.invalidFlag)
1476
return (totalTime, self.dateFlag + self.timeFlag)
1229
1479
def inc(self, source, month=None, year=None):
1231
Takes the given date, or current date if none is passed, and
1232
increments it according to the values passed in by month
1235
This routine is needed because the timedelta() routine does
1236
not allow for month or year increments.
1238
@type source: datetime
1239
@param source: datetime value to increment
1481
Takes the given C{source} date, or current date if none is
1482
passed, and increments it according to the values passed in
1483
by month and/or year.
1485
This routine is needed because Python's C{timedelta()} function
1486
does not allow for month or year increments.
1488
@type source: struct_time
1489
@param source: C{struct_time} value to increment
1240
1490
@type month: integer
1241
1491
@param month: optional number of months to increment
1242
1492
@type year: integer
1243
1493
@param year: optional number of years to increment
1245
1495
@rtype: datetime
1246
@return: L{source} incremented by the number of months and/or years
1496
@return: C{source} incremented by the number of months and/or years
1248
1498
yr = source.year
1249
1499
mth = source.month