Back to index

moin  1.9.0~rc2
parsedatetime.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 """
00004 Parse human-readable date/time text.
00005 """
00006 
00007 __license__ = """
00008 Copyright (c) 2004-2008 Mike Taylor
00009 Copyright (c) 2006-2008 Darshana Chhajed
00010 All rights reserved.
00011 
00012 Licensed under the Apache License, Version 2.0 (the "License");
00013 you may not use this file except in compliance with the License.
00014 You may obtain a copy of the License at
00015 
00016    http://www.apache.org/licenses/LICENSE-2.0
00017 
00018 Unless required by applicable law or agreed to in writing, software
00019 distributed under the License is distributed on an "AS IS" BASIS,
00020 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00021 See the License for the specific language governing permissions and
00022 limitations under the License.
00023 """
00024 
00025 _debug = False
00026 
00027 
00028 import re
00029 import time
00030 import datetime
00031 import rfc822
00032 import parsedatetime_consts
00033 
00034 
00035 # Copied from feedparser.py
00036 # Universal Feedparser
00037 # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
00038 # Originally a def inside of _parse_date_w3dtf()
00039 def _extract_date(m):
00040     year = int(m.group('year'))
00041     if year < 100:
00042         year = 100 * int(time.gmtime()[0] / 100) + int(year)
00043     if year < 1000:
00044         return 0, 0, 0
00045     julian = m.group('julian')
00046     if julian:
00047         julian = int(julian)
00048         month = julian / 30 + 1
00049         day = julian % 30 + 1
00050         jday = None
00051         while jday != julian:
00052             t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
00053             jday = time.gmtime(t)[-2]
00054             diff = abs(jday - julian)
00055             if jday > julian:
00056                 if diff < day:
00057                     day = day - diff
00058                 else:
00059                     month = month - 1
00060                     day = 31
00061             elif jday < julian:
00062                 if day + diff < 28:
00063                     day = day + diff
00064                 else:
00065                     month = month + 1
00066         return year, month, day
00067     month = m.group('month')
00068     day = 1
00069     if month is None:
00070         month = 1
00071     else:
00072         month = int(month)
00073         day = m.group('day')
00074         if day:
00075             day = int(day)
00076         else:
00077             day = 1
00078     return year, month, day
00079 
00080 # Copied from feedparser.py
00081 # Universal Feedparser
00082 # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
00083 # Originally a def inside of _parse_date_w3dtf()
00084 def _extract_time(m):
00085     if not m:
00086         return 0, 0, 0
00087     hours = m.group('hours')
00088     if not hours:
00089         return 0, 0, 0
00090     hours = int(hours)
00091     minutes = int(m.group('minutes'))
00092     seconds = m.group('seconds')
00093     if seconds:
00094         seconds = int(seconds)
00095     else:
00096         seconds = 0
00097     return hours, minutes, seconds
00098 
00099 
00100 # Copied from feedparser.py
00101 # Universal Feedparser
00102 # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
00103 # Modified to return a tuple instead of mktime
00104 #
00105 # Original comment:
00106 #   W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
00107 #   Drake and licensed under the Python license.  Removed all range checking
00108 #   for month, day, hour, minute, and second, since mktime will normalize
00109 #   these later
00110 def _parse_date_w3dtf(dateString):
00111     # the __extract_date and __extract_time methods were
00112     # copied-out so they could be used by my code --bear
00113     def __extract_tzd(m):
00114         '''Return the Time Zone Designator as an offset in seconds from UTC.'''
00115         if not m:
00116             return 0
00117         tzd = m.group('tzd')
00118         if not tzd:
00119             return 0
00120         if tzd == 'Z':
00121             return 0
00122         hours = int(m.group('tzdhours'))
00123         minutes = m.group('tzdminutes')
00124         if minutes:
00125             minutes = int(minutes)
00126         else:
00127             minutes = 0
00128         offset = (hours*60 + minutes) * 60
00129         if tzd[0] == '+':
00130             return -offset
00131         return offset
00132 
00133     __date_re = ('(?P<year>\d\d\d\d)'
00134                  '(?:(?P<dsep>-|)'
00135                  '(?:(?P<julian>\d\d\d)'
00136                  '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
00137     __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
00138     __tzd_rx = re.compile(__tzd_re)
00139     __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
00140                  '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
00141                  + __tzd_re)
00142     __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
00143     __datetime_rx = re.compile(__datetime_re)
00144     m = __datetime_rx.match(dateString)
00145     if (m is None) or (m.group() != dateString): return
00146     return _extract_date(m) + _extract_time(m) + (0, 0, 0)
00147 
00148 
00149 # Copied from feedparser.py
00150 # Universal Feedparser
00151 # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
00152 # Modified to return a tuple instead of mktime
00153 #
00154 def _parse_date_rfc822(dateString):
00155     '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
00156     data = dateString.split()
00157     if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
00158         del data[0]
00159     if len(data) == 4:
00160         s = data[3]
00161         i = s.find('+')
00162         if i > 0:
00163             data[3:] = [s[:i], s[i+1:]]
00164         else:
00165             data.append('')
00166         dateString = " ".join(data)
00167     if len(data) < 5:
00168         dateString += ' 00:00:00 GMT'
00169     return rfc822.parsedate_tz(dateString)
00170 
00171 # rfc822.py defines several time zones, but we define some extra ones.
00172 # 'ET' is equivalent to 'EST', etc.
00173 _additional_timezones = {'AT': -400, 'ET': -500,
00174                          'CT': -600, 'MT': -700,
00175                          'PT': -800}
00176 rfc822._timezones.update(_additional_timezones)
00177 
00178 
00179 class Calendar:
00180     """
00181     A collection of routines to input, parse and manipulate date and times.
00182     The text can either be 'normal' date values or it can be human readable.
00183     """
00184 
00185     def __init__(self, constants=None):
00186         """
00187         Default constructor for the L{Calendar} class.
00188 
00189         @type  constants: object
00190         @param constants: Instance of the class L{parsedatetime_consts.Constants}
00191 
00192         @rtype:  object
00193         @return: L{Calendar} instance
00194         """
00195           # if a constants reference is not included, use default
00196         if constants is None:
00197             self.ptc = parsedatetime_consts.Constants()
00198         else:
00199             self.ptc = constants
00200 
00201         self.weekdyFlag    = False  # monday/tuesday/...
00202         self.dateStdFlag   = False  # 07/21/06
00203         self.dateStrFlag   = False  # July 21st, 2006
00204         self.timeStdFlag   = False  # 5:50 
00205         self.meridianFlag  = False  # am/pm
00206         self.dayStrFlag    = False  # tomorrow/yesterday/today/..
00207         self.timeStrFlag   = False  # lunch/noon/breakfast/...
00208         self.modifierFlag  = False  # after/before/prev/next/..
00209         self.modifier2Flag = False  # after/before/prev/next/..
00210         self.unitsFlag     = False  # hrs/weeks/yrs/min/..
00211         self.qunitsFlag    = False  # h/m/t/d..
00212 
00213         self.timeFlag      = 0
00214         self.dateFlag      = 0
00215 
00216 
00217     def _convertUnitAsWords(self, unitText):
00218         """
00219         Converts text units into their number value
00220 
00221         Five = 5
00222         Twenty Five = 25
00223         Two hundred twenty five = 225
00224         Two thousand and twenty five = 2025
00225         Two thousand twenty five = 2025
00226 
00227         @type  unitText: string
00228         @param unitText: number text to convert
00229 
00230         @rtype:  integer
00231         @return: numerical value of unitText
00232         """
00233         # TODO: implement this
00234         pass
00235 
00236 
00237     def _buildTime(self, source, quantity, modifier, units):
00238         """
00239         Take C{quantity}, C{modifier} and C{unit} strings and convert them into values.
00240         After converting, calcuate the time and return the adjusted sourceTime.
00241 
00242         @type  source:   time
00243         @param source:   time to use as the base (or source)
00244         @type  quantity: string
00245         @param quantity: quantity string
00246         @type  modifier: string
00247         @param modifier: how quantity and units modify the source time
00248         @type  units:    string
00249         @param units:    unit of the quantity (i.e. hours, days, months, etc)
00250 
00251         @rtype:  struct_time
00252         @return: C{struct_time} of the calculated time
00253         """
00254         if _debug:
00255             print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
00256 
00257         if source is None:
00258             source = time.localtime()
00259 
00260         if quantity is None:
00261             quantity = ''
00262         else:
00263             quantity = quantity.strip()
00264 
00265         if len(quantity) == 0:
00266             qty = 1
00267         else:
00268             try:
00269                 qty = int(quantity)
00270             except ValueError:
00271                 qty = 0
00272 
00273         if modifier in self.ptc.Modifiers:
00274             qty = qty * self.ptc.Modifiers[modifier]
00275 
00276             if units is None or units == '':
00277                 units = 'dy'
00278 
00279         # plurals are handled by regex's (could be a bug tho)
00280 
00281         (yr, mth, dy, hr, mn, sec, _, _, _) = source
00282 
00283         start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
00284         target = start
00285 
00286         if units.startswith('y'):
00287             target        = self.inc(start, year=qty)
00288             self.dateFlag = 1
00289         elif units.endswith('th') or units.endswith('ths'):
00290             target        = self.inc(start, month=qty)
00291             self.dateFlag = 1
00292         else:
00293             if units.startswith('d'):
00294                 target        = start + datetime.timedelta(days=qty)
00295                 self.dateFlag = 1
00296             elif units.startswith('h'):
00297                 target        = start + datetime.timedelta(hours=qty)
00298                 self.timeFlag = 2
00299             elif units.startswith('m'):
00300                 target        = start + datetime.timedelta(minutes=qty)
00301                 self.timeFlag = 2
00302             elif units.startswith('s'):
00303                 target        = start + datetime.timedelta(seconds=qty)
00304                 self.timeFlag = 2
00305             elif units.startswith('w'):
00306                 target        = start + datetime.timedelta(weeks=qty)
00307                 self.dateFlag = 1
00308 
00309         return target.timetuple()
00310 
00311 
00312     def parseDate(self, dateString):
00313         """
00314         Parse short-form date strings::
00315 
00316             '05/28/2006' or '04.21'
00317 
00318         @type  dateString: string
00319         @param dateString: text to convert to a C{datetime}
00320 
00321         @rtype:  struct_time
00322         @return: calculated C{struct_time} value of dateString
00323         """
00324         yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
00325 
00326         # values pulled from regex's will be stored here and later
00327         # assigned to mth, dy, yr based on information from the locale
00328         # -1 is used as the marker value because we want zero values
00329         # to be passed thru so they can be flagged as errors later
00330         v1 = -1
00331         v2 = -1
00332         v3 = -1
00333 
00334         s = dateString
00335         m = self.ptc.CRE_DATE2.search(s)
00336         if m is not None:
00337             index = m.start()
00338             v1    = int(s[:index])
00339             s     = s[index + 1:]
00340 
00341         m = self.ptc.CRE_DATE2.search(s)
00342         if m is not None:
00343             index = m.start()
00344             v2    = int(s[:index])
00345             v3    = int(s[index + 1:])
00346         else:
00347             v2 = int(s.strip())
00348 
00349         v = [ v1, v2, v3 ]
00350         d = { 'm': mth, 'd': dy, 'y': yr }
00351 
00352         for i in range(0, 3):
00353             n = v[i]
00354             c = self.ptc.dp_order[i]
00355             if n >= 0:
00356                 d[c] = n
00357 
00358         # if the year is not specified and the date has already
00359         # passed, increment the year
00360         if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
00361             yr = d['y'] + 1
00362         else:
00363             yr  = d['y']
00364 
00365         mth = d['m']
00366         dy  = d['d']
00367 
00368         # birthday epoch constraint
00369         if yr < self.ptc.BirthdayEpoch:
00370             yr += 2000
00371         elif yr < 100:
00372             yr += 1900
00373 
00374         if _debug:
00375             print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr)
00376 
00377         if (mth > 0 and mth <= 12) and \
00378            (dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)):
00379             sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
00380         else:
00381             self.dateFlag = 0
00382             self.timeFlag = 0
00383             sourceTime    = time.localtime() # return current time if date
00384                                              # string is invalid
00385 
00386         return sourceTime
00387 
00388 
00389     def parseDateText(self, dateString):
00390         """
00391         Parse long-form date strings::
00392 
00393             'May 31st, 2006'
00394             'Jan 1st'
00395             'July 2006'
00396 
00397         @type  dateString: string
00398         @param dateString: text to convert to a datetime
00399 
00400         @rtype:  struct_time
00401         @return: calculated C{struct_time} value of dateString
00402         """
00403         yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
00404 
00405         currentMth = mth
00406         currentDy  = dy
00407 
00408         s   = dateString.lower()
00409         m   = self.ptc.CRE_DATE3.search(s)
00410         mth = m.group('mthname')
00411         mth = self.ptc.MonthOffsets[mth]
00412 
00413         if m.group('day') !=  None:
00414             dy = int(m.group('day'))
00415         else:
00416             dy = 1
00417 
00418         if m.group('year') !=  None:
00419             yr = int(m.group('year'))
00420 
00421             # birthday epoch constraint
00422             if yr < self.ptc.BirthdayEpoch:
00423                 yr += 2000
00424             elif yr < 100:
00425                 yr += 1900
00426 
00427         elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
00428             # if that day and month have already passed in this year,
00429             # then increment the year by 1
00430             yr += 1
00431 
00432         if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr):
00433             sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
00434         else:
00435             # Return current time if date string is invalid
00436             self.dateFlag = 0
00437             self.timeFlag = 0
00438             sourceTime    = time.localtime()
00439 
00440         return sourceTime
00441 
00442 
00443     def evalRanges(self, datetimeString, sourceTime=None):
00444         """
00445         Evaluate the C{datetimeString} text and determine if
00446         it represents a date or time range.
00447 
00448         @type  datetimeString: string
00449         @param datetimeString: datetime text to evaluate
00450         @type  sourceTime:     struct_time
00451         @param sourceTime:     C{struct_time} value to use as the base
00452 
00453         @rtype:  tuple
00454         @return: tuple of: start datetime, end datetime and the invalid flag
00455         """
00456         startTime = ''
00457         endTime   = ''
00458         startDate = ''
00459         endDate   = ''
00460         rangeFlag = 0
00461 
00462         s = datetimeString.strip().lower()
00463 
00464         if self.ptc.rangeSep in s:
00465             s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep)
00466             s = s.replace('  ', ' ')
00467 
00468         m = self.ptc.CRE_TIMERNG1.search(s)
00469         if m is not None:
00470             rangeFlag = 1
00471         else:
00472             m = self.ptc.CRE_TIMERNG2.search(s)
00473             if m is not None:
00474                 rangeFlag = 2
00475             else:
00476                 m = self.ptc.CRE_TIMERNG4.search(s)
00477                 if m is not None:
00478                     rangeFlag = 7
00479                 else:
00480                     m = self.ptc.CRE_TIMERNG3.search(s)
00481                     if m is not None:
00482                         rangeFlag = 3
00483                     else:
00484                         m = self.ptc.CRE_DATERNG1.search(s)
00485                         if m is not None:
00486                             rangeFlag = 4
00487                         else:
00488                             m = self.ptc.CRE_DATERNG2.search(s)
00489                             if m is not None:
00490                                 rangeFlag = 5
00491                             else:
00492                                 m = self.ptc.CRE_DATERNG3.search(s)
00493                                 if m is not None:
00494                                     rangeFlag = 6
00495 
00496         if _debug:
00497             print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
00498 
00499         if m is not None:
00500             if (m.group() != s):
00501                 # capture remaining string
00502                 parseStr = m.group()
00503                 chunk1   = s[:m.start()]
00504                 chunk2   = s[m.end():]
00505                 s        = '%s %s' % (chunk1, chunk2)
00506                 flag     = 1
00507 
00508                 sourceTime, flag = self.parse(s, sourceTime)
00509 
00510                 if flag == 0:
00511                     sourceTime = None
00512             else:
00513                 parseStr = s
00514 
00515         if rangeFlag == 1:
00516             m                = re.search(self.ptc.rangeSep, parseStr)
00517             startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
00518             endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
00519 
00520             if (eflag != 0)  and (sflag != 0):
00521                 return (startTime, endTime, 2)
00522 
00523         elif rangeFlag == 2:
00524             m                = re.search(self.ptc.rangeSep, parseStr)
00525             startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
00526             endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
00527 
00528             if (eflag != 0)  and (sflag != 0):
00529                 return (startTime, endTime, 2)
00530 
00531         elif rangeFlag == 3 or rangeFlag == 7:
00532             m = re.search(self.ptc.rangeSep, parseStr)
00533             # capturing the meridian from the end time
00534             if self.ptc.usesMeridian:
00535                 ampm = re.search(self.ptc.am[0], parseStr)
00536 
00537                 # appending the meridian to the start time
00538                 if ampm is not None:
00539                     startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime)
00540                 else:
00541                     startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime)
00542             else:
00543                 startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
00544 
00545             endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
00546 
00547             if (eflag != 0)  and (sflag != 0):
00548                 return (startTime, endTime, 2)
00549 
00550         elif rangeFlag == 4:
00551             m                = re.search(self.ptc.rangeSep, parseStr)
00552             startDate, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
00553             endDate, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
00554 
00555             if (eflag != 0)  and (sflag != 0):
00556                 return (startDate, endDate, 1)
00557 
00558         elif rangeFlag == 5:
00559             m       = re.search(self.ptc.rangeSep, parseStr)
00560             endDate = parseStr[(m.start() + 1):]
00561 
00562             # capturing the year from the end date
00563             date    = self.ptc.CRE_DATE3.search(endDate)
00564             endYear = date.group('year')
00565 
00566             # appending the year to the start date if the start date
00567             # does not have year information and the end date does.
00568             # eg : "Aug 21 - Sep 4, 2007"
00569             if endYear is not None:
00570                 startDate = (parseStr[:m.start()]).strip()
00571                 date      = self.ptc.CRE_DATE3.search(startDate)
00572                 startYear = date.group('year')
00573 
00574                 if startYear is None:
00575                     startDate = startDate + ', ' + endYear
00576             else:
00577                 startDate = parseStr[:m.start()]
00578 
00579             startDate, sflag = self.parse(startDate, sourceTime)
00580             endDate, eflag   = self.parse(endDate, sourceTime)
00581 
00582             if (eflag != 0)  and (sflag != 0):
00583                 return (startDate, endDate, 1)
00584 
00585         elif rangeFlag == 6:
00586             m = re.search(self.ptc.rangeSep, parseStr)
00587 
00588             startDate = parseStr[:m.start()]
00589 
00590             # capturing the month from the start date
00591             mth = self.ptc.CRE_DATE3.search(startDate)
00592             mth = mth.group('mthname')
00593 
00594             # appending the month name to the end date
00595             endDate = mth + parseStr[(m.start() + 1):]
00596 
00597             startDate, sflag = self.parse(startDate, sourceTime)
00598             endDate, eflag   = self.parse(endDate, sourceTime)
00599 
00600             if (eflag != 0)  and (sflag != 0):
00601                 return (startDate, endDate, 1)
00602         else:
00603             # if range is not found
00604             sourceTime = time.localtime()
00605 
00606             return (sourceTime, sourceTime, 0)
00607 
00608 
00609     def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle):
00610         """
00611         Based on the C{style} and C{currentDayStyle} determine what
00612         day-of-week value is to be returned.
00613 
00614         @type  wd:              integer
00615         @param wd:              day-of-week value for the current day
00616         @type  wkdy:            integer
00617         @param wkdy:            day-of-week value for the parsed day
00618         @type  offset:          integer
00619         @param offset:          offset direction for any modifiers (-1, 0, 1)
00620         @type  style:           integer
00621         @param style:           normally the value set in C{Constants.DOWParseStyle}
00622         @type  currentDayStyle: integer
00623         @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}
00624 
00625         @rtype:  integer
00626         @return: calculated day-of-week
00627         """
00628         if offset == 1:
00629             # modifier is indicating future week eg: "next".
00630             # DOW is calculated as DOW of next week
00631             diff = 7 - wd + wkdy
00632 
00633         elif offset == -1:
00634             # modifier is indicating past week eg: "last","previous"
00635             # DOW is calculated as DOW of previous week
00636             diff = wkdy - wd - 7
00637 
00638         elif offset == 0:
00639             # modifier is indiacting current week eg: "this"
00640             # DOW is calculated as DOW of this week
00641             diff = wkdy - wd
00642 
00643         elif offset == 2:
00644             # no modifier is present.
00645             # i.e. string to be parsed is just DOW
00646             if style == 1:
00647                 # next occurance of the DOW is calculated
00648                 if currentDayStyle == True:
00649                     if wkdy >= wd:
00650                         diff = wkdy - wd
00651                     else:
00652                         diff = 7 - wd + wkdy
00653                 else:
00654                     if wkdy > wd:
00655                         diff = wkdy - wd
00656                     else:
00657                         diff = 7 - wd + wkdy
00658 
00659             elif style == -1:
00660                 # last occurance of the DOW is calculated
00661                 if currentDayStyle == True:
00662                     if wkdy <= wd:
00663                         diff = wkdy - wd
00664                     else:
00665                         diff = wkdy - wd - 7
00666                 else:
00667                     if wkdy < wd:
00668                         diff = wkdy - wd
00669                     else:
00670                         diff = wkdy - wd - 7
00671             else:
00672                 # occurance of the DOW in the current week is calculated
00673                 diff = wkdy - wd
00674 
00675         if _debug:
00676             print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
00677 
00678         return diff
00679 
00680 
00681     def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
00682         """
00683         Evaluate the C{modifier} string and following text (passed in
00684         as C{chunk1} and C{chunk2}) and if they match any known modifiers
00685         calculate the delta and apply it to C{sourceTime}.
00686 
00687         @type  modifier:   string
00688         @param modifier:   modifier text to apply to sourceTime
00689         @type  chunk1:     string
00690         @param chunk1:     first text chunk that followed modifier (if any)
00691         @type  chunk2:     string
00692         @param chunk2:     second text chunk that followed modifier (if any)
00693         @type  sourceTime: struct_time
00694         @param sourceTime: C{struct_time} value to use as the base
00695 
00696         @rtype:  tuple
00697         @return: tuple of: remaining text and the modified sourceTime
00698         """
00699         offset = self.ptc.Modifiers[modifier]
00700 
00701         if sourceTime is not None:
00702             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
00703         else:
00704             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
00705 
00706         # capture the units after the modifier and the remaining
00707         # string after the unit
00708         m = self.ptc.CRE_REMAINING.search(chunk2)
00709         if m is not None:
00710             index  = m.start() + 1
00711             unit   = chunk2[:m.start()]
00712             chunk2 = chunk2[index:]
00713         else:
00714             unit   = chunk2
00715             chunk2 = ''
00716 
00717         flag = False
00718 
00719         if unit == 'month' or \
00720            unit == 'mth' or \
00721            unit == 'm':
00722             if offset == 0:
00723                 dy         = self.ptc.daysInMonth(mth, yr)
00724                 sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
00725             elif offset == 2:
00726                 # if day is the last day of the month, calculate the last day
00727                 # of the next month
00728                 if dy == self.ptc.daysInMonth(mth, yr):
00729                     dy = self.ptc.daysInMonth(mth + 1, yr)
00730 
00731                 start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
00732                 target     = self.inc(start, month=1)
00733                 sourceTime = target.timetuple()
00734             else:
00735                 start      = datetime.datetime(yr, mth, 1, 9, 0, 0)
00736                 target     = self.inc(start, month=offset)
00737                 sourceTime = target.timetuple()
00738 
00739             flag = True
00740             self.dateFlag = 1
00741 
00742         if unit == 'week' or \
00743              unit == 'wk' or \
00744              unit == 'w':
00745             if offset == 0:
00746                 start      = datetime.datetime(yr, mth, dy, 17, 0, 0)
00747                 target     = start + datetime.timedelta(days=(4 - wd))
00748                 sourceTime = target.timetuple()
00749             elif offset == 2:
00750                 start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
00751                 target     = start + datetime.timedelta(days=7)
00752                 sourceTime = target.timetuple()
00753             else:
00754                 return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
00755 
00756             flag          = True
00757             self.dateFlag = 1
00758 
00759         if unit == 'day' or \
00760             unit == 'dy' or \
00761             unit == 'd':
00762             if offset == 0:
00763                 sourceTime    = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
00764                 self.timeFlag = 2
00765             elif offset == 2:
00766                 start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
00767                 target     = start + datetime.timedelta(days=1)
00768                 sourceTime = target.timetuple()
00769             else:
00770                 start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
00771                 target     = start + datetime.timedelta(days=offset)
00772                 sourceTime = target.timetuple()
00773 
00774             flag          = True
00775             self.dateFlag = 1
00776 
00777         if unit == 'hour' or \
00778            unit == 'hr':
00779             if offset == 0:
00780                 sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
00781             else:
00782                 start      = datetime.datetime(yr, mth, dy, hr, 0, 0)
00783                 target     = start + datetime.timedelta(hours=offset)
00784                 sourceTime = target.timetuple()
00785 
00786             flag          = True
00787             self.timeFlag = 2
00788 
00789         if unit == 'year' or \
00790              unit == 'yr' or \
00791              unit == 'y':
00792             if offset == 0:
00793                 sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
00794             elif offset == 2:
00795                 sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
00796             else:
00797                 sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
00798 
00799             flag          = True
00800             self.dateFlag = 1
00801 
00802         if flag == False:
00803             m = self.ptc.CRE_WEEKDAY.match(unit)
00804             if m is not None:
00805                 wkdy          = m.group()
00806                 self.dateFlag = 1
00807 
00808                 if modifier == 'eod':
00809                     # Calculate the  upcoming weekday
00810                     self.modifierFlag = False
00811                     (sourceTime, _)   = self.parse(wkdy, sourceTime)
00812                     sources           = self.ptc.buildSources(sourceTime)
00813                     self.timeFlag     = 2
00814 
00815                     if modifier in sources:
00816                         sourceTime = sources[modifier]
00817 
00818                 else:
00819                     wkdy       = self.ptc.WeekdayOffsets[wkdy]
00820                     diff       = self._CalculateDOWDelta(wd, wkdy, offset,
00821                                                          self.ptc.DOWParseStyle,
00822                                                          self.ptc.CurrentDOWParseStyle)
00823                     start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
00824                     target     = start + datetime.timedelta(days=diff)
00825                     sourceTime = target.timetuple()
00826 
00827                 flag          = True
00828                 self.dateFlag = 1
00829 
00830         if not flag:
00831             m = self.ptc.CRE_TIME.match(unit)
00832             if m is not None:
00833                 self.modifierFlag = False
00834                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
00835 
00836                 start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
00837                 target     = start + datetime.timedelta(days=offset)
00838                 sourceTime = target.timetuple()
00839                 flag       = True
00840             else:
00841                 self.modifierFlag = False
00842 
00843                 # check if the remaining text is parsable and if so,
00844                 # use it as the base time for the modifier source time
00845                 t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
00846 
00847                 if flag2 != 0:
00848                     sourceTime = t
00849 
00850                 sources = self.ptc.buildSources(sourceTime)
00851 
00852                 if modifier in sources:
00853                     sourceTime    = sources[modifier]
00854                     flag          = True
00855                     self.timeFlag = 2
00856 
00857         # if the word after next is a number, the string is more than likely
00858         # to be "next 4 hrs" which we will have to combine the units with the
00859         # rest of the string
00860         if not flag:
00861             if offset < 0:
00862                 # if offset is negative, the unit has to be made negative
00863                 unit = '-%s' % unit
00864 
00865             chunk2 = '%s %s' % (unit, chunk2)
00866 
00867         self.modifierFlag = False
00868 
00869         #return '%s %s' % (chunk1, chunk2), sourceTime
00870         return '%s' % chunk2, sourceTime
00871 
00872     def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
00873         """
00874         Evaluate the C{modifier} string and following text (passed in
00875         as C{chunk1} and C{chunk2}) and if they match any known modifiers
00876         calculate the delta and apply it to C{sourceTime}.
00877 
00878         @type  modifier:   string
00879         @param modifier:   modifier text to apply to C{sourceTime}
00880         @type  chunk1:     string
00881         @param chunk1:     first text chunk that followed modifier (if any)
00882         @type  chunk2:     string
00883         @param chunk2:     second text chunk that followed modifier (if any)
00884         @type  sourceTime: struct_time
00885         @param sourceTime: C{struct_time} value to use as the base
00886 
00887         @rtype:  tuple
00888         @return: tuple of: remaining text and the modified sourceTime
00889         """
00890         offset = self.ptc.Modifiers[modifier]
00891         digit  = r'\d+'
00892 
00893         self.modifier2Flag = False
00894 
00895         # If the string after the negative modifier starts with digits,
00896         # then it is likely that the string is similar to ' before 3 days'
00897         # or 'evening prior to 3 days'.
00898         # In this case, the total time is calculated by subtracting '3 days'
00899         # from the current date.
00900         # So, we have to identify the quantity and negate it before parsing
00901         # the string.
00902         # This is not required for strings not starting with digits since the
00903         # string is enough to calculate the sourceTime
00904         if chunk2 != '':
00905             if offset < 0:
00906                 m = re.match(digit, chunk2.strip())
00907                 if m is not None:
00908                     qty    = int(m.group()) * -1
00909                     chunk2 = chunk2[m.end():]
00910                     chunk2 = '%d%s' % (qty, chunk2)
00911 
00912             sourceTime, flag1 = self.parse(chunk2, sourceTime)
00913             if flag1 == 0:
00914                 flag1 = True
00915             else:
00916                 flag1 = False
00917             flag2 = False
00918         else:
00919             flag1 = False
00920 
00921         if chunk1 != '':
00922             if offset < 0:
00923                 m = re.search(digit, chunk1.strip())
00924                 if m is not None:
00925                     qty    = int(m.group()) * -1
00926                     chunk1 = chunk1[m.end():]
00927                     chunk1 = '%d%s' % (qty, chunk1)
00928 
00929             tempDateFlag       = self.dateFlag
00930             tempTimeFlag       = self.timeFlag
00931             sourceTime2, flag2 = self.parse(chunk1, sourceTime)
00932         else:
00933             return sourceTime, (flag1 and flag2)
00934 
00935         # if chunk1 is not a datetime and chunk2 is then do not use datetime
00936         # value returned by parsing chunk1
00937         if not (flag1 == False and flag2 == 0):
00938             sourceTime = sourceTime2
00939         else:
00940             self.timeFlag = tempTimeFlag
00941             self.dateFlag = tempDateFlag
00942 
00943         return sourceTime, (flag1 and flag2)
00944 
00945 
00946     def _evalString(self, datetimeString, sourceTime=None):
00947         """
00948         Calculate the datetime based on flags set by the L{parse()} routine
00949 
00950         Examples handled::
00951             RFC822, W3CDTF formatted dates
00952             HH:MM[:SS][ am/pm]
00953             MM/DD/YYYY
00954             DD MMMM YYYY
00955 
00956         @type  datetimeString: string
00957         @param datetimeString: text to try and parse as more "traditional"
00958                                date/time text
00959         @type  sourceTime:     struct_time
00960         @param sourceTime:     C{struct_time} value to use as the base
00961 
00962         @rtype:  datetime
00963         @return: calculated C{struct_time} value or current C{struct_time}
00964                  if not parsed
00965         """
00966         s   = datetimeString.strip()
00967         now = time.localtime()
00968 
00969         # Given string date is a RFC822 date
00970         if sourceTime is None:
00971             sourceTime = _parse_date_rfc822(s)
00972 
00973             if sourceTime is not None:
00974                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
00975                 self.dateFlag = 1
00976 
00977                 if (hr != 0) and (mn != 0) and (sec != 0):
00978                     self.timeFlag = 2
00979 
00980                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
00981 
00982         # Given string date is a W3CDTF date
00983         if sourceTime is None:
00984             sourceTime = _parse_date_w3dtf(s)
00985 
00986             if sourceTime is not None:
00987                 self.dateFlag = 1
00988                 self.timeFlag = 2
00989 
00990         if sourceTime is None:
00991             s = s.lower()
00992 
00993         # Given string is in the format HH:MM(:SS)(am/pm)
00994         if self.meridianFlag:
00995             if sourceTime is None:
00996                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
00997             else:
00998                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
00999 
01000             m = self.ptc.CRE_TIMEHMS2.search(s)
01001             if m is not None:
01002                 dt = s[:m.start('meridian')].strip()
01003                 if len(dt) <= 2:
01004                     hr  = int(dt)
01005                     mn  = 0
01006                     sec = 0
01007                 else:
01008                     hr, mn, sec = _extract_time(m)
01009 
01010                 if hr == 24:
01011                     hr = 0
01012 
01013                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
01014                 meridian   = m.group('meridian').lower()
01015 
01016                   # if 'am' found and hour is 12 - force hour to 0 (midnight)
01017                 if (meridian in self.ptc.am) and hr == 12:
01018                     sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
01019 
01020                   # if 'pm' found and hour < 12, add 12 to shift to evening
01021                 if (meridian in self.ptc.pm) and hr < 12:
01022                     sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst)
01023 
01024               # invalid time
01025             if hr > 24 or mn > 59 or sec > 59:
01026                 sourceTime    = now
01027                 self.dateFlag = 0
01028                 self.timeFlag = 0
01029 
01030             self.meridianFlag = False
01031 
01032           # Given string is in the format HH:MM(:SS)
01033         if self.timeStdFlag:
01034             if sourceTime is None:
01035                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
01036             else:
01037                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
01038 
01039             m = self.ptc.CRE_TIMEHMS.search(s)
01040             if m is not None:
01041                 hr, mn, sec = _extract_time(m)
01042             if hr == 24:
01043                 hr = 0
01044 
01045             if hr > 24 or mn > 59 or sec > 59:
01046                 # invalid time
01047                 sourceTime    = now
01048                 self.dateFlag = 0
01049                 self.timeFlag = 0
01050             else:
01051                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
01052 
01053             self.timeStdFlag = False
01054 
01055         # Given string is in the format 07/21/2006
01056         if self.dateStdFlag:
01057             sourceTime       = self.parseDate(s)
01058             self.dateStdFlag = False
01059 
01060         # Given string is in the format  "May 23rd, 2005"
01061         if self.dateStrFlag:
01062             sourceTime       = self.parseDateText(s)
01063             self.dateStrFlag = False
01064 
01065         # Given string is a weekday
01066         if self.weekdyFlag:
01067             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
01068 
01069             start = datetime.datetime(yr, mth, dy, hr, mn, sec)
01070             wkdy  = self.ptc.WeekdayOffsets[s]
01071 
01072             if wkdy > wd:
01073                 qty = self._CalculateDOWDelta(wd, wkdy, 2,
01074                                               self.ptc.DOWParseStyle,
01075                                               self.ptc.CurrentDOWParseStyle)
01076             else:
01077                 qty = self._CalculateDOWDelta(wd, wkdy, 2,
01078                                               self.ptc.DOWParseStyle,
01079                                               self.ptc.CurrentDOWParseStyle)
01080 
01081             target = start + datetime.timedelta(days=qty)
01082             wd     = wkdy
01083 
01084             sourceTime      = target.timetuple()
01085             self.weekdyFlag = False
01086 
01087         # Given string is a natural language time string like
01088         # lunch, midnight, etc
01089         if self.timeStrFlag:
01090             if s in self.ptc.re_values['now']:
01091                 sourceTime = now
01092             else:
01093                 sources = self.ptc.buildSources(sourceTime)
01094 
01095                 if s in sources:
01096                     sourceTime = sources[s]
01097                 else:
01098                     sourceTime    = now
01099                     self.dateFlag = 0
01100                     self.timeFlag = 0
01101 
01102             self.timeStrFlag = False
01103 
01104         # Given string is a natural language date string like today, tomorrow..
01105         if self.dayStrFlag:
01106             if sourceTime is None:
01107                 sourceTime = now
01108 
01109             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
01110 
01111             if s in self.ptc.dayOffsets:
01112                 offset = self.ptc.dayOffsets[s]
01113             else:
01114                 offset = 0
01115 
01116             start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
01117             target     = start + datetime.timedelta(days=offset)
01118             sourceTime = target.timetuple()
01119 
01120             self.dayStrFlag = False
01121 
01122         # Given string is a time string with units like "5 hrs 30 min"
01123         if self.unitsFlag:
01124             modifier = ''  # TODO
01125 
01126             if sourceTime is None:
01127                 sourceTime = now
01128 
01129             m = self.ptc.CRE_UNITS.search(s)
01130             if m is not None:
01131                 units    = m.group('units')
01132                 quantity = s[:m.start('units')]
01133 
01134             sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
01135             self.unitsFlag = False
01136 
01137         # Given string is a time string with single char units like "5 h 30 m"
01138         if self.qunitsFlag:
01139             modifier = ''  # TODO
01140 
01141             if sourceTime is None:
01142                 sourceTime = now
01143 
01144             m = self.ptc.CRE_QUNITS.search(s)
01145             if m is not None:
01146                 units    = m.group('qunits')
01147                 quantity = s[:m.start('qunits')]
01148 
01149             sourceTime      = self._buildTime(sourceTime, quantity, modifier, units)
01150             self.qunitsFlag = False
01151 
01152           # Given string does not match anything
01153         if sourceTime is None:
01154             sourceTime    = now
01155             self.dateFlag = 0
01156             self.timeFlag = 0
01157 
01158         return sourceTime
01159 
01160 
01161     def parse(self, datetimeString, sourceTime=None):
01162         """
01163         Splits the given C{datetimeString} into tokens, finds the regex
01164         patterns that match and then calculates a C{struct_time} value from
01165         the chunks.
01166 
01167         If C{sourceTime} is given then the C{struct_time} value will be
01168         calculated from that value, otherwise from the current date/time.
01169 
01170         If the C{datetimeString} is parsed and date/time value found then
01171         the second item of the returned tuple will be a flag to let you know
01172         what kind of C{struct_time} value is being returned::
01173 
01174             0 = not parsed at all
01175             1 = parsed as a C{date}
01176             2 = parsed as a C{time}
01177             3 = parsed as a C{datetime}
01178 
01179         @type  datetimeString: string
01180         @param datetimeString: date/time text to evaluate
01181         @type  sourceTime:     struct_time
01182         @param sourceTime:     C{struct_time} value to use as the base
01183 
01184         @rtype:  tuple
01185         @return: tuple of: modified C{sourceTime} and the result flag
01186         """
01187 
01188         if sourceTime:
01189             if isinstance(sourceTime, datetime.datetime):
01190                 if _debug:
01191                     print 'coercing datetime to timetuple'
01192                 sourceTime = sourceTime.timetuple()
01193             else:
01194                 if not isinstance(sourceTime, time.struct_time) and \
01195                    not isinstance(sourceTime, tuple):
01196                     raise Exception('sourceTime is not a struct_time')
01197 
01198         s         = datetimeString.strip().lower()
01199         parseStr  = ''
01200         totalTime = sourceTime
01201 
01202         if s == '' :
01203             if sourceTime is not None:
01204                 return (sourceTime, self.dateFlag + self.timeFlag)
01205             else:
01206                 return (time.localtime(), 0)
01207 
01208         self.timeFlag = 0
01209         self.dateFlag = 0
01210 
01211         while len(s) > 0:
01212             flag   = False
01213             chunk1 = ''
01214             chunk2 = ''
01215 
01216             if _debug:
01217                 print 'parse (top of loop): [%s][%s]' % (s, parseStr)
01218 
01219             if parseStr == '':
01220                 # Modifier like next\prev..
01221                 m = self.ptc.CRE_MODIFIER.search(s)
01222                 if m is not None:
01223                     self.modifierFlag = True
01224                     if (m.group('modifier') != s):
01225                         # capture remaining string
01226                         parseStr = m.group('modifier')
01227                         chunk1   = s[:m.start('modifier')].strip()
01228                         chunk2   = s[m.end('modifier'):].strip()
01229                         flag     = True
01230                     else:
01231                         parseStr = s
01232 
01233             if parseStr == '':
01234                 # Modifier like from\after\prior..
01235                 m = self.ptc.CRE_MODIFIER2.search(s)
01236                 if m is not None:
01237                     self.modifier2Flag = True
01238                     if (m.group('modifier') != s):
01239                         # capture remaining string
01240                         parseStr = m.group('modifier')
01241                         chunk1   = s[:m.start('modifier')].strip()
01242                         chunk2   = s[m.end('modifier'):].strip()
01243                         flag     = True
01244                     else:
01245                         parseStr = s
01246 
01247             if parseStr == '':
01248                 valid_date = False
01249                 for match in self.ptc.CRE_DATE3.finditer(s):
01250                     # to prevent "HH:MM(:SS) time strings" expressions from triggering
01251                     # this regex, we checks if the month field exists in the searched 
01252                     # expression, if it doesn't exist, the date field is not valid
01253                     if match.group('mthname'):
01254                         m = self.ptc.CRE_DATE3.search(s, match.start())
01255                         valid_date = True
01256                         break
01257 
01258                 # String date format
01259                 if valid_date:
01260                     self.dateStrFlag = True
01261                     self.dateFlag    = 1
01262                     if (m.group('date') != s):
01263                         # capture remaining string
01264                         parseStr = m.group('date')
01265                         chunk1   = s[:m.start('date')]
01266                         chunk2   = s[m.end('date'):]
01267                         s        = '%s %s' % (chunk1, chunk2)
01268                         flag     = True
01269                     else:
01270                         parseStr = s
01271 
01272             if parseStr == '':
01273                 # Standard date format
01274                 m = self.ptc.CRE_DATE.search(s)
01275                 if m is not None:
01276                     self.dateStdFlag = True
01277                     self.dateFlag    = 1
01278                     if (m.group('date') != s):
01279                         # capture remaining string
01280                         parseStr = m.group('date')
01281                         chunk1   = s[:m.start('date')]
01282                         chunk2   = s[m.end('date'):]
01283                         s        = '%s %s' % (chunk1, chunk2)
01284                         flag     = True
01285                     else:
01286                         parseStr = s
01287 
01288             if parseStr == '':
01289                 # Natural language day strings
01290                 m = self.ptc.CRE_DAY.search(s)
01291                 if m is not None:
01292                     self.dayStrFlag = True
01293                     self.dateFlag   = 1
01294                     if (m.group('day') != s):
01295                         # capture remaining string
01296                         parseStr = m.group('day')
01297                         chunk1   = s[:m.start('day')]
01298                         chunk2   = s[m.end('day'):]
01299                         s        = '%s %s' % (chunk1, chunk2)
01300                         flag     = True
01301                     else:
01302                         parseStr = s
01303 
01304             if parseStr == '':
01305                 # Quantity + Units
01306                 m = self.ptc.CRE_UNITS.search(s)
01307                 if m is not None:
01308                     self.unitsFlag = True
01309                     if (m.group('qty') != s):
01310                         # capture remaining string
01311                         parseStr = m.group('qty')
01312                         chunk1   = s[:m.start('qty')].strip()
01313                         chunk2   = s[m.end('qty'):].strip()
01314 
01315                         if chunk1[-1:] == '-':
01316                             parseStr = '-%s' % parseStr
01317                             chunk1   = chunk1[:-1]
01318 
01319                         s    = '%s %s' % (chunk1, chunk2)
01320                         flag = True
01321                     else:
01322                         parseStr = s
01323 
01324             if parseStr == '':
01325                 # Quantity + Units
01326                 m = self.ptc.CRE_QUNITS.search(s)
01327                 if m is not None:
01328                     self.qunitsFlag = True
01329 
01330                     if (m.group('qty') != s):
01331                         # capture remaining string
01332                         parseStr = m.group('qty')
01333                         chunk1   = s[:m.start('qty')].strip()
01334                         chunk2   = s[m.end('qty'):].strip()
01335 
01336                         if chunk1[-1:] == '-':
01337                             parseStr = '-%s' % parseStr
01338                             chunk1   = chunk1[:-1]
01339 
01340                         s    = '%s %s' % (chunk1, chunk2)
01341                         flag = True
01342                     else:
01343                         parseStr = s 
01344 
01345             if parseStr == '':
01346                 # Weekday
01347                 m = self.ptc.CRE_WEEKDAY.search(s)
01348                 if m is not None:
01349                     gv = m.group('weekday')
01350                     if s not in self.ptc.dayOffsets:
01351                         self.weekdyFlag = True
01352                         self.dateFlag   = 1
01353                         if (gv != s):
01354                             # capture remaining string
01355                             parseStr = gv
01356                             chunk1   = s[:m.start('weekday')]
01357                             chunk2   = s[m.end('weekday'):]
01358                             s        = '%s %s' % (chunk1, chunk2)
01359                             flag     = True
01360                         else:
01361                             parseStr = s
01362 
01363             if parseStr == '':
01364                 # Natural language time strings
01365                 m = self.ptc.CRE_TIME.search(s)
01366                 if m is not None:
01367                     self.timeStrFlag = True
01368                     self.timeFlag    = 2
01369                     if (m.group('time') != s):
01370                         # capture remaining string
01371                         parseStr = m.group('time')
01372                         chunk1   = s[:m.start('time')]
01373                         chunk2   = s[m.end('time'):]
01374                         s        = '%s %s' % (chunk1, chunk2)
01375                         flag     = True
01376                     else:
01377                         parseStr = s
01378 
01379             if parseStr == '':
01380                 # HH:MM(:SS) am/pm time strings
01381                 m = self.ptc.CRE_TIMEHMS2.search(s)
01382                 if m is not None:
01383                     self.meridianFlag = True
01384                     self.timeFlag     = 2
01385                     if m.group('minutes') is not None:
01386                         if m.group('seconds') is not None:
01387                             parseStr = '%s:%s:%s %s' % (m.group('hours'),
01388                                                         m.group('minutes'),
01389                                                         m.group('seconds'),
01390                                                         m.group('meridian'))
01391                         else:
01392                             parseStr = '%s:%s %s' % (m.group('hours'),
01393                                                      m.group('minutes'),
01394                                                      m.group('meridian'))
01395                     else:
01396                         parseStr = '%s %s' % (m.group('hours'),
01397                                               m.group('meridian'))
01398 
01399                     chunk1 = s[:m.start('hours')]
01400                     chunk2 = s[m.end('meridian'):]
01401 
01402                     s    = '%s %s' % (chunk1, chunk2)
01403                     flag = True
01404 
01405             if parseStr == '':
01406                 # HH:MM(:SS) time strings
01407                 m = self.ptc.CRE_TIMEHMS.search(s)
01408                 if m is not None:
01409                     self.timeStdFlag = True
01410                     self.timeFlag    = 2
01411                     if m.group('seconds') is not None:
01412                         parseStr = '%s:%s:%s' % (m.group('hours'),
01413                                                  m.group('minutes'),
01414                                                  m.group('seconds'))
01415                         chunk1   = s[:m.start('hours')]
01416                         chunk2   = s[m.end('seconds'):]
01417                     else:
01418                         parseStr = '%s:%s' % (m.group('hours'),
01419                                               m.group('minutes'))
01420                         chunk1   = s[:m.start('hours')]
01421                         chunk2   = s[m.end('minutes'):]
01422 
01423                     s    = '%s %s' % (chunk1, chunk2)
01424                     flag = True
01425 
01426             # if string does not match any regex, empty string to
01427             # come out of the while loop
01428             if not flag:
01429                 s = ''
01430 
01431             if _debug:
01432                 print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
01433                 print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
01434                        (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
01435                 print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
01436                        (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
01437 
01438             # evaluate the matched string
01439             if parseStr != '':
01440                 if self.modifierFlag == True:
01441                     t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
01442                     # t is the unparsed part of the chunks.
01443                     # If it is not date/time, return current
01444                     # totalTime as it is; else return the output
01445                     # after parsing t.
01446                     if (t != '') and (t != None):
01447                         tempDateFlag       = self.dateFlag
01448                         tempTimeFlag       = self.timeFlag
01449                         (totalTime2, flag) = self.parse(t, totalTime)
01450 
01451                         if flag == 0 and totalTime is not None:
01452                             self.timeFlag = tempTimeFlag
01453                             self.dateFlag = tempDateFlag
01454 
01455                             return (totalTime, self.dateFlag + self.timeFlag)
01456                         else:
01457                             return (totalTime2, self.dateFlag + self.timeFlag)
01458 
01459                 elif self.modifier2Flag == True:
01460                     totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
01461 
01462                     if invalidFlag == True:
01463                         self.dateFlag = 0
01464                         self.timeFlag = 0
01465 
01466                 else:
01467                     totalTime = self._evalString(parseStr, totalTime)
01468                     parseStr  = ''
01469 
01470         # String is not parsed at all
01471         if totalTime is None or totalTime == sourceTime:
01472             totalTime     = time.localtime()
01473             self.dateFlag = 0
01474             self.timeFlag = 0
01475 
01476         return (totalTime, self.dateFlag + self.timeFlag)
01477 
01478 
01479     def inc(self, source, month=None, year=None):
01480         """
01481         Takes the given C{source} date, or current date if none is
01482         passed, and increments it according to the values passed in
01483         by month and/or year.
01484 
01485         This routine is needed because Python's C{timedelta()} function
01486         does not allow for month or year increments.
01487 
01488         @type  source: struct_time
01489         @param source: C{struct_time} value to increment
01490         @type  month:  integer
01491         @param month:  optional number of months to increment
01492         @type  year:   integer
01493         @param year:   optional number of years to increment
01494 
01495         @rtype:  datetime
01496         @return: C{source} incremented by the number of months and/or years
01497         """
01498         yr  = source.year
01499         mth = source.month
01500         dy  = source.day
01501 
01502         if year:
01503             try:
01504                 yi = int(year)
01505             except ValueError:
01506                 yi = 0
01507 
01508             yr += yi
01509 
01510         if month:
01511             try:
01512                 mi = int(month)
01513             except ValueError:
01514                 mi = 0
01515 
01516             m = abs(mi)
01517             y = m / 12      # how many years are in month increment
01518             m = m % 12      # get remaining months
01519 
01520             if mi < 0:
01521                 mth = mth - m           # sub months from start month
01522                 if mth < 1:             # cross start-of-year?
01523                     y   -= 1            #   yes - decrement year
01524                     mth += 12           #         and fix month
01525             else:
01526                 mth = mth + m           # add months to start month
01527                 if mth > 12:            # cross end-of-year?
01528                     y   += 1            #   yes - increment year
01529                     mth -= 12           #         and fix month
01530 
01531             yr += y
01532 
01533             # if the day ends up past the last day of
01534             # the new month, set it to the last day
01535             if dy > self.ptc.daysInMonth(mth, yr):
01536                 dy = self.ptc.daysInMonth(mth, yr)
01537 
01538         d = source.replace(year=yr, month=mth, day=dy)
01539 
01540         return source + (d - source)
01541