parsedatetime.parsedatetime

Package parsedatetime :: Module parsedatetime |
[hide private]
[frames] | no frames]

# Source Code for Module parsedatetime.parsedatetime

       1  #!/usr/bin/env python

       2

       3  """

       4  Parse human-readable date/time text.

       5  """
       6

       7  __license__ = """

       8  Copyright (c) 2004-2007 Mike Taylor

       9  Copyright (c) 2007 Darshana Chhajed

      10  All rights reserved.

      11

      12  Licensed under the Apache License, Version 2.0 (the "License");

      13  you may not use this file except in compliance with the License.

      14  You may obtain a copy of the License at

      15

      16     http://www.apache.org/licenses/LICENSE-2.0

      17

      18  Unless required by applicable law or agreed to in writing, software

      19  distributed under the License is distributed on an "AS IS" BASIS,

      20  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

      21  See the License for the specific language governing permissions and

      22  limitations under the License.

      23  """
      24

      25  _debug = False
      26

      27

      28  import re
      29  import time
      30  import datetime
      31  import rfc822
      32  import parsedatetime_consts
      33

      34

      35  # Copied from feedparser.py

      36  # Universal Feedparser

      37  # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.

      38  # Originally a def inside of _parse_date_w3dtf()

      39 -def _extract_date(m):
      40      year = int(m.group('year'))
      41      if year < 100:
      42          year = 100 * int(time.gmtime()[0] / 100) + int(year)
      43      if year < 1000:
      44          return 0, 0, 0
      45      julian = m.group('julian')
      46      if julian:
      47          julian = int(julian)
      48          month = julian / 30 + 1
      49          day = julian % 30 + 1
      50          jday = None
      51          while jday != julian:
      52              t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
      53              jday = time.gmtime(t)[-2]
      54              diff = abs(jday - julian)
      55              if jday > julian:
      56                  if diff < day:
      57                      day = day - diff
      58                  else:
      59                      month = month - 1
      60                      day = 31
      61              elif jday < julian:
      62                  if day + diff < 28:
      63                      day = day + diff
      64                  else:
      65                      month = month + 1
      66          return year, month, day
      67      month = m.group('month')
      68      day = 1
      69      if month is None:
      70          month = 1
      71      else:
      72          month = int(month)
      73          day = m.group('day')
      74          if day:
      75              day = int(day)
      76          else:
      77              day = 1
      78      return year, month, day
      79

      80  # Copied from feedparser.py

      81  # Universal Feedparser

      82  # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.

      83  # Originally a def inside of _parse_date_w3dtf()

      84 -def _extract_time(m):
      85      if not m:
      86          return 0, 0, 0
      87      hours = m.group('hours')
      88      if not hours:
      89          return 0, 0, 0
      90      hours = int(hours)
      91      minutes = int(m.group('minutes'))
      92      seconds = m.group('seconds')
      93      if seconds:
      94          seconds = int(seconds)
      95      else:
      96          seconds = 0
      97      return hours, minutes, seconds
      98

      99

     100  # Copied from feedparser.py

     101  # Universal Feedparser

     102  # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.

     103  # Modified to return a tuple instead of mktime

     104  #

     105  # Original comment:

     106  #   W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by

     107  #   Drake and licensed under the Python license.  Removed all range checking

     108  #   for month, day, hour, minute, and second, since mktime will normalize

     109  #   these later

     110 -def _parse_date_w3dtf(dateString):
     111      # the __extract_date and __extract_time methods were

     112      # copied-out so they could be used by my code --bear

     113      def __extract_tzd(m):
     114          '''Return the Time Zone Designator as an offset in seconds from UTC.'''
     115          if not m:
     116              return 0
     117          tzd = m.group('tzd')
     118          if not tzd:
     119              return 0
     120          if tzd == 'Z':
     121              return 0
     122          hours = int(m.group('tzdhours'))
     123          minutes = m.group('tzdminutes')
     124          if minutes:
     125              minutes = int(minutes)
     126          else:
     127              minutes = 0
     128          offset = (hours*60 + minutes) * 60
     129          if tzd[0] == '+':
     130              return -offset
     131          return offset
     132

     133      __date_re = ('(?P<year>\d\d\d\d)'

     134                   '(?:(?P<dsep>-|)'

     135                   '(?:(?P<julian>\d\d\d)'

     136                   '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
     137      __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
     138      __tzd_rx = re.compile(__tzd_re)
     139      __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'

     140                   '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'

     141                   + __tzd_re)
     142      __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
     143      __datetime_rx = re.compile(__datetime_re)
     144      m = __datetime_rx.match(dateString)
     145      if (m is None) or (m.group() != dateString): return
     146      return _extract_date(m) + _extract_time(m) + (0, 0, 0)
     147

     148

     149  # Copied from feedparser.py

     150  # Universal Feedparser

     151  # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.

     152  # Modified to return a tuple instead of mktime

     153  #

     154 -def _parse_date_rfc822(dateString):
     155      '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
     156      data = dateString.split()
     157      if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
     158          del data[0]
     159      if len(data) == 4:
     160          s = data[3]
     161          i = s.find('+')
     162          if i > 0:
     163              data[3:] = [s[:i], s[i+1:]]
     164          else:
     165              data.append('')
     166          dateString = " ".join(data)
     167      if len(data) < 5:
     168          dateString += ' 00:00:00 GMT'
     169      return rfc822.parsedate_tz(dateString)
     170

     171  # rfc822.py defines several time zones, but we define some extra ones.

     172  # 'ET' is equivalent to 'EST', etc.

     173  _additional_timezones = {'AT': -400, 'ET': -500,

     174                           'CT': -600, 'MT': -700,

     175                           'PT': -800}
     176  rfc822._timezones.update(_additional_timezones)
     177

     178

     179 -class Calendar:
     180      """

     181      A collection of routines to input, parse and manipulate date and times.

     182      The text can either be 'normal' date values or it can be human readable.

     183      """
     184

     185 -    def __init__(self, constants=None):
     186          """

     187          Default constructor for the L{Calendar} class.

     188

     189          @type  constants: object

     190          @param constants: Instance of the class L{parsedatetime_consts.Constants}

     191

     192          @rtype:  object

     193          @return: L{Calendar} instance

     194          """
     195            # if a constants reference is not included, use default

     196          if constants is None:
     197              self.ptc = parsedatetime_consts.Constants()
     198          else:
     199              self.ptc = constants
     200

     201          self.weekdyFlag    = False  # monday/tuesday/...
     202          self.dateStdFlag   = False  # 07/21/06
     203          self.dateStrFlag   = False  # July 21st, 2006
     204          self.timeStdFlag   = False  # 5:50
     205          self.meridianFlag  = False  # am/pm
     206          self.dayStrFlag    = False  # tomorrow/yesterday/today/..
     207          self.timeStrFlag   = False  # lunch/noon/breakfast/...
     208          self.modifierFlag  = False  # after/before/prev/next/..
     209          self.modifier2Flag = False  # after/before/prev/next/..
     210          self.unitsFlag     = False  # hrs/weeks/yrs/min/..
     211          self.qunitsFlag    = False  # h/m/t/d..
     212

     213          self.timeFlag      = 0
     214          self.dateFlag      = 0
     215

     216

     217 -    def _convertUnitAsWords(self, unitText):
     218          """

     219          Converts text units into their number value

     220

     221          Five = 5

     222          Twenty Five = 25

     223          Two hundred twenty five = 225

     224          Two thousand and twenty five = 2025

     225          Two thousand twenty five = 2025

     226

     227          @type  unitText: string

     228          @param unitText: number text to convert

     229

     230          @rtype:  integer

     231          @return: numerical value of unitText

     232          """
     233          # TODO: implement this

     234          pass
     235

     236

     237 -    def _buildTime(self, source, quantity, modifier, units):
     238          """

     239          Take C{quantity}, C{modifier} and C{unit} strings and convert them into values.

     240          After converting, calcuate the time and return the adjusted sourceTime.

     241

     242          @type  source:   time

     243          @param source:   time to use as the base (or source)

     244          @type  quantity: string

     245          @param quantity: quantity string

     246          @type  modifier: string

     247          @param modifier: how quantity and units modify the source time

     248          @type  units:    string

     249          @param units:    unit of the quantity (i.e. hours, days, months, etc)

     250

     251          @rtype:  struct_time

     252          @return: C{struct_time} of the calculated time

     253          """
     254          if _debug:
     255              print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
     256

     257          if source is None:
     258              source = time.localtime()
     259

     260          if quantity is None:
     261              quantity = ''
     262          else:
     263              quantity = quantity.strip()
     264

     265          if len(quantity) == 0:
     266              qty = 1
     267          else:
     268              try:
     269                  qty = int(quantity)
     270              except ValueError:
     271                  qty = 0
     272

     273          if modifier in self.ptc.Modifiers:
     274              qty = qty * self.ptc.Modifiers[modifier]
     275

     276              if units is None or units == '':
     277                  units = 'dy'
     278

     279          # plurals are handled by regex's (could be a bug tho)

     280

     281          (yr, mth, dy, hr, mn, sec, _, _, _) = source
     282

     283          start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
     284          target = start
     285

     286          if units.startswith('y'):
     287              target        = self.inc(start, year=qty)
     288              self.dateFlag = 1
     289          elif units.endswith('th') or units.endswith('ths'):
     290              target        = self.inc(start, month=qty)
     291              self.dateFlag = 1
     292          else:
     293              if units.startswith('d'):
     294                  target        = start + datetime.timedelta(days=qty)
     295                  self.dateFlag = 1
     296              elif units.startswith('h'):
     297                  target        = start + datetime.timedelta(hours=qty)
     298                  self.timeFlag = 2
     299              elif units.startswith('m'):
     300                  target        = start + datetime.timedelta(minutes=qty)
     301                  self.timeFlag = 2
     302              elif units.startswith('s'):
     303                  target        = start + datetime.timedelta(seconds=qty)
     304                  self.timeFlag = 2
     305              elif units.startswith('w'):
     306                  target        = start + datetime.timedelta(weeks=qty)
     307                  self.dateFlag = 1
     308

     309          return target.timetuple()
     310

     311

     312 -    def parseDate(self, dateString):
     313          """

     314          Parse short-form date strings::

     315

     316              '05/28/2006' or '04.21'

     317

     318          @type  dateString: string

     319          @param dateString: text to convert to a C{datetime}

     320

     321          @rtype:  struct_time

     322          @return: calculated C{struct_time} value of dateString

     323          """
     324          yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
     325

     326          # values pulled from regex's will be stored here and later

     327          # assigned to mth, dy, yr based on information from the locale

     328          # -1 is used as the marker value because we want zero values

     329          # to be passed thru so they can be flagged as errors later

     330          v1 = -1
     331          v2 = -1
     332          v3 = -1
     333

     334          s = dateString
     335          m = self.ptc.CRE_DATE2.search(s)
     336          if m is not None:
     337              index = m.start()
     338              v1    = int(s[:index])
     339              s     = s[index + 1:]
     340

     341          m = self.ptc.CRE_DATE2.search(s)
     342          if m is not None:
     343              index = m.start()
     344              v2    = int(s[:index])
     345              v3    = int(s[index + 1:])
     346          else:
     347              v2 = int(s.strip())
     348

     349          v = [ v1, v2, v3 ]
     350          d = { 'm': mth, 'd': dy, 'y': yr }
     351

     352          for i in range(0, 3):
     353              n = v[i]
     354              c = self.ptc.dp_order[i]
     355              if n >= 0:
     356                  d[c] = n
     357

     358          # if the year is not specified and the date has already

     359          # passed, increment the year

     360          if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
     361              yr = d['y'] + 1
     362          else:
     363              yr  = d['y']
     364

     365          mth = d['m']
     366          dy  = d['d']
     367

     368          # birthday epoch constraint

     369          if yr < self.ptc.BirthdayEpoch:
     370              yr += 2000
     371          elif yr < 100:
     372              yr += 1900
     373

     374          if (mth > 0 and mth <= 12) and \

     375             (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
     376              sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
     377          else:
     378              self.dateFlag = 0
     379              self.timeFlag = 0
     380              sourceTime    = time.localtime() # return current time if date
     381                                               # string is invalid

     382

     383          return sourceTime
     384

     385

     386 -    def parseDateText(self, dateString):
     387          """

     388          Parse long-form date strings::

     389

     390              'May 31st, 2006'

     391              'Jan 1st'

     392              'July 2006'

     393

     394          @type  dateString: string

     395          @param dateString: text to convert to a datetime

     396

     397          @rtype:  struct_time

     398          @return: calculated C{struct_time} value of dateString

     399          """
     400          yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
     401

     402          currentMth = mth
     403          currentDy  = dy
     404

     405          s   = dateString.lower()
     406          m   = self.ptc.CRE_DATE3.search(s)
     407          mth = m.group('mthname')
     408          mth = self.ptc.MonthOffsets[mth]
     409

     410          if m.group('day') !=  None:
     411              dy = int(m.group('day'))
     412          else:
     413              dy = 1
     414

     415          if m.group('year') !=  None:
     416              yr = int(m.group('year'))
     417

     418              # birthday epoch constraint

     419              if yr < self.ptc.BirthdayEpoch:
     420                  yr += 2000
     421              elif yr < 100:
     422                  yr += 1900
     423

     424          elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
     425              # if that day and month have already passed in this year,

     426              # then increment the year by 1

     427              yr += 1
     428

     429          if dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
     430              sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
     431          else:
     432              # Return current time if date string is invalid

     433              self.dateFlag = 0
     434              self.timeFlag = 0
     435              sourceTime    = time.localtime()
     436

     437          return sourceTime
     438

     439

     440 -    def evalRanges(self, datetimeString, sourceTime=None):
     441          """

     442          Evaluate the C{datetimeString} text and determine if

     443          it represents a date or time range.

     444

     445          @type  datetimeString: string

     446          @param datetimeString: datetime text to evaluate

     447          @type  sourceTime:     struct_time

     448          @param sourceTime:     C{struct_time} value to use as the base

     449

     450          @rtype:  tuple

     451          @return: tuple of: start datetime, end datetime and the invalid flag

     452          """
     453          startTime = ''
     454          endTime   = ''
     455          startDate = ''
     456          endDate   = ''
     457          rangeFlag = 0
     458

     459          s = datetimeString.strip().lower()
     460

     461          m = self.ptc.CRE_TIMERNG1.search(s)
     462          if m is not None:
     463              rangeFlag = 1
     464          else:
     465              m = self.ptc.CRE_TIMERNG2.search(s)
     466              if m is not None:
     467                  rangeFlag = 2
     468              else:
     469                  m = self.ptc.CRE_TIMERNG3.search(s)
     470                  if m is not None:
     471                      rangeFlag = 3
     472                  else:
     473                      m = self.ptc.CRE_DATERNG1.search(s)
     474                      if m is not None:
     475                          rangeFlag = 4
     476                      else:
     477                          m = self.ptc.CRE_DATERNG2.search(s)
     478                          if m is not None:
     479                              rangeFlag = 5
     480                          else:
     481                              m = self.ptc.CRE_DATERNG3.search(s)
     482                              if m is not None:
     483                                  rangeFlag = 6
     484

     485          if _debug:
     486              print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
     487

     488          if m is not None:
     489              if (m.group() != s):
     490                  # capture remaining string

     491                  parseStr = m.group()
     492                  chunk1   = s[:m.start()]
     493                  chunk2   = s[m.end():]
     494                  s        = '%s %s' % (chunk1, chunk2)
     495                  flag     = 1
     496

     497                  sourceTime, flag = self.parse(s, sourceTime)
     498

     499                  if flag == 0:
     500                      sourceTime = None
     501              else:
     502                  parseStr = s
     503

     504          if rangeFlag == 1:
     505              m                = re.search(self.ptc.rangeSep, parseStr)
     506              startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
     507              endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
     508

     509              if (eflag != 0)  and (sflag != 0):
     510                  return (startTime, endTime, 2)
     511

     512          elif rangeFlag == 2:
     513              m                = re.search(self.ptc.rangeSep, parseStr)
     514              startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
     515              endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
     516

     517              if (eflag != 0)  and (sflag != 0):
     518                  return (startTime, endTime, 2)
     519

     520          elif rangeFlag == 3:
     521              m = re.search(self.ptc.rangeSep, parseStr)
     522

     523              # capturing the meridian from the end time

     524              if self.ptc.usesMeridian:
     525                  ampm = re.search(self.ptc.am[0], parseStr)
     526

     527                  # appending the meridian to the start time

     528                  if ampm is not None:
     529                      startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime)
     530                  else:
     531                      startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime)
     532              else:
     533                  startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
     534

     535              endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
     536

     537              if (eflag != 0)  and (sflag != 0):
     538                  return (startTime, endTime, 2)
     539

     540          elif rangeFlag == 4:
     541              m                = re.search(self.ptc.rangeSep, parseStr)
     542              startDate, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
     543              endDate, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
     544

     545              if (eflag != 0)  and (sflag != 0):
     546                  return (startDate, endDate, 1)
     547

     548          elif rangeFlag == 5:
     549              m       = re.search(self.ptc.rangeSep, parseStr)
     550              endDate = parseStr[(m.start() + 1):]
     551

     552              # capturing the year from the end date

     553              date    = self.ptc.CRE_DATE3.search(endDate)
     554              endYear = date.group('year')
     555

     556              # appending the year to the start date if the start date

     557              # does not have year information and the end date does.

     558              # eg : "Aug 21 - Sep 4, 2007"

     559              if endYear is not None:
     560                  startDate = (parseStr[:m.start()]).strip()
     561                  date      = self.ptc.CRE_DATE3.search(startDate)
     562                  startYear = date.group('year')
     563

     564                  if startYear is None:
     565                      startDate = startDate + ', ' + endYear
     566              else:
     567                  startDate = parseStr[:m.start()]
     568

     569              startDate, sflag = self.parse(startDate, sourceTime)
     570              endDate, eflag   = self.parse(endDate, sourceTime)
     571

     572              if (eflag != 0)  and (sflag != 0):
     573                  return (startDate, endDate, 1)
     574

     575          elif rangeFlag == 6:
     576              m = re.search(self.ptc.rangeSep, parseStr)
     577

     578              startDate = parseStr[:m.start()]
     579

     580              # capturing the month from the start date

     581              mth = self.ptc.CRE_DATE3.search(startDate)
     582              mth = mth.group('mthname')
     583

     584              # appending the month name to the end date

     585              endDate = mth + parseStr[(m.start() + 1):]
     586

     587              startDate, sflag = self.parse(startDate, sourceTime)
     588              endDate, eflag   = self.parse(endDate, sourceTime)
     589

     590              if (eflag != 0)  and (sflag != 0):
     591                  return (startDate, endDate, 1)
     592          else:
     593              # if range is not found

     594              sourceTime = time.localtime()
     595

     596              return (sourceTime, sourceTime, 0)
     597

     598

     599 -    def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle):
     600          """

     601          Based on the C{style} and C{currentDayStyle} determine what

     602          day-of-week value is to be returned.

     603

     604          @type  wd:              integer

     605          @param wd:              day-of-week value for the current day

     606          @type  wkdy:            integer

     607          @param wkdy:            day-of-week value for the parsed day

     608          @type  offset:          integer

     609          @param offset:          offset direction for any modifiers (-1, 0, 1)

     610          @type  style:           integer

     611          @param style:           normally the value set in C{Constants.DOWParseStyle}

     612          @type  currentDayStyle: integer

     613          @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}

     614

     615          @rtype:  integer

     616          @return: calculated day-of-week

     617          """
     618          if offset == 1:
     619              # modifier is indicating future week eg: "next".

     620              # DOW is calculated as DOW of next week

     621              diff = 7 - wd + wkdy
     622

     623          elif offset == -1:
     624              # modifier is indicating past week eg: "last","previous"

     625              # DOW is calculated as DOW of previous week

     626              diff = wkdy - wd - 7
     627

     628          elif offset == 0:
     629              # modifier is indiacting current week eg: "this"

     630              # DOW is calculated as DOW of this week

     631              diff = wkdy - wd
     632

     633          elif offset == 2:
     634              # no modifier is present.

     635              # i.e. string to be parsed is just DOW

     636              if style == 1:
     637                  # next occurance of the DOW is calculated

     638                  if currentDayStyle == True:
     639                      if wkdy >= wd:
     640                          diff = wkdy - wd
     641                      else:
     642                          diff = 7 - wd + wkdy
     643                  else:
     644                      if wkdy > wd:
     645                          diff = wkdy - wd
     646                      else:
     647                          diff = 7 - wd + wkdy
     648

     649              elif style == -1:
     650                  # last occurance of the DOW is calculated

     651                  if currentDayStyle == True:
     652                      if wkdy <= wd:
     653                          diff = wkdy - wd
     654                      else:
     655                          diff = wkdy - wd - 7
     656                  else:
     657                      if wkdy < wd:
     658                          diff = wkdy - wd
     659                      else:
     660                          diff = wkdy - wd - 7
     661              else:
     662                  # occurance of the DOW in the current week is calculated

     663                  diff = wkdy - wd
     664

     665          if _debug:
     666              print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
     667

     668          return diff
     669

     670

     671 -    def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
     672          """

     673          Evaluate the C{modifier} string and following text (passed in

     674          as C{chunk1} and C{chunk2}) and if they match any known modifiers

     675          calculate the delta and apply it to C{sourceTime}.

     676

     677          @type  modifier:   string

     678          @param modifier:   modifier text to apply to sourceTime

     679          @type  chunk1:     string

     680          @param chunk1:     first text chunk that followed modifier (if any)

     681          @type  chunk2:     string

     682          @param chunk2:     second text chunk that followed modifier (if any)

     683          @type  sourceTime: struct_time

     684          @param sourceTime: C{struct_time} value to use as the base

     685

     686          @rtype:  tuple

     687          @return: tuple of: remaining text and the modified sourceTime

     688          """
     689          offset = self.ptc.Modifiers[modifier]
     690

     691          if sourceTime is not None:
     692              (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
     693          else:
     694              (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
     695

     696          # capture the units after the modifier and the remaining

     697          # string after the unit

     698          m = self.ptc.CRE_REMAINING.search(chunk2)
     699          if m is not None:
     700              index  = m.start() + 1
     701              unit   = chunk2[:m.start()]
     702              chunk2 = chunk2[index:]
     703          else:
     704              unit   = chunk2
     705              chunk2 = ''
     706

     707          flag = False
     708

     709          if unit == 'month' or \

     710             unit == 'mth' or \

     711             unit == 'm':
     712              if offset == 0:
     713                  dy         = self.ptc.DaysInMonthList[mth - 1]
     714                  sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
     715              elif offset == 2:
     716                  # if day is the last day of the month, calculate the last day

     717                  # of the next month

     718                  if dy == self.ptc.DaysInMonthList[mth - 1]:
     719                      dy = self.ptc.DaysInMonthList[mth]
     720

     721                  start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
     722                  target     = self.inc(start, month=1)
     723                  sourceTime = target.timetuple()
     724              else:
     725                  start      = datetime.datetime(yr, mth, 1, 9, 0, 0)
     726                  target     = self.inc(start, month=offset)
     727                  sourceTime = target.timetuple()
     728

     729              flag = True
     730              self.dateFlag = 1
     731

     732          if unit == 'week' or \

     733               unit == 'wk' or \

     734               unit == 'w':
     735              if offset == 0:
     736                  start      = datetime.datetime(yr, mth, dy, 17, 0, 0)
     737                  target     = start + datetime.timedelta(days=(4 - wd))
     738                  sourceTime = target.timetuple()
     739              elif offset == 2:
     740                  start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
     741                  target     = start + datetime.timedelta(days=7)
     742                  sourceTime = target.timetuple()
     743              else:
     744                  return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
     745

     746              flag          = True
     747              self.dateFlag = 1
     748

     749          if unit == 'day' or \

     750              unit == 'dy' or \

     751              unit == 'd':
     752              if offset == 0:
     753                  sourceTime    = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
     754                  self.timeFlag = 2
     755              elif offset == 2:
     756                  start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
     757                  target     = start + datetime.timedelta(days=1)
     758                  sourceTime = target.timetuple()
     759              else:
     760                  start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
     761                  target     = start + datetime.timedelta(days=offset)
     762                  sourceTime = target.timetuple()
     763

     764              flag          = True
     765              self.dateFlag = 1
     766

     767          if unit == 'hour' or \

     768             unit == 'hr':
     769              if offset == 0:
     770                  sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
     771              else:
     772                  start      = datetime.datetime(yr, mth, dy, hr, 0, 0)
     773                  target     = start + datetime.timedelta(hours=offset)
     774                  sourceTime = target.timetuple()
     775

     776              flag          = True
     777              self.timeFlag = 2
     778

     779          if unit == 'year' or \

     780               unit == 'yr' or \

     781               unit == 'y':
     782              if offset == 0:
     783                  sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
     784              elif offset == 2:
     785                  sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
     786              else:
     787                  sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
     788

     789              flag          = True
     790              self.dateFlag = 1
     791

     792          if flag == False:
     793              m = self.ptc.CRE_WEEKDAY.match(unit)
     794              if m is not None:
     795                  wkdy          = m.group()
     796                  self.dateFlag = 1
     797

     798                  if modifier == 'eod':
     799                      # Calculate the  upcoming weekday

     800                      self.modifierFlag = False
     801                      (sourceTime, _)   = self.parse(wkdy, sourceTime)
     802                      sources           = self.ptc.buildSources(sourceTime)
     803                      self.timeFlag     = 2
     804

     805                      if modifier in sources:
     806                          sourceTime = sources[modifier]
     807

     808                  else:
     809                      wkdy       = self.ptc.WeekdayOffsets[wkdy]
     810                      diff       = self._CalculateDOWDelta(wd, wkdy, offset,

     811                                                           self.ptc.DOWParseStyle,

     812                                                           self.ptc.CurrentDOWParseStyle)
     813                      start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
     814                      target     = start + datetime.timedelta(days=diff)
     815                      sourceTime = target.timetuple()
     816

     817                  flag          = True
     818                  self.dateFlag = 1
     819

     820          if not flag:
     821              m = self.ptc.CRE_TIME.match(unit)
     822              if m is not None:
     823                  self.modifierFlag = False
     824                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
     825

     826                  start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
     827                  target     = start + datetime.timedelta(days=offset)
     828                  sourceTime = target.timetuple()
     829                  flag       = True
     830              else:
     831                  self.modifierFlag = False
     832

     833                  # check if the remaining text is parsable and if so,

     834                  # use it as the base time for the modifier source time

     835                  t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
     836

     837                  if flag2 != 0:
     838                      sourceTime = t
     839

     840                  sources = self.ptc.buildSources(sourceTime)
     841

     842                  if modifier in sources:
     843                      sourceTime    = sources[modifier]
     844                      flag          = True
     845                      self.timeFlag = 2
     846

     847          # if the word after next is a number, the string is more than likely

     848          # to be "next 4 hrs" which we will have to combine the units with the

     849          # rest of the string

     850          if not flag:
     851              if offset < 0:
     852                  # if offset is negative, the unit has to be made negative

     853                  unit = '-%s' % unit
     854

     855              chunk2 = '%s %s' % (unit, chunk2)
     856

     857          self.modifierFlag = False
     858

     859          #return '%s %s' % (chunk1, chunk2), sourceTime

     860          return '%s' % chunk2, sourceTime
     861

     862 -    def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
     863          """

     864          Evaluate the C{modifier} string and following text (passed in

     865          as C{chunk1} and C{chunk2}) and if they match any known modifiers

     866          calculate the delta and apply it to C{sourceTime}.

     867

     868          @type  modifier:   string

     869          @param modifier:   modifier text to apply to C{sourceTime}

     870          @type  chunk1:     string

     871          @param chunk1:     first text chunk that followed modifier (if any)

     872          @type  chunk2:     string

     873          @param chunk2:     second text chunk that followed modifier (if any)

     874          @type  sourceTime: struct_time

     875          @param sourceTime: C{struct_time} value to use as the base

     876

     877          @rtype:  tuple

     878          @return: tuple of: remaining text and the modified sourceTime

     879          """
     880          offset = self.ptc.Modifiers[modifier]
     881          digit  = r'\d+'
     882

     883          self.modifier2Flag = False
     884

     885          # If the string after the negative modifier starts with digits,

     886          # then it is likely that the string is similar to ' before 3 days'

     887          # or 'evening prior to 3 days'.

     888          # In this case, the total time is calculated by subtracting '3 days'

     889          # from the current date.

     890          # So, we have to identify the quantity and negate it before parsing

     891          # the string.

     892          # This is not required for strings not starting with digits since the

     893          # string is enough to calculate the sourceTime

     894          if chunk2 != '':
     895              if offset < 0:
     896                  m = re.match(digit, chunk2.strip())
     897                  if m is not None:
     898                      qty    = int(m.group()) * -1
     899                      chunk2 = chunk2[m.end():]
     900                      chunk2 = '%d%s' % (qty, chunk2)
     901

     902              sourceTime, flag1 = self.parse(chunk2, sourceTime)
     903              if flag1 == 0:
     904                  flag1 = True
     905              else:
     906                  flag1 = False
     907              flag2 = False
     908          else:
     909              flag1 = False
     910

     911          if chunk1 != '':
     912              if offset < 0:
     913                  m = re.search(digit, chunk1.strip())
     914                  if m is not None:
     915                      qty    = int(m.group()) * -1
     916                      chunk1 = chunk1[m.end():]
     917                      chunk1 = '%d%s' % (qty, chunk1)
     918

     919              tempDateFlag       = self.dateFlag
     920              tempTimeFlag       = self.timeFlag
     921              sourceTime2, flag2 = self.parse(chunk1, sourceTime)
     922          else:
     923              return sourceTime, (flag1 and flag2)
     924

     925          # if chunk1 is not a datetime and chunk2 is then do not use datetime

     926          # value returned by parsing chunk1

     927          if not (flag1 == False and flag2 == 0):
     928              sourceTime = sourceTime2
     929          else:
     930              self.timeFlag = tempTimeFlag
     931              self.dateFlag = tempDateFlag
     932

     933          return sourceTime, (flag1 and flag2)
     934

     935

     936 -    def _evalString(self, datetimeString, sourceTime=None):
     937          """

     938          Calculate the datetime based on flags set by the L{parse()} routine

     939

     940          Examples handled::

     941              RFC822, W3CDTF formatted dates

     942              HH:MM[:SS][ am/pm]

     943              MM/DD/YYYY

     944              DD MMMM YYYY

     945

     946          @type  datetimeString: string

     947          @param datetimeString: text to try and parse as more "traditional"

     948                                 date/time text

     949          @type  sourceTime:     struct_time

     950          @param sourceTime:     C{struct_time} value to use as the base

     951

     952          @rtype:  datetime

     953          @return: calculated C{struct_time} value or current C{struct_time}

     954                   if not parsed

     955          """
     956          s   = datetimeString.strip()
     957          now = time.localtime()
     958

     959          # Given string date is a RFC822 date

     960          if sourceTime is None:
     961              sourceTime = _parse_date_rfc822(s)
     962

     963              if sourceTime is not None:
     964                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
     965                  self.dateFlag = 1
     966

     967                  if (hr != 0) and (mn != 0) and (sec != 0):
     968                      self.timeFlag = 2
     969

     970                  sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
     971

     972          # Given string date is a W3CDTF date

     973          if sourceTime is None:
     974              sourceTime = _parse_date_w3dtf(s)
     975

     976              if sourceTime is not None:
     977                  self.dateFlag = 1
     978                  self.timeFlag = 2
     979

     980          if sourceTime is None:
     981              s = s.lower()
     982

     983          # Given string is in the format HH:MM(:SS)(am/pm)

     984          if self.meridianFlag:
     985              if sourceTime is None:
     986                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
     987              else:
     988                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
     989

     990              m = self.ptc.CRE_TIMEHMS2.search(s)
     991              if m is not None:
     992                  dt = s[:m.start('meridian')].strip()
     993                  if len(dt) <= 2:
     994                      hr  = int(dt)
     995                      mn  = 0
     996                      sec = 0
     997                  else:
     998                      hr, mn, sec = _extract_time(m)
     999

    1000                  if hr == 24:
    1001                      hr = 0
    1002

    1003                  sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
    1004                  meridian   = m.group('meridian').lower()
    1005

    1006                    # if 'am' found and hour is 12 - force hour to 0 (midnight)

    1007                  if (meridian in self.ptc.am) and hr == 12:
    1008                      sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
    1009

    1010                    # if 'pm' found and hour < 12, add 12 to shift to evening

    1011                  if (meridian in self.ptc.pm) and hr < 12:
    1012                      sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst)
    1013

    1014                # invalid time

    1015              if hr > 24 or mn > 59 or sec > 59:
    1016                  sourceTime    = now
    1017                  self.dateFlag = 0
    1018                  self.timeFlag = 0
    1019

    1020              self.meridianFlag = False
    1021

    1022            # Given string is in the format HH:MM(:SS)

    1023          if self.timeStdFlag:
    1024              if sourceTime is None:
    1025                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
    1026              else:
    1027                  (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
    1028

    1029              m = self.ptc.CRE_TIMEHMS.search(s)
    1030              if m is not None:
    1031                  hr, mn, sec = _extract_time(m)
    1032              if hr == 24:
    1033                  hr = 0
    1034

    1035              if hr > 24 or mn > 59 or sec > 59:
    1036                  # invalid time

    1037                  sourceTime    = now
    1038                  self.dateFlag = 0
    1039                  self.timeFlag = 0
    1040              else:
    1041                  sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
    1042

    1043              self.timeStdFlag = False
    1044

    1045          # Given string is in the format 07/21/2006

    1046          if self.dateStdFlag:
    1047              sourceTime       = self.parseDate(s)
    1048              self.dateStdFlag = False
    1049

    1050          # Given string is in the format  "May 23rd, 2005"

    1051          if self.dateStrFlag:
    1052              sourceTime       = self.parseDateText(s)
    1053              self.dateStrFlag = False
    1054

    1055          # Given string is a weekday

    1056          if self.weekdyFlag:
    1057              (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
    1058

    1059              start = datetime.datetime(yr, mth, dy, hr, mn, sec)
    1060              wkdy  = self.ptc.WeekdayOffsets[s]
    1061

    1062              if wkdy > wd:
    1063                  qty = self._CalculateDOWDelta(wd, wkdy, 2,

    1064                                                self.ptc.DOWParseStyle,

    1065                                                self.ptc.CurrentDOWParseStyle)
    1066              else:
    1067                  qty = self._CalculateDOWDelta(wd, wkdy, 2,

    1068                                                self.ptc.DOWParseStyle,

    1069                                                self.ptc.CurrentDOWParseStyle)
    1070

    1071              target = start + datetime.timedelta(days=qty)
    1072              wd     = wkdy
    1073

    1074              sourceTime      = target.timetuple()
    1075              self.weekdyFlag = False
    1076

    1077          # Given string is a natural language time string like

    1078          # lunch, midnight, etc

    1079          if self.timeStrFlag:
    1080              if s in self.ptc.re_values['now']:
    1081                  sourceTime = now
    1082              else:
    1083                  sources = self.ptc.buildSources(sourceTime)
    1084

    1085                  if s in sources:
    1086                      sourceTime = sources[s]
    1087                  else:
    1088                      sourceTime    = now
    1089                      self.dateFlag = 0
    1090                      self.timeFlag = 0
    1091

    1092              self.timeStrFlag = False
    1093

    1094          # Given string is a natural language date string like today, tomorrow..

    1095          if self.dayStrFlag:
    1096              if sourceTime is None:
    1097                  sourceTime = now
    1098

    1099              (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
    1100

    1101              if s in self.ptc.dayOffsets:
    1102                  offset = self.ptc.dayOffsets[s]
    1103              else:
    1104                  offset = 0
    1105

    1106              start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
    1107              target     = start + datetime.timedelta(days=offset)
    1108              sourceTime = target.timetuple()
    1109

    1110              self.dayStrFlag = False
    1111

    1112          # Given string is a time string with units like "5 hrs 30 min"

    1113          if self.unitsFlag:
    1114              modifier = ''  # TODO
    1115

    1116              if sourceTime is None:
    1117                  sourceTime = now
    1118

    1119              m = self.ptc.CRE_UNITS.search(s)
    1120              if m is not None:
    1121                  units    = m.group('units')
    1122                  quantity = s[:m.start('units')]
    1123

    1124              sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
    1125              self.unitsFlag = False
    1126

    1127          # Given string is a time string with single char units like "5 h 30 m"

    1128          if self.qunitsFlag:
    1129              modifier = ''  # TODO
    1130

    1131              if sourceTime is None:
    1132                  sourceTime = now
    1133

    1134              m = self.ptc.CRE_QUNITS.search(s)
    1135              if m is not None:
    1136                  units    = m.group('qunits')
    1137                  quantity = s[:m.start('qunits')]
    1138

    1139              sourceTime      = self._buildTime(sourceTime, quantity, modifier, units)
    1140              self.qunitsFlag = False
    1141

    1142            # Given string does not match anything

    1143          if sourceTime is None:
    1144              sourceTime    = now
    1145              self.dateFlag = 0
    1146              self.timeFlag = 0
    1147

    1148          return sourceTime
    1149

    1150

    1151 -    def parse(self, datetimeString, sourceTime=None):
    1152          """

    1153          Splits the given C{datetimeString} into tokens, finds the regex

    1154          patterns that match and then calculates a C{struct_time} value from

    1155          the chunks.

    1156

    1157          If C{sourceTime} is given then the C{struct_time} value will be

    1158          calculated from that value, otherwise from the current date/time.

    1159

    1160          If the C{datetimeString} is parsed and date/time value found then

    1161          the second item of the returned tuple will be a flag to let you know

    1162          what kind of C{struct_time} value is being returned::

    1163

    1164              0 = not parsed at all

    1165              1 = parsed as a C{date}

    1166              2 = parsed as a C{time}

    1167              3 = parsed as a C{datetime}

    1168

    1169          @type  datetimeString: string

    1170          @param datetimeString: date/time text to evaluate

    1171          @type  sourceTime:     struct_time

    1172          @param sourceTime:     C{struct_time} value to use as the base

    1173

    1174          @rtype:  tuple

    1175          @return: tuple of: modified C{sourceTime} and the result flag

    1176          """
    1177

    1178          if sourceTime:
    1179              if isinstance(sourceTime, datetime.datetime):
    1180                  if _debug:
    1181                      print 'coercing datetime to timetuple'
    1182                  sourceTime = sourceTime.timetuple()
    1183              else:
    1184                  if not isinstance(sourceTime, time.struct_time) and \

    1185                     not isinstance(sourceTime, tuple):
    1186                      raise Exception('sourceTime is not a struct_time')
    1187

    1188          s         = datetimeString.strip().lower()
    1189          parseStr  = ''
    1190          totalTime = sourceTime
    1191

    1192          if s == '' :
    1193              if sourceTime is not None:
    1194                  return (sourceTime, self.dateFlag + self.timeFlag)
    1195              else:
    1196                  return (time.localtime(), 0)
    1197

    1198          self.timeFlag = 0
    1199          self.dateFlag = 0
    1200

    1201          while len(s) > 0:
    1202              flag   = False
    1203              chunk1 = ''
    1204              chunk2 = ''
    1205

    1206              if _debug:
    1207                  print 'parse (top of loop): [%s][%s]' % (s, parseStr)
    1208

    1209              if parseStr == '':
    1210                  # Modifier like next\prev..

    1211                  m = self.ptc.CRE_MODIFIER.search(s)
    1212                  if m is not None:
    1213                      self.modifierFlag = True
    1214                      if (m.group('modifier') != s):
    1215                          # capture remaining string

    1216                          parseStr = m.group('modifier')
    1217                          chunk1   = s[:m.start('modifier')].strip()
    1218                          chunk2   = s[m.end('modifier'):].strip()
    1219                          flag     = True
    1220                      else:
    1221                          parseStr = s
    1222

    1223              if parseStr == '':
    1224                  # Modifier like from\after\prior..

    1225                  m = self.ptc.CRE_MODIFIER2.search(s)
    1226                  if m is not None:
    1227                      self.modifier2Flag = True
    1228                      if (m.group('modifier') != s):
    1229                          # capture remaining string

    1230                          parseStr = m.group('modifier')
    1231                          chunk1   = s[:m.start('modifier')].strip()
    1232                          chunk2   = s[m.end('modifier'):].strip()
    1233                          flag     = True
    1234                      else:
    1235                          parseStr = s
    1236

    1237              if parseStr == '':
    1238                  # String date format

    1239                  m = self.ptc.CRE_DATE3.search(s)
    1240                  if m is not None:
    1241                      self.dateStrFlag = True
    1242                      self.dateFlag    = 1
    1243                      if (m.group('date') != s):
    1244                          # capture remaining string

    1245                          parseStr = m.group('date')
    1246                          chunk1   = s[:m.start('date')]
    1247                          chunk2   = s[m.end('date'):]
    1248                          s        = '%s %s' % (chunk1, chunk2)
    1249                          flag     = True
    1250                      else:
    1251                          parseStr = s
    1252

    1253              if parseStr == '':
    1254                  # Standard date format

    1255                  m = self.ptc.CRE_DATE.search(s)
    1256                  if m is not None:
    1257                      self.dateStdFlag = True
    1258                      self.dateFlag    = 1
    1259                      if (m.group('date') != s):
    1260                          # capture remaining string

    1261                          parseStr = m.group('date')
    1262                          chunk1   = s[:m.start('date')]
    1263                          chunk2   = s[m.end('date'):]
    1264                          s        = '%s %s' % (chunk1, chunk2)
    1265                          flag     = True
    1266                      else:
    1267                          parseStr = s
    1268

    1269              if parseStr == '':
    1270                  # Natural language day strings

    1271                  m = self.ptc.CRE_DAY.search(s)
    1272                  if m is not None:
    1273                      self.dayStrFlag = True
    1274                      self.dateFlag   = 1
    1275                      if (m.group('day') != s):
    1276                          # capture remaining string

    1277                          parseStr = m.group('day')
    1278                          chunk1   = s[:m.start('day')]
    1279                          chunk2   = s[m.end('day'):]
    1280                          s        = '%s %s' % (chunk1, chunk2)
    1281                          flag     = True
    1282                      else:
    1283                          parseStr = s
    1284

    1285              if parseStr == '':
    1286                  # Quantity + Units

    1287                  m = self.ptc.CRE_UNITS.search(s)
    1288                  if m is not None:
    1289                      self.unitsFlag = True
    1290                      if (m.group('qty') != s):
    1291                          # capture remaining string

    1292                          parseStr = m.group('qty')
    1293                          chunk1   = s[:m.start('qty')].strip()
    1294                          chunk2   = s[m.end('qty'):].strip()
    1295

    1296                          if chunk1[-1:] == '-':
    1297                              parseStr = '-%s' % parseStr
    1298                              chunk1   = chunk1[:-1]
    1299

    1300                          s    = '%s %s' % (chunk1, chunk2)
    1301                          flag = True
    1302                      else:
    1303                          parseStr = s
    1304

    1305              if parseStr == '':
    1306                  # Quantity + Units

    1307                  m = self.ptc.CRE_QUNITS.search(s)
    1308                  if m is not None:
    1309                      self.qunitsFlag = True
    1310

    1311                      if (m.group('qty') != s):
    1312                          # capture remaining string

    1313                          parseStr = m.group('qty')
    1314                          chunk1   = s[:m.start('qty')].strip()
    1315                          chunk2   = s[m.end('qty'):].strip()
    1316

    1317                          if chunk1[-1:] == '-':
    1318                              parseStr = '-%s' % parseStr
    1319                              chunk1   = chunk1[:-1]
    1320

    1321                          s    = '%s %s' % (chunk1, chunk2)
    1322                          flag = True
    1323                      else:
    1324                          parseStr = s
    1325

    1326              if parseStr == '':
    1327                  # Weekday

    1328                  m = self.ptc.CRE_WEEKDAY.search(s)
    1329                  if m is not None:
    1330                      self.weekdyFlag = True
    1331                      self.dateFlag   = 1
    1332                      if (m.group('weekday') != s):
    1333                          # capture remaining string

    1334                          parseStr = m.group('weekday')
    1335                          chunk1   = s[:m.start('weekday')]
    1336                          chunk2   = s[m.end('weekday'):]
    1337                          s        = '%s %s' % (chunk1, chunk2)
    1338                          flag     = True
    1339                      else:
    1340                          parseStr = s
    1341

    1342              if parseStr == '':
    1343                  # Natural language time strings

    1344                  m = self.ptc.CRE_TIME.search(s)
    1345                  if m is not None:
    1346                      self.timeStrFlag = True
    1347                      self.timeFlag    = 2
    1348                      if (m.group('time') != s):
    1349                          # capture remaining string

    1350                          parseStr = m.group('time')
    1351                          chunk1   = s[:m.start('time')]
    1352                          chunk2   = s[m.end('time'):]
    1353                          s        = '%s %s' % (chunk1, chunk2)
    1354                          flag     = True
    1355                      else:
    1356                          parseStr = s
    1357

    1358              if parseStr == '':
    1359                  # HH:MM(:SS) am/pm time strings

    1360                  m = self.ptc.CRE_TIMEHMS2.search(s)
    1361                  if m is not None:
    1362                      self.meridianFlag = True
    1363                      self.timeFlag     = 2
    1364                      if m.group('minutes') is not None:
    1365                          if m.group('seconds') is not None:
    1366                              parseStr = '%s:%s:%s %s' % (m.group('hours'),

    1367                                                          m.group('minutes'),

    1368                                                          m.group('seconds'),

    1369                                                          m.group('meridian'))
    1370                          else:
    1371                              parseStr = '%s:%s %s' % (m.group('hours'),

    1372                                                       m.group('minutes'),

    1373                                                       m.group('meridian'))
    1374                      else:
    1375                          parseStr = '%s %s' % (m.group('hours'),

    1376                                                m.group('meridian'))
    1377

    1378                      chunk1 = s[:m.start('hours')]
    1379                      chunk2 = s[m.end('meridian'):]
    1380

    1381                      s    = '%s %s' % (chunk1, chunk2)
    1382                      flag = True
    1383

    1384              if parseStr == '':
    1385                  # HH:MM(:SS) time strings

    1386                  m = self.ptc.CRE_TIMEHMS.search(s)
    1387                  if m is not None:
    1388                      self.timeStdFlag = True
    1389                      self.timeFlag    = 2
    1390                      if m.group('seconds') is not None:
    1391                          parseStr = '%s:%s:%s' % (m.group('hours'),

    1392                                                   m.group('minutes'),

    1393                                                   m.group('seconds'))
    1394                          chunk1   = s[:m.start('hours')]
    1395                          chunk2   = s[m.end('seconds'):]
    1396                      else:
    1397                          parseStr = '%s:%s' % (m.group('hours'),

    1398                                                m.group('minutes'))
    1399                          chunk1   = s[:m.start('hours')]
    1400                          chunk2   = s[m.end('minutes'):]
    1401

    1402                      s    = '%s %s' % (chunk1, chunk2)
    1403                      flag = True
    1404

    1405              # if string does not match any regex, empty string to

    1406              # come out of the while loop

    1407              if not flag:
    1408                  s = ''
    1409

    1410              if _debug:
    1411                  print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
    1412                  print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \

    1413                         (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
    1414                  print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \

    1415                         (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
    1416

    1417              # evaluate the matched string

    1418              if parseStr != '':
    1419                  if self.modifierFlag == True:
    1420                      t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
    1421                      # t is the unparsed part of the chunks.

    1422                      # If it is not date/time, return current

    1423                      # totalTime as it is; else return the output

    1424                      # after parsing t.

    1425                      if (t != '') and (t != None):
    1426                          tempDateFlag       = self.dateFlag
    1427                          tempTimeFlag       = self.timeFlag
    1428                          (totalTime2, flag) = self.parse(t, totalTime)
    1429

    1430                          if flag == 0 and totalTime is not None:
    1431                              self.timeFlag = tempTimeFlag
    1432                              self.dateFlag = tempDateFlag
    1433

    1434                              return (totalTime, self.dateFlag + self.timeFlag)
    1435                          else:
    1436                              return (totalTime2, self.dateFlag + self.timeFlag)
    1437

    1438                  elif self.modifier2Flag == True:
    1439                      totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
    1440

    1441                      if invalidFlag == True:
    1442                          self.dateFlag = 0
    1443                          self.timeFlag = 0
    1444

    1445                  else:
    1446                      totalTime = self._evalString(parseStr, totalTime)
    1447                      parseStr  = ''
    1448

    1449          # String is not parsed at all

    1450          if totalTime is None or totalTime == sourceTime:
    1451              totalTime     = time.localtime()
    1452              self.dateFlag = 0
    1453              self.timeFlag = 0
    1454

    1455          return (totalTime, self.dateFlag + self.timeFlag)
    1456

    1457

    1458 -    def inc(self, source, month=None, year=None):
    1459          """

    1460          Takes the given C{source} date, or current date if none is

    1461          passed, and increments it according to the values passed in

    1462          by month and/or year.

    1463

    1464          This routine is needed because Python's C{timedelta()} function

    1465          does not allow for month or year increments.

    1466

    1467          @type  source: struct_time

    1468          @param source: C{struct_time} value to increment

    1469          @type  month:  integer

    1470          @param month:  optional number of months to increment

    1471          @type  year:   integer

    1472          @param year:   optional number of years to increment

    1473

    1474          @rtype:  datetime

    1475          @return: C{source} incremented by the number of months and/or years

    1476          """
    1477          yr  = source.year
    1478          mth = source.month
    1479          dy  = source.day
    1480

    1481          if year:
    1482              try:
    1483                  yi = int(year)
    1484              except ValueError:
    1485                  yi = 0
    1486

    1487              yr += yi
    1488

    1489          if month:
    1490              try:
    1491                  mi = int(month)
    1492              except ValueError:
    1493                  mi = 0
    1494

    1495              m = abs(mi)
    1496              y = m / 12      # how many years are in month increment
    1497              m = m % 12      # get remaining months
    1498

    1499              if mi < 0:
    1500                  mth = mth - m           # sub months from start month
    1501                  if mth < 1:             # cross start-of-year?
    1502                      y   -= 1            #   yes - decrement year
    1503                      mth += 12           #         and fix month
    1504              else:
    1505                  mth = mth + m           # add months to start month
    1506                  if mth > 12:            # cross end-of-year?
    1507                      y   += 1            #   yes - increment year
    1508                      mth -= 12           #         and fix month
    1509

    1510              yr += y
    1511

    1512              # if the day ends up past the last day of

    1513              # the new month, set it to the last day

    1514              if dy > self.ptc.DaysInMonthList[mth - 1]:
    1515                  dy = self.ptc.DaysInMonthList[mth - 1]
    1516

    1517          d = source.replace(year=yr, month=mth, day=dy)
    1518

    1519          return source + (d - source)
    1520