4
Parse human-readable date/time text.
7
__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
9
Licensed under the Apache License, Version 2.0 (the "License");
10
you may not use this file except in compliance with the License.
11
You may obtain a copy of the License at
13
http://www.apache.org/licenses/LICENSE-2.0
15
Unless required by applicable law or agreed to in writing, software
16
distributed under the License is distributed on an "AS IS" BASIS,
17
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
See the License for the specific language governing permissions and
19
limitations under the License.
21
__author__ = 'Mike Taylor <http://code-bear.com>'
22
__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
28
import string, re, time
29
import datetime, calendar, rfc822
30
import parsedatetime_consts
33
# Copied from feedparser.py
34
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
35
# Originally a def inside of _parse_date_w3dtf()
37
year = int(m.group('year'))
39
year = 100 * int(time.gmtime()[0] / 100) + int(year)
42
julian = m.group('julian')
45
month = julian / 30 + 1
49
t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
50
jday = time.gmtime(t)[-2]
51
diff = abs(jday - julian)
63
return year, month, day
64
month = m.group('month')
75
return year, month, day
77
# Copied from feedparser.py
78
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
79
# Originally a def inside of _parse_date_w3dtf()
83
hours = m.group('hours')
87
minutes = int(m.group('minutes'))
88
seconds = m.group('seconds')
90
seconds = int(seconds)
93
return hours, minutes, seconds
96
# Copied from feedparser.py
97
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
98
# Modified to return a tuple instead of mktime
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
105
def _parse_date_w3dtf(dateString):
106
# the __extract_date and __extract_time methods were
107
# copied-out so they could be used by my code --bear
108
def __extract_tzd(m):
109
'''Return the Time Zone Designator as an offset in seconds from UTC.'''
117
hours = int(m.group('tzdhours'))
118
minutes = m.group('tzdminutes')
120
minutes = int(minutes)
123
offset = (hours*60 + minutes) * 60
128
__date_re = ('(?P<year>\d\d\d\d)'
130
'(?:(?P<julian>\d\d\d)'
131
'|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
132
__tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
133
__tzd_rx = re.compile(__tzd_re)
134
__time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
135
'(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
137
__datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
138
__datetime_rx = re.compile(__datetime_re)
139
m = __datetime_rx.match(dateString)
140
if (m is None) or (m.group() != dateString): return
141
return _extract_date(m) + _extract_time(m) + (0, 0, 0)
144
# Copied from feedparser.py
145
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
146
# Modified to return a tuple instead of mktime
148
def _parse_date_rfc822(dateString):
149
'''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
150
data = dateString.split()
151
if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
157
data[3:] = [s[:i], s[i+1:]]
160
dateString = " ".join(data)
162
dateString += ' 00:00:00 GMT'
163
return rfc822.parsedate_tz(dateString)
165
# rfc822.py defines several time zones, but we define some extra ones.
166
# 'ET' is equivalent to 'EST', etc.
167
_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
168
rfc822._timezones.update(_additional_timezones)
173
A collection of routines to input, parse and manipulate date and times.
174
The text can either be 'normal' date values or it can be human readable.
177
def __init__(self, constants=None):
179
Default constructor for the Calendar class.
181
@type constants: object
182
@param constants: Instance of the class L{CalendarConstants}
185
@return: Calendar instance
187
# if a constants reference is not included, use default
188
if constants is None:
189
self.ptc = parsedatetime_consts.CalendarConstants()
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
self.weekdyFlag = False # monday/tuesday/...
224
self.dateStdFlag = False # 07/21/06
225
self.dateStrFlag = False # July 21st, 2006
226
self.timeFlag = False # 5:50
227
self.meridianFlag = False # am/pm
228
self.dayStrFlag = False # tomorrow/yesterday/today/..
229
self.timeStrFlag = False # lunch/noon/breakfast/...
230
self.modifierFlag = False # after/before/prev/next/..
231
self.modifier2Flag = False # after/before/prev/next/..
232
self.unitsFlag = False # hrs/weeks/yrs/min/..
233
self.qunitsFlag = False # h/m/t/d..
236
def _convertUnitAsWords(self, unitText):
238
Converts text units into their number value
242
Two hundred twenty five = 225
243
Two thousand and twenty five = 2025
244
Two thousand twenty five = 2025
246
@type unitText: string
247
@param unitText: number string
250
@return: numerical value of unitText
252
# TODO: implement this
256
def _buildTime(self, source, quantity, modifier, units):
258
Take quantity, modifier and unit strings and convert them into values.
259
Then calcuate the time and return the adjusted sourceTime
262
@param source: time to use as the base (or source)
263
@type quantity: string
264
@param quantity: quantity string
265
@type modifier: string
266
@param modifier: how quantity and units modify the source time
268
@param units: unit of the quantity (i.e. hours, days, months, etc)
271
@return: timetuple of the calculated time
274
print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
277
source = time.localtime()
282
quantity = string.strip(quantity)
284
if len(quantity) == 0:
292
if modifier in self.ptc.Modifiers:
293
qty = qty * self.ptc.Modifiers[modifier]
295
if units is None or units == '':
298
# plurals are handled by regex's (could be a bug tho)
300
if units in self.ptc.Units:
301
u = self.ptc.Units[units]
305
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
307
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
310
if units.startswith('y'):
311
target = self.inc(start, year=qty)
312
elif units.endswith('th') or units.endswith('ths'):
313
target = self.inc(start, month=qty)
315
if units.startswith('d'):
316
target = start + datetime.timedelta(days=qty)
317
elif units.startswith('h'):
318
target = start + datetime.timedelta(hours=qty)
319
elif units.startswith('m'):
320
target = start + datetime.timedelta(minutes=qty)
321
elif units.startswith('s'):
322
target = start + datetime.timedelta(seconds=qty)
323
elif units.startswith('w'):
324
target = start + datetime.timedelta(weeks=qty)
327
self.invalidFlag = False
329
return target.timetuple()
332
def parseDate(self, dateString):
334
Parses strings like 05/28/200 or 04.21
336
@type dateString: string
337
@param dateString: text to convert to a datetime
340
@return: calculated datetime value of dateString
342
yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
345
m = self.CRE_DATE2.search(s)
351
m = self.CRE_DATE2.search(s)
355
yr = int(s[index + 1:])
356
# TODO should this have a birthday epoch constraint?
360
dy = int(string.strip(s))
362
if mth <= 12 and dy <= self.ptc.DaysInMonthList[mth - 1]:
363
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
365
self.invalidFlag = True
366
sourceTime = time.localtime() #return current time if date string is invalid
371
def parseDateText(self, dateString):
373
Parses strings like "May 31st, 2006" or "Jan 1st" or "July 2006"
375
@type dateString: string
376
@param dateString: text to convert to a datetime
379
@return: calculated datetime value of dateString
381
yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
386
s = dateString.lower()
387
m = self.CRE_DATE3.search(s)
388
mth = m.group('mthname')
389
mth = int(self.ptc.MthNames[mth])
391
if m.group('day') != None:
392
dy = int(m.group('day'))
396
if m.group('year') != None:
397
yr = int(m.group('year'))
398
elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
399
# if that day and month have already passed in this year,
400
# then increment the year by 1
403
if dy <= self.ptc.DaysInMonthList[mth - 1]:
404
sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
406
# Return current time if date string is invalid
407
self.invalidFlag = True
408
sourceTime = time.localtime()
413
def evalRanges(self,datetimeString,sourceTime=None):
415
Evaluates the strings with time or date ranges
424
s = string.strip(datetimeString.lower())
426
m = self.CRE_TIMERNG1.search(s)
430
m = self.CRE_TIMERNG2.search(s)
434
m = self.CRE_TIMERNG3.search(s)
438
m = self.CRE_DATERNG1.search(s)
442
m = self.CRE_DATERNG2.search(s)
446
m = self.CRE_DATERNG3.search(s)
452
# capture remaining string
456
s = str1 + ' ' + str2
458
sourceTime, flag = self.parse(s, sourceTime)
466
m = re.search('-',parseStr)
467
startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
468
endTime, eflag = self.parse((parseStr[(m.start()+1):]), sourceTime)
469
if eflag is False and sflag is False:
470
return (startTime, endTime, False)
473
m = re.search('-',parseStr)
474
startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
475
endTime, eflag = self.parse((parseStr[(m.start()+1):]), sourceTime)
476
if eflag is False and sflag is False:
477
return (startTime, endTime, False)
480
m = re.search('-',parseStr)
482
#capturing the meridian from the end time
483
ampm = re.search('a',parseStr)
485
#appending the meridian to the start time
487
startTime, sflag = self.parse((parseStr[:m.start()]+'am'), sourceTime)
489
startTime, sflag = self.parse((parseStr[:m.start()]+'pm'), sourceTime)
490
endTime, eflag = self.parse(parseStr[(m.start()+1):], sourceTime)
491
if eflag is False and sflag is False:
492
return (startTime, endTime, False)
495
m = re.search('-',parseStr)
496
startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
497
endDate, eflag = self.parse((parseStr[(m.start()+1):]), sourceTime)
498
if eflag is False and sflag is False:
499
return (startDate, endDate, False)
502
m = re.search('-',parseStr)
503
endDate = parseStr[(m.start()+1):]
505
#capturing the year from the end date
506
date = self.CRE_DATE3.search(endDate)
507
endYear = date.group('year')
509
# appending the year to the start date if the start date does not have year information
510
# and the end date does. eg : "Aug 21 - Sep 4, 2007
511
if endYear is not None:
512
startDate = parseStr[:m.start()]
513
date = self.CRE_DATE3.search(startDate)
514
startYear = date.group('year')
515
if startYear is None:
518
startDate = parseStr[:m.start()]
520
startDate, sflag = self.parse(startDate, sourceTime)
521
endDate, eflag = self.parse(endDate, sourceTime)
522
if eflag is False and sflag is False:
523
return (startDate, endDate, False)
526
m = re.search('-',parseStr)
528
startDate = parseStr[:m.start()]
530
#capturing the month from the start date
531
mth = self.CRE_DATE3.search(startDate)
532
mth = mth.group('mthname')
534
# appending the month name to the end date
535
endDate = mth + parseStr[(m.start()+1):]
537
startDate, sflag = self.parse(startDate, sourceTime)
538
endDate, eflag = self.parse(endDate, sourceTime)
539
if eflag is False and sflag is False:
540
return (startDate, endDate, False)
542
sourceTime = time.localtime()
543
#if range is not found
544
return (sourceTime, sourceTime, True)
547
def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
549
Evaluate the modifier string and following text (passed in
550
as chunk1 and chunk2) and if they match any known modifiers
551
calculate the delta and apply it to sourceTime
553
@type modifier: string
554
@param modifier: modifier text to apply to sourceTime
556
@param chunk1: first text chunk that followed modifier (if any)
558
@param chunk2: second text chunk that followed modifier (if any)
559
@type sourceTime: datetime
560
@param sourceTime: datetime value to use as the base
563
@return: tuple of any remaining text and the modified sourceTime
565
offset = self.ptc.Modifiers[modifier]
567
if sourceTime is not None:
568
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
570
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
572
# capture the units after the modifier and the remaining string after the unit
573
m = self.CRE_REMAINING.search(chunk2)
575
index = m.start() + 1
576
unit = chunk2[:m.start()]
577
chunk2 = chunk2[index:]
584
if unit == self.ptc.Target_Text['month'] or \
585
unit == self.ptc.Target_Text['mth']:
587
dy = self.ptc.DaysInMonthList[mth - 1]
588
sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
590
# if day is the last day of the month, calculate the last day of the next month
591
if dy == self.ptc.DaysInMonthList[mth - 1]:
592
dy = self.ptc.DaysInMonthList[mth]
594
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
595
target = self.inc(start, month=1)
596
sourceTime = target.timetuple()
598
start = datetime.datetime(yr, mth, 1, 9, 0, 0)
599
target = self.inc(start, month=offset)
600
sourceTime = target.timetuple()
604
if unit == self.ptc.Target_Text['week'] or \
605
unit == self.ptc.Target_Text['wk'] or \
606
unit == self.ptc.Target_Text['w']:
608
start = datetime.datetime(yr, mth, dy, 17, 0, 0)
609
target = start + datetime.timedelta(days=(4 - wd))
610
sourceTime = target.timetuple()
612
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
613
target = start + datetime.timedelta(days=7)
614
sourceTime = target.timetuple()
616
return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
620
if unit == self.ptc.Target_Text['day'] or \
621
unit == self.ptc.Target_Text['dy'] or \
622
unit == self.ptc.Target_Text['d']:
624
sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
626
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
627
target = start + datetime.timedelta(days=1)
628
sourceTime = target.timetuple()
630
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
631
target = start + datetime.timedelta(days=offset)
632
sourceTime = target.timetuple()
636
if unit == self.ptc.Target_Text['hour'] or \
637
unit == self.ptc.Target_Text['hr']:
639
sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
641
start = datetime.datetime(yr, mth, dy, hr, 0, 0)
642
target = start + datetime.timedelta(hours=offset)
643
sourceTime = target.timetuple()
647
if unit == self.ptc.Target_Text['year'] or \
648
unit == self.ptc.Target_Text['yr'] or \
649
unit == self.ptc.Target_Text['y']:
651
sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
653
sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
655
sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
660
m = self.CRE_WEEKDAY.match(unit)
663
wkdy = self.ptc.WeekDays[wkdy]
667
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
668
target = start + datetime.timedelta(days=diff)
669
sourceTime = target.timetuple()
672
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
673
target = start + datetime.timedelta(days=diff + 7 * offset)
674
sourceTime = target.timetuple()
679
m = self.CRE_TIME.match(unit)
681
(yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
682
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
683
target = start + datetime.timedelta(days=offset)
684
sourceTime = target.timetuple()
687
self.modifierFlag = False
689
# if the word after next is a number, the string is likely
690
# to be something like "next 4 hrs" for which we have to
691
# combine the units with the rest of the string
694
# if offset is negative, the unit has to be made negative
697
chunk2 = '%s %s' % (unit, chunk2)
699
self.modifierFlag = False
701
return '%s %s' % (chunk1, chunk2), sourceTime
704
def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
706
Evaluate the modifier string and following text (passed in
707
as chunk1 and chunk2) and if they match any known modifiers
708
calculate the delta and apply it to sourceTime
710
@type modifier: string
711
@param modifier: modifier text to apply to sourceTime
713
@param chunk1: first text chunk that followed modifier (if any)
715
@param chunk2: second text chunk that followed modifier (if any)
716
@type sourceTime: datetime
717
@param sourceTime: datetime value to use as the base
720
@return: tuple of any remaining text and the modified sourceTime
722
offset = self.ptc.Modifiers[modifier]
725
if sourceTime is not None:
726
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
728
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
730
self.modifier2Flag = False
732
# If the string after the negative modifier starts with
733
# digits, then it is likely that the string is similar to
734
# " before 3 days" or 'evening prior to 3 days'.
735
# In this case, the total time is calculated by subtracting
736
# '3 days' from the current date.
737
# So, we have to identify the quantity and negate it before
738
# parsing the string.
739
# This is not required for strings not starting with digits
740
# since the string is enough to calculate the sourceTime
742
m = re.match(digit, string.strip(chunk2))
744
qty = int(m.group()) * -1
745
chunk2 = chunk2[m.end():]
746
chunk2 = '%d%s' % (qty, chunk2)
748
sourceTime, flag = self.parse(chunk2, sourceTime)
752
m = re.match(digit, string.strip(chunk1))
754
qty = int(m.group()) * -1
755
chunk1 = chunk1[m.end():]
756
chunk1 = '%d%s' % (qty, chunk1)
758
sourceTime, flag = self.parse(chunk1, sourceTime)
760
return '', sourceTime
763
def _evalString(self, datetimeString, sourceTime=None):
765
Calculate the datetime based on flags set by the L{parse()} routine
768
RFC822, W3CDTF formatted dates
773
@type datetimeString: string
774
@param datetimeString: text to try and parse as more "traditional" date/time text
775
@type sourceTime: datetime
776
@param sourceTime: datetime value to use as the base
779
@return: calculated datetime value or current datetime if not parsed
781
s = string.strip(datetimeString)
783
# Given string date is a RFC822 date
784
if sourceTime is None:
785
sourceTime = _parse_date_rfc822(s)
787
# Given string date is a W3CDTF date
788
if sourceTime is None:
789
sourceTime = _parse_date_w3dtf(s)
791
if sourceTime is None:
794
# Given string is in the format HH:MM(:SS)(am/pm)
795
if self.meridianFlag:
796
if sourceTime is None:
797
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
799
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
801
m = self.CRE_TIMEHMS2.search(s)
803
dt = s[:m.start('meridian')].strip()
809
hr, mn, sec = _extract_time(m)
814
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
815
meridian = m.group('meridian')
817
if (re.compile("a").search(meridian)) and hr == 12:
818
sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
819
if (re.compile("p").search(meridian)) and hr < 12:
820
sourceTime = (yr, mth, dy, hr+12, mn, sec, wd, yd, isdst)
823
if hr > 24 or mn > 59 or sec > 59:
824
sourceTime = time.localtime()
825
self.invalidFlag = True
827
self.meridianFlag = False
829
# Given string is in the format HH:MM(:SS)
831
if sourceTime is None:
832
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
834
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
836
m = self.CRE_TIMEHMS.search(s)
838
hr, mn, sec = _extract_time(m)
842
if hr > 24 or mn > 59 or sec > 59:
844
sourceTime = time.localtime()
845
self.invalidFlag = True
847
sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
849
self.timeFlag = False
851
# Given string is in the format 07/21/2006
853
sourceTime = self.parseDate(s)
854
self.dateStdFlag = False
856
# Given string is in the format "May 23rd, 2005"
858
sourceTime = self.parseDateText(s)
859
self.dateStrFlag = False
861
# Given string is a weekday
863
yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
864
start = datetime.datetime(yr, mth, dy, hr, mn, sec)
865
wkDy = self.ptc.WeekDays[s]
869
target = start + datetime.timedelta(days=qty)
872
qty = 6 - wd + wkDy + 1
873
target = start + datetime.timedelta(days=qty)
876
sourceTime = target.timetuple()
877
self.weekdyFlag = False
879
# Given string is a natural language time string like lunch, midnight, etc
881
if sourceTime is None:
882
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
884
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
886
sources = { 'now': (yr, mth, dy, hr, mn, sec, wd, yd, isdst),
887
'noon': (yr, mth, dy, 12, 0, 0, wd, yd, isdst),
888
'lunch': (yr, mth, dy, 12, 0, 0, wd, yd, isdst),
889
'morning': (yr, mth, dy, 6, 0, 0, wd, yd, isdst),
890
'breakfast': (yr, mth, dy, 8, 0, 0, wd, yd, isdst),
891
'dinner': (yr, mth, dy, 19, 0, 0, wd, yd, isdst),
892
'evening': (yr, mth, dy, 18, 0, 0, wd, yd, isdst),
893
'midnight': (yr, mth, dy, 0, 0, 0, wd, yd, isdst),
894
'night': (yr, mth, dy, 21, 0, 0, wd, yd, isdst),
895
'tonight': (yr, mth, dy, 21, 0, 0, wd, yd, isdst),
899
sourceTime = sources[s]
901
sourceTime = time.localtime()
902
self.invalidFlag = True
904
self.timeStrFlag = False
906
# Given string is a natural language date string like today, tomorrow..
908
if sourceTime is None:
909
sourceTime = time.localtime()
911
(yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
913
sources = { 'tomorrow': 1,
918
start = datetime.datetime(yr, mth, dy, 9, 0, 0)
919
target = start + datetime.timedelta(days=sources[s])
920
sourceTime = target.timetuple()
922
self.dayStrFlag = False
924
# Given string is a time string with units like "5 hrs 30 min"
928
if sourceTime is None:
929
sourceTime = time.localtime()
931
m = self.CRE_UNITS.search(s)
933
units = m.group('units')
934
quantity = s[:m.start('units')]
936
sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
937
self.unitsFlag = False
939
# Given string is a time string with single char units like "5 h 30 m"
943
if sourceTime is None:
944
sourceTime = time.localtime()
946
m = self.CRE_QUNITS.search(s)
948
units = m.group('qunits')
949
quantity = s[:m.start('qunits')]
951
sourceTime = self._buildTime(sourceTime, quantity, modifier, units)
952
self.qunitsFlag = False
954
# Given string does not match anything
955
if sourceTime is None:
956
sourceTime = time.localtime()
957
self.invalidFlag = True
962
def parse(self, datetimeString, sourceTime=None):
964
Splits the L{datetimeString} into tokens, finds the regex patters
965
that match and then calculates a datetime value from the chunks
967
if L{sourceTime} is given then the datetime value will be calcualted
968
from that datetime, otherwise from the current datetime.
970
@type datetimeString: string
971
@param datetimeString: datetime text to evaluate
972
@type sourceTime: datetime
973
@param sourceTime: datetime value to use as the base
976
@return: tuple of any remaining text and the modified sourceTime
978
s = string.strip(datetimeString.lower())
981
totalTime = sourceTime
983
self.invalidFlag = False
986
if sourceTime is not None:
987
return (sourceTime, False)
989
return (time.localtime(), True)
997
print 'parse (top of loop): [%s][%s]' % (s, parseStr)
1000
# Modifier like next\prev..
1001
m = self.CRE_MODIFIER.search(s)
1003
self.modifierFlag = True
1004
if (m.group('modifier') != s):
1005
# capture remaining string
1006
parseStr = m.group('modifier')
1007
chunk1 = string.strip(s[:m.start('modifier')])
1008
chunk2 = string.strip(s[m.end('modifier'):])
1014
# Modifier like from\after\prior..
1015
m = self.CRE_MODIFIER2.search(s)
1017
self.modifier2Flag = True
1018
if (m.group('modifier') != s):
1019
# capture remaining string
1020
parseStr = m.group('modifier')
1021
chunk1 = string.strip(s[:m.start('modifier')])
1022
chunk2 = string.strip(s[m.end('modifier'):])
1028
# String date format
1029
m = self.CRE_DATE3.search(s)
1031
self.dateStrFlag = True
1032
if (m.group('date') != s):
1033
# capture remaining string
1034
parseStr = m.group('date')
1035
chunk1 = s[:m.start('date')]
1036
chunk2 = s[m.end('date'):]
1037
s = '%s %s' % (chunk1, chunk2)
1043
# Standard date format
1044
m = self.CRE_DATE.search(s)
1046
self.dateStdFlag = True
1047
if (m.group('date') != s):
1048
# capture remaining string
1049
parseStr = m.group('date')
1050
chunk1 = s[:m.start('date')]
1051
chunk2 = s[m.end('date'):]
1052
s = '%s %s' % (chunk1, chunk2)
1058
# Natural language day strings
1059
m = self.CRE_DAY.search(s)
1061
self.dayStrFlag = True
1062
if (m.group('day') != s):
1063
# capture remaining string
1064
parseStr = m.group('day')
1065
chunk1 = s[:m.start('day')]
1066
chunk2 = s[m.end('day'):]
1067
s = '%s %s' % (chunk1, chunk2)
1074
m = self.CRE_UNITS.search(s)
1076
self.unitsFlag = True
1077
if (m.group('qty') != s):
1078
# capture remaining string
1079
parseStr = m.group('qty')
1080
chunk1 = s[:m.start('qty')].strip()
1081
chunk2 = s[m.end('qty'):].strip()
1083
if chunk1[-1:] == '-':
1084
parseStr = '-%s' % parseStr
1085
chunk1 = chunk1[:-1]
1087
s = '%s %s' % (chunk1, chunk2)
1094
m = self.CRE_QUNITS.search(s)
1096
self.qunitsFlag = True
1097
if (m.group('qty') != s):
1098
# capture remaining string
1099
parseStr = m.group('qty')
1100
chunk1 = s[:m.start('qty')].strip()
1101
chunk2 = s[m.end('qty'):].strip()
1103
if chunk1[-1:] == '-':
1104
parseStr = '-%s' % parseStr
1105
chunk1 = chunk1[:-1]
1107
s = '%s %s' % (chunk1, chunk2)
1114
m = self.CRE_WEEKDAY.search(s)
1116
self.weekdyFlag = True
1117
if (m.group('weekday') != s):
1118
# capture remaining string
1119
parseStr = m.group()
1120
chunk1 = s[:m.start('weekday')]
1121
chunk2 = s[m.end('weekday'):]
1122
s = '%s %s' % (chunk1, chunk2)
1128
# Natural language time strings
1129
m = self.CRE_TIME.search(s)
1131
self.timeStrFlag = True
1132
if (m.group('time') != s):
1133
# capture remaining string
1134
parseStr = m.group('time')
1135
chunk1 = s[:m.start('time')]
1136
chunk2 = s[m.end('time'):]
1137
s = '%s %s' % (chunk1, chunk2)
1143
# HH:MM(:SS) am/pm time strings
1144
m = self.CRE_TIMEHMS2.search(s)
1146
self.meridianFlag = True
1147
if m.group('minutes') is not None:
1148
if m.group('seconds') is not None:
1149
parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
1151
parseStr = '%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('meridian'))
1153
parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
1155
chunk1 = s[:m.start('hours')]
1156
chunk2 = s[m.end('meridian'):]
1158
s = '%s %s' % (chunk1, chunk2)
1162
# HH:MM(:SS) time strings
1163
m = self.CRE_TIMEHMS.search(s)
1165
self.timeFlag = True
1166
if m.group('seconds') is not None:
1167
parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
1168
chunk1 = s[:m.start('hours')]
1169
chunk2 = s[m.end('seconds'):]
1171
parseStr = '%s:%s' % (m.group('hours'), m.group('minutes'))
1172
chunk1 = s[:m.start('hours')]
1173
chunk2 = s[m.end('minutes'):]
1175
s = '%s %s' % (chunk1, chunk2)
1178
# if string does not match any regex, empty string to come out of the while loop
1183
print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
1184
print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1185
(self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
1186
print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
1187
(self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
1189
# evaluate the matched string
1191
if self.modifierFlag == True:
1192
t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
1194
return self.parse(t, totalTime)
1196
elif self.modifier2Flag == True:
1197
s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
1199
totalTime = self._evalString(parseStr, totalTime)
1202
# String is not parsed at all
1203
if totalTime is None or totalTime == sourceTime:
1204
totalTime = time.localtime()
1205
self.invalidFlag = True
1207
return (totalTime, self.invalidFlag)
1210
def inc(self, source, month=None, year=None):
1212
Takes the given date, or current date if none is passed, and
1213
increments it according to the values passed in by month
1216
This routine is needed because the timedelta() routine does
1217
not allow for month or year increments.
1219
@type source: datetime
1220
@param source: datetime value to increment
1221
@type month: integer
1222
@param month: optional number of months to increment
1224
@param year: optional number of years to increment
1227
@return: L{source} incremented by the number of months and/or years
1247
y = m / 12 # how many years are in month increment
1248
m = m % 12 # get remaining months
1251
mth = mth - m # sub months from start month
1252
if mth < 1: # cross start-of-year?
1253
y -= 1 # yes - decrement year
1254
mth += 12 # and fix month
1256
mth = mth + m # add months to start month
1257
if mth > 12: # cross end-of-year?
1258
y += 1 # yes - increment year
1259
mth -= 12 # and fix month
1263
d = source.replace(year=yr, month=mth)
1265
return source + (d - source)