changeset 1470:9d82a1c73131

backout of the ) highlight hack
author Franz Pletz <fpletz AT franz-pletz DOT org>
date Wed, 23 Aug 2006 20:42:35 +0200
parents af6c7a0fcd27 (current diff) a0af7d5778de (diff)
children 415e5c22adda
files MoinMoin/search/results.py
diffstat 11 files changed, 2526 insertions(+), 424 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/search/Xapian.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/search/Xapian.py	Wed Aug 23 20:42:35 2006 +0200
@@ -197,7 +197,7 @@
         """ Check if the Xapian index exists """
         return BaseIndex.exists(self) and os.listdir(self.dir)
 
-    def _search(self, query, sort=None):
+    def _search(self, query, sort=None, historysearch=0):
         """ read lock must be acquired """
         while True:
             try:
@@ -217,6 +217,7 @@
             # XXX: we need real weight here, like _moinSearch
             # (TradWeight in xapian)
             kw['sortByRelevence'] = True
+            kw['sortKey'] = 'revision'
         if sort == 'page_name':
             kw['sortKey'] = 'pagename'
 
@@ -468,10 +469,12 @@
                 mimetype, att_content = self.contentfilter(filename)
                 xmimetype = xapdoc.Keyword('mimetype', mimetype)
                 xcontent = xapdoc.TextField('content', att_content)
+                xdomains = [xapdoc.Keyword('domain', domain)
+                        for domain in domains]
                 doc = xapdoc.Document(textFields=(xcontent, ),
-                                      keywords=(xatt_itemid, xtitle,
-                                          xlanguage, xstem_language,
-                                          xmimetype, ),
+                                      keywords=xdomains + [xatt_itemid,
+                                          xtitle, xlanguage, xstem_language,
+                                          xmimetype, ],
                                       sortFields=(xpname, xattachment, xmtime,
                                           xwname, xrev, ),
                                      )
--- a/MoinMoin/search/__init__.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/search/__init__.py	Wed Aug 23 20:42:35 2006 +0200
@@ -19,6 +19,12 @@
     
     @param request: current request
     @param query: the expression (string or query objects) we want to search for
+    @keyword sort: sorting of the search results, either 'weight' or 'page_name'
+    @keyword mtime: only items modified since mtime
+    @keyword historysearch: include older revisions of items in search
+    @keyword titlesearch: treat all terms as title searches (passed to qp)
+    @keyword case: do case sensitive search (passed to qp)
+    @keyword regex: treat all terms as regular expression (passed to qp)
     @rtype: SearchResults instance
     @return: search results
     """
--- a/MoinMoin/search/builtin.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/search/builtin.py	Wed Aug 23 20:42:35 2006 +0200
@@ -28,6 +28,7 @@
         self.readLock = lock.ReadLock(lock_dir, timeout=10.0)
 
     def exists(self):
+        """ Checks if the queue exists on the filesystem """
         return os.path.exists(self.file)
 
     def append(self, pagename):
@@ -136,10 +137,15 @@
                 raise
 
 class BaseIndex:
+    """ Represents a search engine index """
+
     class LockedException(Exception):
         pass
 
     def __init__(self, request):
+        """
+        @param request: current request
+        """
         self.request = request
         cache_dir = request.cfg.cache_dir
         main_dir = self._main_dir()
@@ -166,24 +172,34 @@
         return os.path.exists(self.sig_file)
                 
     def mtime(self):
+        """ Modification time of the index """
         return os.path.getmtime(self.dir)
 
     def touch(self):
+        """ Touch the index """
         os.utime(self.dir, None)
     
     def _search(self, query):
         raise NotImplemented('...')
 
-    def search(self, query, *args, **kw):
+    def search(self, query, **kw):
+        """ Search for items in the index
+        
+        @param query: the query to pass to the index
+        """
         #if not self.read_lock.acquire(1.0):
         #    raise self.LockedException
         #try:
-        hits = self._search(query, *args, **kw)
+        hits = self._search(query, **kw)
         #finally:
         #    self.read_lock.release()
         return hits
 
     def update_page(self, page):
+        """ Update a single page in the index
+
+        @param page: the page object to update
+        """
         self.queue.append(page.page_name)
         self._do_queued_updates_InNewThread()
 
@@ -192,7 +208,8 @@
         
         Can be called only from a script. To index pages during a user
         request, use indexPagesInNewThread.
-        @arg files: iterator or list of files to index additionally
+        @keyword files: iterator or list of files to index additionally
+        @keyword mode: set the mode of indexing the pages, either 'update', 'add' or 'rebuild'
         """
         if not self.lock.acquire(1.0):
             self.request.log("can't index: can't acquire lock")
@@ -242,6 +259,11 @@
 
         When called in a new thread, lock is acquired before the call,
         and this method must release it when it finishes or fails.
+
+        @param request: current request
+        @keyword files: iterator or list of files to index additionally
+        @keyword mode: set the mode of indexing the pages, either 'update',
+        'add' or 'rebuild'
         """
         raise NotImplemented('...')
 
@@ -286,6 +308,7 @@
         raise NotImplemented('...')
 
     def optimize(self):
+        """ Optimize the the index if possible """
         raise NotImplemented('...')
 
     def contentfilter(self, filename):
@@ -310,15 +333,14 @@
             request.log("Filter %s threw error '%s' for file %s" % (modulename, str(err), filename))
         return mt.mime_type(), data
 
-    def test(self, request):
-        raise NotImplemented('...')
-
     def _indexingRequest(self, request):
         """ Return a new request that can be used for index building.
         
         This request uses a security policy that lets the current user
         read any page. Without this policy some pages will not render,
-        which will create broken pagelinks index.        
+        which will create broken pagelinks index.
+
+        @param request: current request
         """
         from MoinMoin.request.CLI import Request
         from MoinMoin.security import Permissions
@@ -394,6 +416,10 @@
     # Private!
 
     def _xapianIndex(request):
+        """ Get the xapian index if possible
+
+        @param request: current request
+        """
         try:
             from MoinMoin.search.Xapian import Index
             index = Index(request)
@@ -409,32 +435,35 @@
         """ Search using Xapian
         
         Get a list of pages using fast xapian search and
-        return moin search in those pages.
+        return moin search in those pages if needed.
         """
         clock = self.request.clock
         pages = None
         index = self._xapianIndex(self.request)
+
         if index and self.query.xapian_wanted():
             clock.start('_xapianSearch')
             try:
                 from MoinMoin.support import xapwrap
+
                 clock.start('_xapianQuery')
                 query = self.query.xapian_term(self.request, index.allterms)
                 self.request.log("xapianSearch: query = %r" %
                         query.get_description())
                 query = xapwrap.index.QObjQuery(query)
-                enq, mset, hits = index.search(query, sort=self.sort)
+                enq, mset, hits = index.search(query, sort=self.sort,
+                        historysearch=self.historysearch)
                 clock.stop('_xapianQuery')
+
                 #self.request.log("xapianSearch: finds: %r" % hits)
                 def dict_decode(d):
                     """ decode dict values to unicode """
                     for k, v in d.items():
                         d[k] = d[k].decode(config.charset)
                     return d
-                #pages = [{'uid': hit['uid'], 'values': dict_decode(hit['values'])}
-                #        for hit in hits]
                 pages = [dict_decode(hit['values']) for hit in hits]
                 self.request.log("xapianSearch: finds pages: %r" % pages)
+                
                 self._xapianEnquire = enq
                 self._xapianMset = mset
                 self._xapianIndex = index
@@ -444,6 +473,7 @@
             #    pages = []
 
             try:
+                # xapian handled the full query
                 if not self.query.xapian_need_postproc():
                     clock.start('_xapianProcess')
                     try:
@@ -456,21 +486,30 @@
             # we didn't use xapian in this request
             self.request.cfg.xapian_search = 0
         
+        # some postprocessing by _moinSearch is required
         return self._moinSearch(pages)
 
     def _xapianMatchDecider(self, term, pos):
+        """ Returns correct Match object for a Xapian match
+        
+        @param term: the term as string
+        @param pos: starting position of the match
+        """
         if term[0] == 'S':      # TitleMatch
             return TitleMatch(start=pos, end=pos+len(term)-1)
         else:                   # TextMatch (incl. headers)
             return TextMatch(start=pos, end=pos+len(term))
         
-    def _xapianMatch(self, page, uid):
-        """ Get all relevant Xapian matches per document id """
+    def _xapianMatch(self, uid, page=None):
+        """ Get all relevant Xapian matches per document id
+        
+        @param uid: the id of the document in the xapian index
+        """
         positions = {}
         term = self._xapianEnquire.get_matching_terms_begin(uid)
         while term != self._xapianEnquire.get_matching_terms_end(uid):
             term_name = term.get_term()
-            for pos in self._xapianIndex.termpositions(uid,term.get_term()):
+            for pos in self._xapianIndex.termpositions(uid, term.get_term()):
                 if pos not in positions or \
                         len(positions[pos]) < len(term_name):
                     positions[pos] = term_name
@@ -488,6 +527,8 @@
         
         Return list of tuples (page, match). The list may contain
         deleted pages or pages the user may not read.
+
+        @keyword pages: optional list of pages to search in
         """
         self.request.clock.start('_moinSearch')
         from MoinMoin.Page import Page
@@ -499,13 +540,17 @@
         self.request.clock.stop('_moinSearch')
         return hits
     
-    def _moinMatch(self, page, uid):
-        """ Just kick off regular moinSearch """
+    def _moinMatch(self, page, uid=None):
+        """ Get all matches from regular moinSearch
+        
+        @param page: the current page instance
+        """
         return self.query.search(page)
 
     def _getHits(self, pages, matchSearchFunction):
         """ Get the hit tuples in pages through matchSearchFunction """
         hits = []
+        revisionCache = {}
         fs_rootpage = self.fs_rootpage
         for hit in pages:
             if 'values' in hit:
@@ -536,9 +581,15 @@
                     else:
                         hits.append((wikiname, page, attachment, None))
                 else:
-                    matches = matchSearchFunction(page, uid)
+                    matches = matchSearchFunction(page=page, uid=uid)
                     if matches:
+                        if not self.historysearch and \
+                                pagename in revisionCache and \
+                                revisionCache[pagename][0] < revision:
+                            hits.remove(revisionCache[pagename][1])
+                            del revisionCache[pagename]
                         hits.append((wikiname, page, attachment, matches))
+                        revisionCache[pagename] = (revision, hits[-1])
             else: # other wiki
                 hits.append((wikiname, pagename, attachment, None, revision))
         return hits
@@ -560,7 +611,10 @@
             return self.request.rootpage.getPageList(user='', exists=0)
         
     def _filter(self, hits):
-        """ Filter out deleted or acl protected pages """
+        """ Filter out deleted or acl protected pages
+        
+        @param hits: list of hits
+        """
         userMayRead = self.request.user.may.read
         fs_rootpage = self.fs_rootpage + "/"
         thiswiki = (self.request.cfg.interwikiname, 'Self')
--- a/MoinMoin/search/results.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/search/results.py	Wed Aug 23 20:42:35 2006 +0200
@@ -733,6 +733,7 @@
     def formatHitInfoBar(self, page):
         f = self.formatter
         _ = self.request.getText
+        request = self.request
 
         rev = page.page.get_real_rev()
         if rev is None:
@@ -744,7 +745,7 @@
             f.text('rev: %d %s- ' % (rev,
                 rev == page.page.getRevList()[0] and 
                 '(%s) ' % _('current') or '')),
-            f.text('last modified: %(time)s' % page.page.lastEditInfo()),
+            f.text('last modified: %s' % page.page.mtime_printable(request)),
             # XXX: proper metadata
             #f.text('lang: %s - ' % page.page.language),
             #f.url(1, href='#'),
--- a/MoinMoin/support/parsedatetime/__init__.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/support/parsedatetime/__init__.py	Wed Aug 23 20:42:35 2006 +0200
@@ -1,4 +1,4 @@
-version = '0.6.4'
+version = '0.7'
 author  = 'Mike Taylor <http://code-bear.com>'
 license = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
 
--- a/MoinMoin/support/parsedatetime/parsedatetime.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/support/parsedatetime/parsedatetime.py	Wed Aug 23 20:42:35 2006 +0200
@@ -186,7 +186,7 @@
         """
           # if a constants reference is not included, use default
         if constants is None:
-            self.ptc = parsedatetime_consts.CalendarConstants()
+            self.ptc = parsedatetime_consts.Constants()
         else:
             self.ptc = constants
 
@@ -206,18 +206,31 @@
         self.CRE_TIME      = re.compile(self.ptc.RE_TIME,      re.IGNORECASE)
         self.CRE_REMAINING = re.compile(self.ptc.RE_REMAINING, re.IGNORECASE)
 
-        self.invalidFlag   = 0  # Is set if the datetime string entered cannot be parsed at all
-        self.weekdyFlag    = 0  # monday/tuesday/...
-        self.dateStdFlag   = 0  # 07/21/06
-        self.dateStrFlag   = 0  # July 21st, 2006
-        self.timeFlag      = 0  # 5:50 
-        self.meridianFlag  = 0  # am/pm
-        self.dayStrFlag    = 0  # tomorrow/yesterday/today/..
-        self.timeStrFlag   = 0  # lunch/noon/breakfast/...
-        self.modifierFlag  = 0  # after/before/prev/next/..
-        self.modifier2Flag = 0  # after/before/prev/next/..
-        self.unitsFlag     = 0  # hrs/weeks/yrs/min/..
-        self.qunitsFlag    = 0  # h/m/t/d..
+        #regex for date/time ranges
+        self.CRE_RTIMEHMS  = re.compile(self.ptc.RE_RTIMEHMS,  re.IGNORECASE)
+        self.CRE_RTIMEHMS2 = re.compile(self.ptc.RE_RTIMEHMS2, re.IGNORECASE)
+        self.CRE_RDATE     = re.compile(self.ptc.RE_RDATE,     re.IGNORECASE)
+        self.CRE_RDATE3    = re.compile(self.ptc.RE_RDATE3,    re.IGNORECASE)
+
+        self.CRE_TIMERNG1  = re.compile(self.ptc.TIMERNG1, re.IGNORECASE)
+        self.CRE_TIMERNG2  = re.compile(self.ptc.TIMERNG2, re.IGNORECASE)
+        self.CRE_TIMERNG3  = re.compile(self.ptc.TIMERNG3, re.IGNORECASE)
+        self.CRE_DATERNG1  = re.compile(self.ptc.DATERNG1, re.IGNORECASE)
+        self.CRE_DATERNG2  = re.compile(self.ptc.DATERNG2, re.IGNORECASE)
+        self.CRE_DATERNG3  = re.compile(self.ptc.DATERNG3, re.IGNORECASE)
+
+        self.invalidFlag   = False  # Is set if the datetime string entered cannot be parsed at all
+        self.weekdyFlag    = False  # monday/tuesday/...
+        self.dateStdFlag   = False  # 07/21/06
+        self.dateStrFlag   = False  # July 21st, 2006
+        self.timeFlag      = False  # 5:50 
+        self.meridianFlag  = False  # am/pm
+        self.dayStrFlag    = False  # tomorrow/yesterday/today/..
+        self.timeStrFlag   = False  # lunch/noon/breakfast/...
+        self.modifierFlag  = False  # after/before/prev/next/..
+        self.modifier2Flag = False  # after/before/prev/next/..
+        self.unitsFlag     = False  # hrs/weeks/yrs/min/..
+        self.qunitsFlag    = False  # h/m/t/d..
 
 
     def _convertUnitAsWords(self, unitText):
@@ -284,11 +297,6 @@
 
         # plurals are handled by regex's (could be a bug tho)
 
-        if units in self.ptc.Units:
-            u = self.ptc.Units[units]
-        else:
-            u = 1
-
         (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
 
         start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
@@ -311,7 +319,7 @@
                 target = start + datetime.timedelta(weeks=qty)
 
         if target != start:
-            self.invalidFlag = 0
+            self.invalidFlag = False
 
         return target.timetuple()
 
@@ -328,9 +336,6 @@
         """
         yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
-        # XXX: Quick fix to ignore 'the'
-        dateString = dateString.replace('the', '')
-
         s = dateString
         m = self.CRE_DATE2.search(s)
         if m is not None:
@@ -349,10 +354,10 @@
         else:
             dy = int(string.strip(s))
 
-        if mth <= 12 and dy <= self.ptc.DaysInMonthList[mth - 1]:
+        if (mth > 0 and mth <= 12) and (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
             sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
         else:
-            self.invalidFlag = 1
+            self.invalidFlag = True
             sourceTime       = time.localtime() #return current time if date string is invalid
 
         return sourceTime
@@ -376,7 +381,7 @@
         s   = dateString.lower()
         m   = self.CRE_DATE3.search(s)
         mth = m.group('mthname')
-        mth = int(self.ptc.MthNames[mth])
+        mth = self.ptc.MonthOffsets[mth]
 
         if m.group('day') !=  None:
             dy = int(m.group('day'))
@@ -390,28 +395,193 @@
             # then increment the year by 1
             yr += 1
 
-        if dy <= self.ptc.DaysInMonthList[mth - 1]:
+        if dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
             sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
         else:
               # Return current time if date string is invalid
-            self.invalidFlag = 1
+            self.invalidFlag = True
             sourceTime       = time.localtime()
 
         return sourceTime
 
 
+    def evalRanges(self, datetimeString, sourceTime=None):
+        """
+        Evaluates the strings with time or date ranges
+
+        @type  datetimeString: string
+        @param datetimeString: datetime text to evaluate
+        @type  sourceTime:     datetime
+        @param sourceTime:     datetime value to use as the base
+
+        @rtype:  tuple
+        @return: tuple of the start datetime, end datetime and the invalid flag
+        """
+        startTime = ''
+        endTime   = ''
+        startDate = ''
+        endDate   = ''
+        rangeFlag = 0
+
+        s = string.strip(datetimeString.lower())
+
+        m = self.CRE_TIMERNG1.search(s)
+        if m is not None:
+            rangeFlag = 1
+        else:
+            m = self.CRE_TIMERNG2.search(s)
+            if m is not None:
+                rangeFlag = 2
+            else:
+                m = self.CRE_TIMERNG3.search(s)
+                if m is not None:
+                    rangeFlag = 3
+                else:
+                    m = self.CRE_DATERNG1.search(s)
+                    if m is not None:
+                        rangeFlag = 4
+                    else:
+                        m = self.CRE_DATERNG2.search(s)
+                        if m is not None:
+                            rangeFlag = 5
+                        else:
+                            m = self.CRE_DATERNG3.search(s)
+                            if m is not None:
+                                rangeFlag = 6
+
+        if _debug:
+            print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
+
+        if m is not None:
+            if (m.group() != s):
+                # capture remaining string
+                parseStr = m.group()
+                chunk1   = s[:m.start()]
+                chunk2   = s[m.end():]
+                s        = '%s %s' % (chunk1, chunk2)
+                flag     = 1
+
+                sourceTime, flag = self.parse(s, sourceTime)
+
+                if flag == True:
+                    sourceTime = None
+            else:
+                parseStr = s
+
+        if rangeFlag == 1:
+            # FIXME hardcoded seperator
+            m                = re.search('-', parseStr)
+            startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
+            endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 2:
+            # FIXME hardcoded seperator
+            m                = re.search('-', parseStr)
+            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+            endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 3:
+            # FIXME hardcoded seperator
+            m = re.search('-', parseStr)
+
+            # capturing the meridian from the end time
+            # FIXME hardcoded meridian
+            if self.ptc.usesMeridian:
+                ampm = re.search('a', parseStr)
+
+                # appending the meridian to the start time
+                if ampm is not None:
+                    startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime)
+                else:
+                    startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime)
+            else:
+                startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+
+            endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 4:
+            # FIXME hardcoded seperator
+            m                = re.search('-', parseStr)
+            startDate, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
+            endDate, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+
+        elif rangeFlag == 5:
+            # FIXME hardcoded seperator
+            m       = re.search('-', parseStr)
+            endDate = parseStr[(m.start() + 1):]
+
+            # capturing the year from the end date
+            date    = self.CRE_DATE3.search(endDate)
+            endYear = date.group('year')
+
+            # appending the year to the start date if the start date
+            # does not have year information and the end date does.
+            # eg : "Aug 21 - Sep 4, 2007"
+            if endYear is not None:
+                startDate = parseStr[:m.start()]
+                date      = self.CRE_DATE3.search(startDate)
+                startYear = date.group('year')
+
+                if startYear is None:
+                    startDate += endYear
+            else:
+                startDate = parseStr[:m.start()]
+
+            startDate, sflag = self.parse(startDate, sourceTime)
+            endDate, eflag   = self.parse(endDate, sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+
+        elif rangeFlag == 6:
+            # FIXME hardcoded seperator
+            m = re.search('-', parseStr)
+
+            startDate = parseStr[:m.start()]
+
+            # capturing the month from the start date
+            mth = self.CRE_DATE3.search(startDate)
+            mth = mth.group('mthname')
+
+            # appending the month name to the end date
+            endDate = mth + parseStr[(m.start() + 1):]
+
+            startDate, sflag = self.parse(startDate, sourceTime)
+            endDate, eflag   = self.parse(endDate, sourceTime)
+
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+        else:
+            # if range is not found
+            sourceTime = time.localtime()
+
+            return (sourceTime, sourceTime, True)
+
+
     def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
         """
         Evaluate the modifier string and following text (passed in
         as chunk1 and chunk2) and if they match any known modifiers
         calculate the delta and apply it to sourceTime
 
-        @type  modifier: string
-        @param modifier: modifier text to apply to sourceTime
-        @type  chunk1:   string
-        @param chunk1:   first text chunk that followed modifier (if any)
-        @type  chunk2:   string
-        @param chunk2:   second text chunk that followed modifier (if any)
+        @type  modifier:   string
+        @param modifier:   modifier text to apply to sourceTime
+        @type  chunk1:     string
+        @param chunk1:     first text chunk that followed modifier (if any)
+        @type  chunk2:     string
+        @param chunk2:     second text chunk that followed modifier (if any)
         @type  sourceTime: datetime
         @param sourceTime: datetime value to use as the base
 
@@ -435,12 +605,12 @@
             unit   = chunk2
             chunk2 = ''
 
-        flag = 0
+        flag = False
 
-        if unit == self.ptc.Target_Text['month'] or \
-           unit == self.ptc.Target_Text['mth']:
+        if unit == 'month' or \
+           unit == 'mth':
             if offset == 0:
-                dy        = self.ptc.DaysInMonthList[mth - 1]
+                dy         = self.ptc.DaysInMonthList[mth - 1]
                 sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
             elif offset == 2:
                 # if day is the last day of the month, calculate the last day of the next month
@@ -455,11 +625,11 @@
                 target     = self.inc(start, month=offset)
                 sourceTime = target.timetuple()
 
-            flag = 1
+            flag = True
 
-        if unit == self.ptc.Target_Text['week'] or \
-             unit == self.ptc.Target_Text['wk'] or \
-             unit == self.ptc.Target_Text['w']:
+        if unit == 'week' or \
+             unit == 'wk' or \
+             unit == 'w':
             if offset == 0:
                 start      = datetime.datetime(yr, mth, dy, 17, 0, 0)
                 target     = start + datetime.timedelta(days=(4 - wd))
@@ -471,11 +641,11 @@
             else:
                 return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
 
-            flag = 1
+            flag = True
 
-        if unit == self.ptc.Target_Text['day'] or \
-            unit == self.ptc.Target_Text['dy'] or \
-            unit == self.ptc.Target_Text['d']:
+        if unit == 'day' or \
+            unit == 'dy' or \
+            unit == 'd':
             if offset == 0:
                 sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
             elif offset == 2:
@@ -487,10 +657,10 @@
                 target     = start + datetime.timedelta(days=offset)
                 sourceTime = target.timetuple()
 
-            flag = 1
+            flag = True
 
-        if unit == self.ptc.Target_Text['hour'] or \
-           unit == self.ptc.Target_Text['hr']:
+        if unit == 'hour' or \
+           unit == 'hr':
             if offset == 0:
                 sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
             else:
@@ -498,11 +668,11 @@
                 target     = start + datetime.timedelta(hours=offset)
                 sourceTime = target.timetuple()
 
-            flag = 1
+            flag = True
 
-        if unit == self.ptc.Target_Text['year'] or \
-             unit == self.ptc.Target_Text['yr'] or \
-             unit == self.ptc.Target_Text['y']:
+        if unit == 'year' or \
+             unit == 'yr' or \
+             unit == 'y':
             if offset == 0:
                 sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
             elif offset == 2:
@@ -510,13 +680,13 @@
             else:
                 sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
 
-            flag = 1
+            flag = True
 
-        if flag == 0:
+        if flag == False:
             m = self.CRE_WEEKDAY.match(unit)
             if m is not None:
                 wkdy = m.group()
-                wkdy = self.ptc.WeekDays[wkdy]
+                wkdy = self.ptc.WeekdayOffsets[wkdy]
 
                 if offset == 0:
                     diff       = wkdy - wd
@@ -529,9 +699,9 @@
                     target     = start + datetime.timedelta(days=diff + 7 * offset)
                     sourceTime = target.timetuple()
 
-                flag = 1
+                flag = True
 
-        if flag == 0:
+        if not flag:
             m = self.CRE_TIME.match(unit)
             if m is not None:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
@@ -539,20 +709,20 @@
                 target     = start + datetime.timedelta(days=offset)
                 sourceTime = target.timetuple()
 
-                flag              = 1
-                self.modifierFlag = 0
+                flag              = True
+                self.modifierFlag = False
 
         # if the word after next is a number, the string is likely
         # to be something like "next 4 hrs" for which we have to
         # combine the units with the rest of the string
-        if flag == 0:
+        if not flag:
             if offset < 0:
                 # if offset is negative, the unit has to be made negative
                 unit = '-%s' % unit
 
             chunk2 = '%s %s' % (unit, chunk2)
 
-        self.modifierFlag = 0
+        self.modifierFlag = False
 
         return '%s %s' % (chunk1, chunk2), sourceTime
 
@@ -583,7 +753,7 @@
         else:
             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
-        self.modifier2Flag = 0
+        self.modifier2Flag = False
 
         # If the string after the negative modifier starts with
         # digits, then it is likely that the string is similar to
@@ -634,7 +804,8 @@
         @rtype:  datetime
         @return: calculated datetime value or current datetime if not parsed
         """
-        s = string.strip(datetimeString)
+        s   = string.strip(datetimeString)
+        now = time.localtime()
 
           # Given string date is a RFC822 date
         if sourceTime is None:
@@ -648,9 +819,9 @@
             s = s.lower()
 
           # Given string is in the format HH:MM(:SS)(am/pm)
-        if self.meridianFlag == 1:
+        if self.meridianFlag:
             if sourceTime is None:
-                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
             else:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
@@ -668,24 +839,27 @@
                     hr = 0
 
                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
-                meridian   = m.group('meridian')
+                meridian   = m.group('meridian').lower()
 
-                if (re.compile("a").search(meridian)) and hr == 12:
+                  # if 'am' found and hour is 12 - force hour to 0 (midnight)
+                if (meridian in self.ptc.am) and hr == 12:
                     sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
-                if (re.compile("p").search(meridian)) and hr < 12:
-                    sourceTime = (yr, mth, dy, hr+12, mn, sec, wd, yd, isdst)
+
+                  # if 'pm' found and hour < 12, add 12 to shift to evening
+                if (meridian in self.ptc.pm) and hr < 12:
+                    sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst)
 
               # invalid time
             if hr > 24 or mn > 59 or sec > 59:
-                sourceTime       = time.localtime()
-                self.invalidFlag = 1
+                sourceTime       = now
+                self.invalidFlag = True
 
-            self.meridianFlag = 0
+            self.meridianFlag = False
 
           # Given string is in the format HH:MM(:SS)
-        if self.timeFlag == 1:
+        if self.timeFlag:
             if sourceTime is None:
-                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
             else:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
@@ -697,28 +871,29 @@
 
             if hr > 24 or mn > 59 or sec > 59:
                 # invalid time
-                sourceTime = time.localtime()
-                self.invalidFlag = 1
+                sourceTime       = now
+                self.invalidFlag = True
             else:
                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
-            self.timeFlag = 0
+            self.timeFlag = False
 
           # Given string is in the format 07/21/2006
-        if self.dateStdFlag == 1:
+        if self.dateStdFlag:
             sourceTime       = self.parseDate(s)
-            self.dateStdFlag = 0
+            self.dateStdFlag = False
 
           # Given string is in the format  "May 23rd, 2005"
-        if self.dateStrFlag == 1:
+        if self.dateStrFlag:
             sourceTime       = self.parseDateText(s)
-            self.dateStrFlag = 0
+            self.dateStrFlag = False
 
           # Given string is a weekday
-        if self.weekdyFlag == 1:
-            yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
+        if self.weekdyFlag:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
+
             start = datetime.datetime(yr, mth, dy, hr, mn, sec)
-            wkDy  = self.ptc.WeekDays[s]
+            wkDy  = self.ptc.WeekdayOffsets[s]
 
             if wkDy > wd:
                 qty    = wkDy - wd
@@ -730,59 +905,47 @@
                 wd     = wkDy
 
             sourceTime      = target.timetuple()
-            self.weekdyFlag = 0
+            self.weekdyFlag = False
 
           # Given string is a natural language time string like lunch, midnight, etc
-        if self.timeStrFlag == 1:
-            if sourceTime is None:
-                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+        if self.timeStrFlag:
+            if s in self.ptc.re_values['now']:
+                sourceTime = now
             else:
-                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+                sources = self.ptc.buildSources(now)
 
-            sources = { 'now':       (yr, mth, dy, hr, mn, sec, wd, yd, isdst),
-                        'noon':      (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
-                        'lunch':     (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
-                        'morning':   (yr, mth, dy,  6,  0,   0, wd, yd, isdst),
-                        'breakfast': (yr, mth, dy,  8,  0,   0, wd, yd, isdst),
-                        'dinner':    (yr, mth, dy, 19,  0,   0, wd, yd, isdst),
-                        'evening':   (yr, mth, dy, 18,  0,   0, wd, yd, isdst),
-                        'midnight':  (yr, mth, dy,  0,  0,   0, wd, yd, isdst),
-                        'night':     (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
-                        'tonight':   (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
-                      }
+                if s in sources:
+                    sourceTime = sources[s]
+                else:
+                    sourceTime       = now
+                    self.invalidFlag = True
 
-            if s in sources:
-                sourceTime = sources[s]
-            else:
-                sourceTime       = time.localtime()
-                self.invalidFlag = 1
-
-            self.timeStrFlag = 0
+            self.timeStrFlag = False
 
            # Given string is a natural language date string like today, tomorrow..
-        if self.dayStrFlag == 1:
+        if self.dayStrFlag:
             if sourceTime is None:
-                sourceTime = time.localtime()
+                sourceTime = now
 
             (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
-            sources = { 'tomorrow':   1,
-                        'today':      0,
-                        'yesterday': -1,
-                       }
+            if s in self.ptc.dayOffsets:
+                offset = self.ptc.dayOffsets[s]
+            else:
+                offset = 0
 
             start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
-            target     = start + datetime.timedelta(days=sources[s])
+            target     = start + datetime.timedelta(days=offset)
             sourceTime = target.timetuple()
 
-            self.dayStrFlag = 0
+            self.dayStrFlag = False
 
           # Given string is a time string with units like "5 hrs 30 min"
-        if self.unitsFlag == 1:
+        if self.unitsFlag:
             modifier = ''  # TODO
 
             if sourceTime is None:
-                sourceTime = time.localtime()
+                sourceTime = now
 
             m = self.CRE_UNITS.search(s)
             if m is not None:
@@ -790,14 +953,14 @@
                 quantity = s[:m.start('units')]
 
             sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
-            self.unitsFlag = 0
+            self.unitsFlag = False
 
           # Given string is a time string with single char units like "5 h 30 m"
-        if self.qunitsFlag == 1:
+        if self.qunitsFlag:
             modifier = ''  # TODO
 
             if sourceTime is None:
-                sourceTime = time.localtime()
+                sourceTime = now
 
             m = self.CRE_QUNITS.search(s)
             if m is not None:
@@ -805,12 +968,12 @@
                 quantity = s[:m.start('qunits')]
 
             sourceTime      = self._buildTime(sourceTime, quantity, modifier, units)
-            self.qunitsFlag = 0
+            self.qunitsFlag = False
 
           # Given string does not match anything
         if sourceTime is None:
-            sourceTime       = time.localtime()
-            self.invalidFlag = 1
+            sourceTime       = now
+            self.invalidFlag = True
 
         return sourceTime
 
@@ -836,16 +999,16 @@
         parseStr  = ''
         totalTime = sourceTime
 
-        self.invalidFlag = 0
+        self.invalidFlag = False
 
         if s == '' :
             if sourceTime is not None:
-                return (sourceTime, 0)
+                return (sourceTime, False)
             else:
-                return (time.localtime(), 1)
+                return (time.localtime(), True)
 
         while len(s) > 0:
-            flag   = 0
+            flag   = False
             chunk1 = ''
             chunk2 = ''
 
@@ -856,13 +1019,13 @@
                 # Modifier like next\prev..
                 m = self.CRE_MODIFIER.search(s)
                 if m is not None:
-                    self.modifierFlag = 1
+                    self.modifierFlag = True
                     if (m.group('modifier') != s):
                         # capture remaining string
                         parseStr = m.group('modifier')
                         chunk1   = string.strip(s[:m.start('modifier')])
                         chunk2   = string.strip(s[m.end('modifier'):])
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -870,13 +1033,13 @@
                 # Modifier like from\after\prior..
                 m = self.CRE_MODIFIER2.search(s)
                 if m is not None:
-                    self.modifier2Flag = 1
+                    self.modifier2Flag = True
                     if (m.group('modifier') != s):
                         # capture remaining string
                         parseStr = m.group('modifier')
                         chunk1   = string.strip(s[:m.start('modifier')])
                         chunk2   = string.strip(s[m.end('modifier'):])
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -884,14 +1047,14 @@
                 # String date format
                 m = self.CRE_DATE3.search(s)
                 if m is not None:
-                    self.dateStrFlag = 1
+                    self.dateStrFlag = True
                     if (m.group('date') != s):
                         # capture remaining string
                         parseStr = m.group('date')
                         chunk1   = s[:m.start('date')]
                         chunk2   = s[m.end('date'):]
                         s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -899,14 +1062,14 @@
                 # Standard date format
                 m = self.CRE_DATE.search(s)
                 if m is not None:
-                    self.dateStdFlag = 1
+                    self.dateStdFlag = True
                     if (m.group('date') != s):
                         # capture remaining string
                         parseStr = m.group('date')
                         chunk1   = s[:m.start('date')]
                         chunk2   = s[m.end('date'):]
                         s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -914,14 +1077,14 @@
                 # Natural language day strings
                 m = self.CRE_DAY.search(s)
                 if m is not None:
-                    self.dayStrFlag = 1
+                    self.dayStrFlag = True
                     if (m.group('day') != s):
                         # capture remaining string
                         parseStr = m.group('day')
                         chunk1   = s[:m.start('day')]
                         chunk2   = s[m.end('day'):]
                         s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -929,14 +1092,19 @@
                 # Quantity + Units
                 m = self.CRE_UNITS.search(s)
                 if m is not None:
-                    self.unitsFlag = 1
+                    self.unitsFlag = True
                     if (m.group('qty') != s):
                         # capture remaining string
                         parseStr = m.group('qty')
-                        chunk1   = s[:m.start('qty')]
-                        chunk2   = s[m.end('qty'):]
-                        s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        chunk1   = s[:m.start('qty')].strip()
+                        chunk2   = s[m.end('qty'):].strip()
+
+                        if chunk1[-1:] == '-':
+                            parseStr = '-%s' % parseStr
+                            chunk1   = chunk1[:-1]
+
+                        s    = '%s %s' % (chunk1, chunk2)
+                        flag = True
                     else:
                         parseStr = s
 
@@ -944,14 +1112,19 @@
                 # Quantity + Units
                 m = self.CRE_QUNITS.search(s)
                 if m is not None:
-                    self.qunitsFlag = 1
+                    self.qunitsFlag = True
                     if (m.group('qty') != s):
                         # capture remaining string
                         parseStr = m.group('qty')
-                        chunk1   = s[:m.start('qty')]
-                        chunk2   = s[m.end('qty'):]
-                        s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        chunk1   = s[:m.start('qty')].strip()
+                        chunk2   = s[m.end('qty'):].strip()
+
+                        if chunk1[-1:] == '-':
+                            parseStr = '-%s' % parseStr
+                            chunk1   = chunk1[:-1]
+
+                        s    = '%s %s' % (chunk1, chunk2)
+                        flag = True
                     else:
                         parseStr = s 
 
@@ -959,14 +1132,14 @@
                 # Weekday
                 m = self.CRE_WEEKDAY.search(s)
                 if m is not None:
-                    self.weekdyFlag = 1
+                    self.weekdyFlag = True
                     if (m.group('weekday') != s):
                         # capture remaining string
                         parseStr = m.group()
                         chunk1   = s[:m.start('weekday')]
                         chunk2   = s[m.end('weekday'):]
                         s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -974,14 +1147,14 @@
                 # Natural language time strings
                 m = self.CRE_TIME.search(s)
                 if m is not None:
-                    self.timeStrFlag = 1
+                    self.timeStrFlag = True
                     if (m.group('time') != s):
                         # capture remaining string
                         parseStr = m.group('time')
                         chunk1   = s[:m.start('time')]
                         chunk2   = s[m.end('time'):]
                         s        = '%s %s' % (chunk1, chunk2)
-                        flag     = 1
+                        flag     = True
                     else:
                         parseStr = s
 
@@ -989,7 +1162,7 @@
                 # HH:MM(:SS) am/pm time strings
                 m = self.CRE_TIMEHMS2.search(s)
                 if m is not None:
-                    self.meridianFlag = 1
+                    self.meridianFlag = True
                     if m.group('minutes') is not None:
                         if m.group('seconds') is not None:
                             parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
@@ -1002,13 +1175,13 @@
                     chunk2 = s[m.end('meridian'):]
 
                     s    = '%s %s' % (chunk1, chunk2)
-                    flag = 1
+                    flag = True
 
             if parseStr == '':
                 # HH:MM(:SS) time strings
                 m = self.CRE_TIMEHMS.search(s)
                 if m is not None:
-                    self.timeFlag = 1
+                    self.timeFlag = True
                     if m.group('seconds') is not None:
                         parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
                         chunk1   = s[:m.start('hours')]
@@ -1019,27 +1192,27 @@
                         chunk2   = s[m.end('minutes'):]
 
                     s    = '%s %s' % (chunk1, chunk2)
-                    flag = 1
+                    flag = True
 
             # if string does not match any regex, empty string to come out of the while loop
-            if flag is 0:
+            if not flag:
                 s = ''
 
             if _debug:
                 print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
-                print 'invalid [%d] weekday [%d] dateStd [%d] dateStr [%d] time [%d] timeStr [%d] meridian [%d]' % \
+                print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
                        (self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
-                print 'dayStr [%d] modifier [%d] modifier2 [%d] units [%d] qunits[%d]' % \
+                print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
                        (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
 
             # evaluate the matched string
             if parseStr != '':
-                if self.modifierFlag == 1:
+                if self.modifierFlag == True:
                     t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
 
                     return self.parse(t, totalTime)
 
-                elif self.modifier2Flag == 1:
+                elif self.modifier2Flag == True:
                     s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
                 else:
                     totalTime = self._evalString(parseStr, totalTime)
@@ -1048,7 +1221,7 @@
         # String is not parsed at all
         if totalTime is None or totalTime == sourceTime:
             totalTime        = time.localtime()
-            self.invalidFlag = 1
+            self.invalidFlag = True
 
         return (totalTime, self.invalidFlag)
 
--- a/MoinMoin/support/parsedatetime/parsedatetime_consts.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/support/parsedatetime/parsedatetime_consts.py	Wed Aug 23 20:42:35 2006 +0200
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 """
-CalendarConstants defines all constants used by parsedatetime.py.
+The Constants class defines all constants used by parsedatetime.py.
 """
 
 __license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
@@ -19,48 +19,514 @@
 limitations under the License.
 """
 __author__       = 'Mike Taylor <http://code-bear.com>'
-__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
+__contributors__ = [ 'Darshana Chhajed <mailto://darshana@osafoundation.org>',
                    ]
 
 
-class CalendarConstants:
-    def __init__(self):
-        self.Locale = 'American'
-
-        self.TIMESEP      = ':'
+try:
+    import PyICU as pyicu
+except:
+    pyicu = None
 
-        self.RE_SPECIAL   = r'(?P<special>^[in|on|of|at]+)\s+'
-        self.RE_UNITS     = r'(?P<qty>(-?\d+\s*(?P<units>((hour|hr|minute|min|second|sec|day|dy|week|wk|month|mth|year|yr)s?))))'
-        self.RE_QUNITS    = r'(?P<qty>(-?\d+\s?(?P<qunits>h|m|s|d|w|m|y)(\s|,|$)))'
-        self.RE_MODIFIER  = r'(?P<modifier>(previous|prev|last|next|this|eo|(end\sof)|(in\sa)))'
-        self.RE_MODIFIER2 = r'(?P<modifier>(from|before|after|ago|prior))'
-        self.RE_TIMEHMS   = r'(?P<hours>\d\d?)(?P<tsep>:|)(?P<minutes>\d\d)(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
-        self.RE_TIMEHMS2  = r'(?P<hours>(\d\d?))((?P<tsep>:|)(?P<minutes>(\d\d?))(?:(?P=tsep)(?P<seconds>\d\d?(?:[.,]\d+)?))?)?\s?(?P<meridian>(am|pm|a.m.|p.m.|a|p))'
-        self.RE_DATE      = r'(?P<date>\d+([/.\\]\d+)+)'
-        self.RE_DATE2     = r'[/.\\-]'
-        self.RE_DATE3     = r'(?P<date>((?P<mthname>(january|february|march|april|may|june|july|august|september|october|november|december))\s?((?P<day>\d\d?)(\s|rd|st|nd|th|,|$)+)?(?P<year>\d\d\d\d)?))'
-        self.RE_MONTH     = r'(?P<month>((?P<mthname>(january|february|march|april|may|june|july|august|september|october|november|december))(\s?(?P<year>(\d\d\d\d)))?))'
-        self.RE_WEEKDAY   = r'(?P<weekday>(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|saturday|sat|sunday|sun))'
-        self.RE_DAY       = r'(?P<day>(today|tomorrow|yesterday))'
-        self.RE_TIME      = r'\s*(?P<time>(morning|breakfast|noon|lunch|evening|midnight|tonight|dinner|night|now))' 
-        self.RE_REMAINING = r'\s+'
 
-          # Used to adjust the returned date before/after the source
+import string
+import datetime, time
 
-        self.Modifiers = { 'from':       1,
-                           'before':    -1,
-                           'after':      1,
-                           'ago':        1,
-                           'prior':     -1,
-                           'prev':      -1,
-                           'last':      -1,
-                           'next':       1,
-                           'this':       0,
-                           'previous':  -1,
-                           'in a':       2,
-                           'end of':     0,
-                           'eo':         0,
-                        }
+
+class pdtLocale_en:
+    """
+    en_US Locale constants
+
+    This class will be used to initialize C{Constants} if PyICU is not located.
+
+    Defined as class variables are the lists and strings needed by parsedatetime
+    to evaluate strings in English (US)
+    """
+
+    localeID      = 'en_US'   # don't use a unicode string
+    dateSep       = u'/'
+    timeSep       = u':'
+    meridian      = [ u'AM', u'PM' ]
+    usesMeridian  = True
+    uses24        = False
+
+    Weekdays      = [ u'sunday', u'monday', u'tuesday',
+                      u'wednesday', u'thursday', u'friday', u'saturday',
+                    ]
+    shortWeekdays = [ u'sun', u'mon', u'tues',
+                      u'wed', u'thu', u'fri', u'sat',
+                    ]
+    Months        = [ u'january', u'february', u'march',
+                      u'april',   u'may',      u'june',
+                      u'july',    u'august',   u'september',
+                      u'october', u'november', u'december',
+                    ]
+    shortMonths   = [ u'jan', u'feb', u'mar',
+                      u'apr', u'may', u'jun',
+                      u'jul', u'aug', u'sep',
+                      u'oct', u'nov', u'dec',
+                    ]
+    dateFormats   = { 'full':   'EEEE, MMMM d, yyyy',
+                      'long':   'MMMM d, yyyy',
+                      'medium': 'MMM d, yyyy',
+                      'short':  'M/d/yy',
+                    }
+    timeFormats   = { 'full':   'h:mm:ss a z',
+                      'long':   'h:mm:ss a z',
+                      'medium': 'h:mm:ss a',
+                      'short':  'h:mm a',
+                    }
+
+      # this will be added to re_consts later
+    units = { 'seconds': [ 'second', 'sec' ],
+              'minutes': [ 'minute', 'min' ],
+              'hours':   [ 'hour',   'hr'  ],
+              'days':    [ 'day',    'dy'  ],
+              'weeks':   [ 'week',   'wk'  ],
+              'months':  [ 'month',  'mth' ],
+              'years':   [ 'year',   'yr'  ],
+            }
+
+      # text constants to be used by regex's later
+    re_consts     = { 'specials':      'in|on|of|at',
+                      'timeseperator': ':',
+                      'daysuffix':     'rd|st|nd|th',
+                      'meridian':      'am|pm|a.m.|p.m.|a|p',
+                      'qunits':        'h|m|s|d|w|m|y',
+                      'now':           [ 'now' ],
+                    }
+
+      # Used to adjust the returned date before/after the source
+    modifiers = { 'from':       1,
+                  'before':    -1,
+                  'after':      1,
+                  'ago':        1,
+                  'prior':     -1,
+                  'prev':      -1,
+                  'last':      -1,
+                  'next':       1,
+                  'this':       0,
+                  'previous':  -1,
+                  'in a':       2,
+                  'end of':     0,
+                  'eo':         0,
+                }
+
+    dayoffsets = { 'tomorrow':   1,
+                   'today':      0,
+                   'yesterday': -1,
+                 }
+
+      # special day and/or times, i.e. lunch, noon, evening
+      # each element in the dictionary is a dictionary that is used
+      # to fill in any value to be replace - the current date/time will
+      # already have been populated by the method buildSources
+    re_sources    = { 'noon':      { 'hr': 12, 'mn': 0, 'sec': 0 },
+                      'lunch':     { 'hr': 12, 'mn': 0, 'sec': 0 },
+                      'morning':   { 'hr':  6, 'mn': 0, 'sec': 0 },
+                      'breakfast': { 'hr':  8, 'mn': 0, 'sec': 0 },
+                      'dinner':    { 'hr': 19, 'mn': 0, 'sec': 0 },
+                      'evening':   { 'hr': 18, 'mn': 0, 'sec': 0 },
+                      'midnight':  { 'hr':  0, 'mn': 0, 'sec': 0 },
+                      'night':     { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      'tonight':   { 'hr': 21, 'mn': 0, 'sec': 0 },
+                    }
+
+
+class pdtLocale_es:
+    """
+    es Locale constants
+
+    This class will be used to initialize C{Constants} if PyICU is not located.
+
+    Defined as class variables are the lists and strings needed by parsedatetime
+    to evaluate strings in Spanish
+
+    Note that I don't speak Spanish so many of the items below are still in English
+    """
+
+    localeID      = 'es'   # don't use a unicode string
+    dateSep       = u'/'
+    timeSep       = u':'
+    meridian      = []
+    usesMeridian  = False
+    uses24        = True
+
+    Weekdays      = [ u'domingo', u'lunes', u'martes',
+                      u'mi\xe9rcoles', u'jueves', u'viernes', u's\xe1bado',
+                    ]
+    shortWeekdays = [ 'dom', u'lun', u'mar',
+                      u'mi\xe9', u'jue', u'vie', u's\xe1b',
+                    ]
+    Months        = [ u'enero', u'febrero', u'marzo',
+                      u'abril', u'mayo', u'junio',
+                      u'julio', u'agosto', u'septiembre',
+                      u'octubre', u'noviembre', u'diciembre'
+                    ]
+    shortMonths   = [ u'ene', u'feb', u'mar',
+                      u'abr', u'may', u'jun',
+                      u'jul', u'ago', u'sep',
+                      u'oct', u'nov', u'dic'
+                    ]
+    dateFormats   = { 'full':   "EEEE d' de 'MMMM' de 'yyyy",
+                      'long':   "d' de 'MMMM' de 'yyyy",
+                      'medium': "dd-MMM-yy",
+                      'short':  "d/MM/yy",
+                    }
+    timeFormats   = { 'full':   "HH'H'mm' 'ss z",
+                      'long':   "HH:mm:ss z",
+                      'medium': "HH:mm:ss",
+                      'short':  "HH:mm",
+                    }
+
+      # this will be added to re_consts later
+    units = { 'seconds': [ 'second', 'sec' ],
+              'minutes': [ 'minute', 'min' ],
+              'hours':   [ 'hour',   'hr'  ],
+              'days':    [ 'day',    'dy'  ],
+              'weeks':   [ 'week',   'wk'  ],
+              'months':  [ 'month',  'mth' ],
+              'years':   [ 'year',   'yr'  ],
+            }
+
+      # text constants to be used by regex's later
+    re_consts     = { 'specials':      'in|on|of|at',
+                      'timeseperator': timeSep,
+                      'dateseperator': dateSep,
+                      'daysuffix':     'rd|st|nd|th',
+                      'qunits':        'h|m|s|d|w|m|y',
+                      'now':           [ 'now' ],
+                    }
+
+      # Used to adjust the returned date before/after the source
+    modifiers = { 'from':       1,
+                  'before':    -1,
+                  'after':      1,
+                  'ago':        1,
+                  'prior':     -1,
+                  'prev':      -1,
+                  'last':      -1,
+                  'next':       1,
+                  'this':       0,
+                  'previous':  -1,
+                  'in a':       2,
+                  'end of':     0,
+                  'eo':         0,
+                }
+
+    dayoffsets = { 'tomorrow':   1,
+                   'today':      0,
+                   'yesterday': -1,
+                 }
+
+      # special day and/or times, i.e. lunch, noon, evening
+      # each element in the dictionary is a dictionary that is used
+      # to fill in any value to be replace - the current date/time will
+      # already have been populated by the method buildSources
+    re_sources    = { 'noon':      { 'hr': 12,  'mn': 0, 'sec': 0 },
+                      'lunch':     { 'hr': 12,  'mn': 0, 'sec': 0 },
+                      'morning':   { 'hr':  6,  'mn': 0, 'sec': 0 },
+                      'breakfast': { 'hr':  8,  'mn': 0, 'sec': 0 },
+                      'dinner':    { 'hr': 19,  'mn': 0, 'sec': 0 },
+                      'evening':   { 'hr': 18,  'mn': 0, 'sec': 0 },
+                      'midnight':  { 'hr':  0,  'mn': 0, 'sec': 0 },
+                      'night':     { 'hr': 21,  'mn': 0, 'sec': 0 },
+                      'tonight':   { 'hr': 21,  'mn': 0, 'sec': 0 },
+                    }
+
+
+pdtLocales = { 'en_US': pdtLocale_en,
+               'es':    pdtLocale_es,
+             }
+
+
+def _initLocale(ptc):
+    """
+    Helper function to initialize the different lists and strings
+    from either PyICU or one of the locale pdt Locales and store
+    them into ptc.
+    """
+    if pyicu and ptc.usePyICU:
+        ptc.icuLocale = pyicu.Locale(ptc.localeID)
+
+        if not ptc.icuLocale:
+            ptc.icuLocale = pyicu.Locale('en_US')
+
+        ptc.icuSymbols    = pyicu.DateFormatSymbols(ptc.icuLocale)
+
+        ptc.Weekdays      = map(string.lower, ptc.icuSymbols.getWeekdays()[1:])
+        ptc.shortWeekdays = map(string.lower, ptc.icuSymbols.getShortWeekdays()[1:])
+        ptc.Months        = map(string.lower, ptc.icuSymbols.getMonths())
+        ptc.shortMonths   = map(string.lower, ptc.icuSymbols.getShortMonths())
+
+          # not quite sure how to init this so for now
+          # set it to none so it will be set to the en_US defaults for now
+        ptc.re_consts     = None
+
+        ptc.icu_df        = { 'full':   pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kFull,   ptc.icuLocale),
+                              'long':   pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kLong,   ptc.icuLocale),
+                              'medium': pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kMedium, ptc.icuLocale),
+                              'short':  pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kShort,  ptc.icuLocale),
+                            }
+        ptc.icu_tf        = { 'full':   pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kFull,   ptc.icuLocale),
+                              'long':   pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kLong,   ptc.icuLocale),
+                              'medium': pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kMedium, ptc.icuLocale),
+                              'short':  pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kShort,  ptc.icuLocale),
+                            }
+
+        ptc.dateFormats   = { 'full':   ptc.icu_df['full'].toPattern(),
+                              'long':   ptc.icu_df['long'].toPattern(),
+                              'medium': ptc.icu_df['medium'].toPattern(),
+                              'short':  ptc.icu_df['short'].toPattern(),
+                            }
+        ptc.timeFormats   = { 'full':   ptc.icu_tf['full'].toPattern(),
+                              'long':   ptc.icu_tf['long'].toPattern(),
+                              'medium': ptc.icu_tf['medium'].toPattern(),
+                              'short':  ptc.icu_tf['short'].toPattern(),
+                            }
+    else:
+        if not ptc.localeID in pdtLocales:
+            ptc.localeID = 'en_US'
+
+        ptc.locale = pdtLocales[ptc.localeID]
+
+        ptc.Weekdays      = ptc.locale.Weekdays
+        ptc.shortWeekdays = ptc.locale.shortWeekdays
+        ptc.Months        = ptc.locale.Months
+        ptc.shortMonths   = ptc.locale.shortMonths
+        ptc.dateFormats   = ptc.locale.dateFormats
+        ptc.timeFormats   = ptc.locale.timeFormats
+
+
+      # these values are used to setup the various bits 
+      # of the regex values used to parse
+      #
+      # check if a local set of constants has been
+      # provided, if not use en_US as the default
+    if ptc.localeID in pdtLocales:
+        ptc.re_sources = pdtLocales[ptc.localeID].re_sources
+        ptc.re_values  = pdtLocales[ptc.localeID].re_consts
+
+        units = pdtLocales[ptc.localeID].units
+
+        ptc.Modifiers  = pdtLocales[ptc.localeID].modifiers
+        ptc.dayOffsets = pdtLocales[ptc.localeID].dayoffsets
+
+          # for now, pull over any missing keys from the US set
+        for key in pdtLocales['en_US'].re_consts:
+            if not key in ptc.re_values:
+                ptc.re_values[key] = pdtLocales['en_US'].re_consts[key]
+    else:
+        ptc.re_sources = pdtLocales['en_US'].re_sources
+        ptc.re_values  = pdtLocales['en_US'].re_consts
+        ptc.Modifiers  = pdtLocales['en_US'].modifiers
+        ptc.dayOffsets = pdtLocales['en_US'].dayoffsets
+        units          = pdtLocales['en_US'].units
+
+    ptc.re_values['months']      = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % tuple(ptc.Months)
+    ptc.re_values['shortmonths'] = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % tuple(ptc.shortMonths)
+    ptc.re_values['days']        = '%s|%s|%s|%s|%s|%s|%s' % tuple(ptc.Weekdays)
+    ptc.re_values['shortdays']   = '%s|%s|%s|%s|%s|%s|%s' % tuple(ptc.shortWeekdays)
+
+    l = []
+    for unit in units:
+        l.append('|'.join(units[unit]))
+
+    ptc.re_values['units'] = '|'.join(l)
+    ptc.Units              = ptc.re_values['units'].split('|')
+
+
+def _initSymbols(ptc):
+    """
+    Helper function to initialize the single character constants
+    and other symbols needed.
+    """
+    ptc.timeSep  = u':'
+    ptc.dateSep  = u'/'
+    ptc.meridian = [ u'AM', u'PM' ]
+
+    ptc.usesMeridian = True
+    ptc.uses24       = False
+
+    if pyicu:
+        am = u''
+        pm = u''
+
+          # ICU doesn't seem to provide directly the
+          # date or time seperator - so we have to
+          # figure it out
+
+        p = pyicu.FieldPosition(pyicu.DateFormat.AM_PM_FIELD)
+        o = ptc.icu_tf['short']
+
+        s = ptc.timeFormats['short']
+
+        ptc.usesMeridian = u'a' in s
+        ptc.uses24       = u'H' in s
+
+        s = o.format(datetime.datetime(2003, 10, 30, 11, 45))       # '11:45 AM' or '11:45'
+
+        s = s.replace('11', '').replace('45', '')                   # ': AM' or ':'
+
+        if len(s) > 0:
+            ptc.timeSep = s[0]
+
+        if ptc.usesMeridian:
+            am = s[1:].strip()                                      # 'AM'
+
+            s = o.format(datetime.datetime(2003, 10, 30, 23, 45))   # '23:45 AM' or '23:45'
+
+            if ptc.uses24:
+                s = s.replace('23', '')
+            else:
+                s = s.replace('11', '')
+
+            pm = s.replace('45', '').replace(ptc.timeSep, '').strip()  # 'PM' or ''
+
+        ptc.meridian = [ am, pm ]
+
+    else:
+        ptc.timeSep      = ptc.locale.timeSep
+        ptc.dateSep      = ptc.locale.dateSep
+        ptc.meridian     = ptc.locale.meridian
+        ptc.usesMeridian = ptc.locale.usesMeridian
+        ptc.uses24       = ptc.locale.uses24
+
+      # build am and pm lists to contain
+      # original case, lowercase and first-char
+      # versions of the meridian text
+
+    if len(ptc.meridian) > 0:
+        am     = ptc.meridian[0]
+        ptc.am = [ am ]
+
+        if len(am) > 0:
+            ptc.am.append(am[0])
+            am = am.lower()
+            ptc.am.append(am)
+            ptc.am.append(am[0])
+    else:
+        am     = ''
+        ptc.am = [ '', '' ]
+
+    if len(ptc.meridian) > 1:
+        pm     = ptc.meridian[1]
+        ptc.pm = [ pm ]
+
+        if len(pm) > 0:
+            ptc.pm.append(pm[0])
+            pm = pm.lower()
+            ptc.pm.append(pm)
+            ptc.pm.append(pm[0])
+    else:
+        pm     = ''
+        ptc.pm = [ '', '' ]
+
+
+def _initPatterns(ptc):
+    """
+    Helper function to take the different localized bits from ptc and
+    create the regex strings.
+    """
+    # TODO add code to parse the date formats and build the regexes up from sub-parts
+    # TODO find all hard-coded uses of date/time seperators
+
+    ptc.RE_DATE3     = r'(?P<date>((?P<mthname>(%(months)s|%(shortmonths)s))\s?((?P<day>\d\d?)(\s|%(daysuffix)s|,|$)+)?(?P<year>\d\d\d\d)?))' % ptc.re_values
+    ptc.RE_MONTH     = r'(?P<month>((?P<mthname>(%(months)s|%(shortmonths)s))(\s?(?P<year>(\d\d\d\d)))?))' % ptc.re_values
+    ptc.RE_WEEKDAY   = r'(?P<weekday>(%(days)s|%(shortdays)s))' % ptc.re_values
+
+    ptc.RE_SPECIAL   = r'(?P<special>^[%(specials)s]+)\s+' % ptc.re_values
+    ptc.RE_UNITS     = r'(?P<qty>(-?\d+\s*(?P<units>((%(units)s)s?))))' % ptc.re_values
+    ptc.RE_QUNITS    = r'(?P<qty>(-?\d+\s?(?P<qunits>%(qunits)s)(\s|,|$)))' % ptc.re_values
+    ptc.RE_MODIFIER  = r'(?P<modifier>(previous|prev|last|next|this|eo|(end\sof)|(in\sa)))' % ptc.re_values
+    ptc.RE_MODIFIER2 = r'(?P<modifier>(from|before|after|ago|prior))' % ptc.re_values
+    ptc.RE_TIMEHMS   = r'(?P<hours>\d\d?)(?P<tsep>%(timeseperator)s|)(?P<minutes>\d\d)(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?' % ptc.re_values
+
+    ptc.RE_TIMEHMS2  = r'(?P<hours>(\d\d?))((?P<tsep>%(timeseperator)s|)(?P<minutes>(\d\d?))(?:(?P=tsep)(?P<seconds>\d\d?(?:[.,]\d+)?))?)?' % ptc.re_values
+
+    if 'meridian' in ptc.re_values:
+        ptc.RE_TIMEHMS2 += r'\s?(?P<meridian>(%(meridian)s))' % ptc.re_values
+
+    ptc.RE_DATE      = r'(?P<date>\d+([/.\\]\d+)+)'
+    ptc.RE_DATE2     = r'[/.\\]'
+    ptc.RE_DAY       = r'(?P<day>(today|tomorrow|yesterday))' % ptc.re_values
+    ptc.RE_TIME      = r'\s*(?P<time>(morning|breakfast|noon|lunch|evening|midnight|tonight|dinner|night|now))' % ptc.re_values
+    ptc.RE_REMAINING = r'\s+'
+
+      # Regex for date/time ranges
+
+    ptc.RE_RTIMEHMS  = r'(\d\d?)%(timeseperator)s(\d\d)(%(timeseperator)s(\d\d))?' % ptc.re_values
+
+    ptc.RE_RTIMEHMS2 = r'(\d\d?)(%(timeseperator)s(\d\d?))?(%(timeseperator)s(\d\d?))?' % ptc.re_values
+
+    if 'meridian' in ptc.re_values:
+        ptc.RE_RTIMEHMS2 += r'\s?(%(meridian)s)' % ptc.re_values
+
+    ptc.RE_RDATE     = r'(\d+([/.\\]\d+)+)'
+    ptc.RE_RDATE3    = r'((((%(months)s))\s?((\d\d?)(\s|%(daysuffix)s|,|$)+)?(\d\d\d\d)?))' % ptc.re_values
+    ptc.DATERNG1     = ptc.RE_RDATE     + r'\s?-\s?' + ptc.RE_RDATE     # "06/07/06 - 08/09/06"
+    ptc.DATERNG2     = ptc.RE_RDATE3    + r'\s?-\s?' + ptc.RE_RDATE3    # "march 31 - june 1st, 2006"
+    ptc.DATERNG3     = ptc.RE_RDATE3    + r'\s?' + r'-' + r'\s?(\d\d?)\s?(rd|st|nd|th)?' % ptc.re_values # "march 1rd -13th"
+    ptc.TIMERNG1     = ptc.RE_RTIMEHMS2 + r'\s?-\s?'+ ptc.RE_RTIMEHMS2  # "4:00:55 pm - 5:90:44 am",'4p-5p'
+    ptc.TIMERNG2     = ptc.RE_RTIMEHMS  + r'\s?-\s?'+ ptc.RE_RTIMEHMS   # "4:00 - 5:90 ","4:55:55-3:44:55"
+    ptc.TIMERNG3     = r'\d\d?\s?-\s?'+ ptc.RE_RTIMEHMS2                # "4-5pm "
+
+
+def _initConstants(ptc):
+    """
+    Create localized versions of the units, week and month names
+    """
+      # build weekday offsets - yes, it assumes the Weekday and shortWeekday
+      # lists are in the same order and Sun..Sat
+    ptc.WeekdayOffsets = {}
+
+    o = 0
+    for key in ptc.Weekdays:
+        ptc.WeekdayOffsets[key] = o
+        o += 1
+    o = 0
+    for key in ptc.shortWeekdays:
+        ptc.WeekdayOffsets[key] = o
+        o += 1
+
+      # build month offsets - yes, it assumes the Months and shortMonths
+      # lists are in the same order and Jan..Dec
+    ptc.MonthOffsets = {}
+    ptc.DaysInMonth  = {}
+
+    o = 1
+    for key in ptc.Months:
+        ptc.MonthOffsets[key] = o
+        ptc.DaysInMonth[key]  = ptc.DaysInMonthList[o - 1]
+        o += 1
+    o = 1
+    for key in ptc.shortMonths:
+        ptc.MonthOffsets[key] = o
+        ptc.DaysInMonth[key]  = ptc.DaysInMonthList[o - 1]
+        o += 1
+
+
+class Constants:
+    """
+    Default set of constants for parsedatetime.
+
+    If PyICU is present, then the class will initialize itself to
+    the current default locale or to the locale specified by C{localeID}.
+
+    If PyICU is not present then the class will initialize itself to
+    en_US locale or if C{localeID} is passed in and the value matches one
+    of the defined pdtLocales then that will be used.
+    """
+    def __init__(self, localeID=None, usePyICU=True):
+        if localeID is None:
+            self.localeID = 'en_US'
+        else:
+            self.localeID = localeID
+
+          # define non-locale specific constants
+
+        self.locale   = None
+        self.usePyICU = usePyICU
 
         self.Second =   1
         self.Minute =  60 * self.Second
@@ -70,209 +536,42 @@
         self.Month  =  30 * self.Day
         self.Year   = 365 * self.Day
 
-        self.WeekDays = { 'monday':    0,
-                          'mon':       0,
-                          'tuesday':   1,
-                          'tue':       1,
-                          'wednesday': 2,
-                          'wed':       2,
-                          'thursday':  3,
-                          'thu':       3,
-                          'friday':    4,
-                          'fri':       4,
-                          'saturday':  5,
-                          'sat':       5,
-                          'sunday':    6,
-                          'sun':       6,
-                        }
-
-          # dictionary to allow for locale specific text
-          # NOTE: The keys are the localized values - the parsing
-          #       code will be using Target_Text using the values
-          #       extracted *from* the user's input
-
-        self.Target_Text = { 'datesep':   '-',
-                             'timesep':   ':',
-                             'day':       'day',
-                             'dy':        'dy',
-                             'd':         'd',
-                             'week':      'week',
-                             'wk':        'wk',
-                             'w':         'w',
-                             'month':     'month',
-                             'mth':       'mth',
-                             'year':      'year',
-                             'yr':        'yr',
-                             'y':         'y',
-                             'hour':      'hour',
-                             'hr':        'hr',
-                             'h':         'h',
-                             'minute':    'minute',
-                             'min':       'min',
-                             'm':         'm',
-                             'second':    'second',
-                             'sec':       'sec',
-                             's':         's',
-                             'now':       'now',
-                             'noon':      'noon',
-                             'morning':   'morning',
-                             'evening':   'evening',
-                             'breakfast': 'breakfast',
-                             'lunch':     'lunch',
-                             'dinner':    'dinner',
-                             'monday':    'monday',
-                             'mon':       'mon',
-                             'tuesday':   'tuesday',
-                             'tue':       'tue',
-                             'wednesday': 'wednesday',
-                             'wed':       'wed',
-                             'thursday':  'thursday',
-                             'thu':       'thu',
-                             'friday':    'friday',
-                             'fri':       'fri',
-                             'saturday':  'saturday',
-                             'sat':       'sat',
-                             'sunday':    'sunday',
-                             'sun':       'sun',
-                             'january':   'january',
-                             'jan':       'jan',
-                             'febuary':   'febuary',
-                             'feb':       'feb',
-                             'march':     'march',
-                             'mar':       'mar',
-                             'april':     'april',
-                             'apr':       'apr',
-                             'may':       'may',
-                             'may':       'may',
-                             'june':      'june',
-                             'jun':       'jun',
-                             'july':      'july',
-                             'jul':       'jul',
-                             'august':    'august',
-                             'aug':       'aug',
-                             'september': 'september',
-                             'sept':      'sep',
-                             'october':   'october',
-                             'oct':       'oct',
-                             'november':  'november',
-                             'nov':       'nov',
-                             'december':  'december',
-                             'dec':       'dec',
-                           }
-
-          # FIXME: there *has* to be a standard routine that does this
-
-        self.DOW_Text = [self.Target_Text['mon'],
-                         self.Target_Text['tue'],
-                         self.Target_Text['wed'],
-                         self.Target_Text['thu'],
-                         self.Target_Text['fri'],
-                         self.Target_Text['sat'],
-                         self.Target_Text['sun'],
-                        ]
-
         self.DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
 
-        self.DaysInMonth = {}
-        self.DaysInMonth[self.Target_Text['january']]   = self.DaysInMonthList[0]
-        self.DaysInMonth[self.Target_Text['febuary']]   = self.DaysInMonthList[1]
-        self.DaysInMonth[self.Target_Text['march']]     = self.DaysInMonthList[2]
-        self.DaysInMonth[self.Target_Text['april']]     = self.DaysInMonthList[3]
-        self.DaysInMonth[self.Target_Text['may']]       = self.DaysInMonthList[4]
-        self.DaysInMonth[self.Target_Text['june']]      = self.DaysInMonthList[5]
-        self.DaysInMonth[self.Target_Text['july']]      = self.DaysInMonthList[6]
-        self.DaysInMonth[self.Target_Text['august']]    = self.DaysInMonthList[7]
-        self.DaysInMonth[self.Target_Text['september']] = self.DaysInMonthList[8]
-        self.DaysInMonth[self.Target_Text['october']]   = self.DaysInMonthList[9]
-        self.DaysInMonth[self.Target_Text['november']]  = self.DaysInMonthList[10]
-        self.DaysInMonth[self.Target_Text['december']]  = self.DaysInMonthList[11]
-
-        self.Month_Text = [ self.Target_Text['january'],
-                            self.Target_Text['febuary'],
-                            self.Target_Text['march'],
-                            self.Target_Text['april'],
-                            self.Target_Text['may'],
-                            self.Target_Text['june'],
-                            self.Target_Text['july'],
-                            self.Target_Text['august'],
-                            self.Target_Text['september'],
-                            self.Target_Text['october'],
-                            self.Target_Text['november'],
-                            self.Target_Text['december'],
-                          ]
-
-
-        self.MthNames = { 'january':    1,
-                          'february':   2,
-                          'march':      3,
-                          'april':      4,
-                          'may' :       5,
-                          'june':       6,
-                          'july':       7,
-                          'august':     8,
-                          'september':  9,
-                          'october':   10,
-                          'november':  11,
-                          'december':  12,
-                        }
-
+        _initLocale(self)
+        _initConstants(self)
+        _initSymbols(self)
+        _initPatterns(self)
 
 
-          # This looks hokey - but it is a nice simple way to get
-          # the proper unit value and it has the advantage that
-          # later I can morph it into something localized.
-          # Any trailing s will be removed before lookup.
-
-        self.Units = {}
-        self.Units[self.Target_Text['second']] = self.Second
-        self.Units[self.Target_Text['sec']]    = self.Second
-        self.Units[self.Target_Text['s']]      = self.Second
-        self.Units[self.Target_Text['minute']] = self.Minute
-        self.Units[self.Target_Text['min']]    = self.Minute
-        self.Units[self.Target_Text['m']]      = self.Minute
-        self.Units[self.Target_Text['hour']]   = self.Hour
-        self.Units[self.Target_Text['hr']]     = self.Hour
-        self.Units[self.Target_Text['h']]      = self.Hour
-        self.Units[self.Target_Text['day']]    = self.Day
-        self.Units[self.Target_Text['dy']]     = self.Day
-        self.Units[self.Target_Text['d']]      = self.Day
-        self.Units[self.Target_Text['week']]   = self.Week
-        self.Units[self.Target_Text['wk']]     = self.Week
-        self.Units[self.Target_Text['w']]      = self.Week
-        self.Units[self.Target_Text['month']]  = self.Month
-        self.Units[self.Target_Text['mth']]    = self.Month
-        self.Units[self.Target_Text['year']]   = self.Year
-        self.Units[self.Target_Text['yr']]     = self.Year
-        self.Units[self.Target_Text['y']]      = self.Year
+    def buildSources(self, sourceTime=None):
+        """
+        Return a dictionary of date/time tuples based on the keys
+        found in self.re_sources.
 
-        self.Units_Text = { 'one':        1,
-                            'two':        2,
-                            'three':      3,
-                            'four':       4,
-                            'five':       5,
-                            'six':        6,
-                            'seven':      7,
-                            'eight':      8,
-                            'nine':       9,
-                            'ten':       10,
-                            'eleven':    11,
-                            'twelve':    12,
-                            'thirteen':  13,
-                            'fourteen':  14,
-                            'fifteen':   15,
-                            'sixteen':   16,
-                            'seventeen': 17,
-                            'eighteen':  18,
-                            'nineteen':  19,
-                            'twenty':    20,
-                            'thirty':    30,
-                            'forty':     40,
-                            'fifty':     50,
-                            'sixty':     60,
-                            'seventy':   70,
-                            'eighty':    80,
-                            'ninety':    90,
-                            'half':      0.5,
-                            'quarter':  0.25,
-                         }
+        The current time is used as the default and any specified
+        item found in self.re_sources is inserted into the value
+        and the generated dictionary is returned.
+        """
+        if sourceTime is None:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+        else:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
+        sources  = {}
+        defaults = { 'yr': yr, 'mth': mth, 'dy':  dy,
+                     'hr': hr, 'mn':  mn,  'sec': sec, }
+
+        for item in self.re_sources:
+            values = self.re_sources[item]
+
+            for key in defaults.keys():
+                if not key in values:
+                    values[key] = defaults[key]
+
+            sources[item] = ( values['yr'], values['mth'], values['dy'],
+                              values['hr'], values['mn'], values['sec'], wd, yd, isdst )
+
+        return sources
+
+        
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/parsedatetime/pdt.py	Wed Aug 23 20:42:35 2006 +0200
@@ -0,0 +1,1266 @@
+#!/usr/bin/env python
+
+"""
+Parse human-readable date/time text.
+"""
+
+__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+__author__       = 'Mike Taylor <http://code-bear.com>'
+__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
+                   ]
+
+_debug = False
+
+
+import string, re, time
+import datetime, calendar, rfc822
+import parsedatetime_consts
+
+
+# Copied from feedparser.py
+# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# Originally a def inside of _parse_date_w3dtf()
+def _extract_date(m):
+    year = int(m.group('year'))
+    if year < 100:
+        year = 100 * int(time.gmtime()[0] / 100) + int(year)
+    if year < 1000:
+        return 0, 0, 0
+    julian = m.group('julian')
+    if julian:
+        julian = int(julian)
+        month = julian / 30 + 1
+        day = julian % 30 + 1
+        jday = None
+        while jday != julian:
+            t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
+            jday = time.gmtime(t)[-2]
+            diff = abs(jday - julian)
+            if jday > julian:
+                if diff < day:
+                    day = day - diff
+                else:
+                    month = month - 1
+                    day = 31
+            elif jday < julian:
+                if day + diff < 28:
+                   day = day + diff
+                else:
+                    month = month + 1
+        return year, month, day
+    month = m.group('month')
+    day = 1
+    if month is None:
+        month = 1
+    else:
+        month = int(month)
+        day = m.group('day')
+        if day:
+            day = int(day)
+        else:
+            day = 1
+    return year, month, day
+
+# Copied from feedparser.py 
+# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# Originally a def inside of _parse_date_w3dtf()
+def _extract_time(m):
+    if not m:
+        return 0, 0, 0
+    hours = m.group('hours')
+    if not hours:
+        return 0, 0, 0
+    hours = int(hours)
+    minutes = int(m.group('minutes'))
+    seconds = m.group('seconds')
+    if seconds:
+        seconds = int(seconds)
+    else:
+        seconds = 0
+    return hours, minutes, seconds
+
+
+# Copied from feedparser.py
+# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# Modified to return a tuple instead of mktime
+#
+# Original comment:
+#       W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
+#       Drake and licensed under the Python license.  Removed all range checking
+#       for month, day, hour, minute, and second, since mktime will normalize
+#       these later
+def _parse_date_w3dtf(dateString):
+    # the __extract_date and __extract_time methods were
+    # copied-out so they could be used by my code --bear
+    def __extract_tzd(m):
+        '''Return the Time Zone Designator as an offset in seconds from UTC.'''
+        if not m:
+            return 0
+        tzd = m.group('tzd')
+        if not tzd:
+            return 0
+        if tzd == 'Z':
+            return 0
+        hours = int(m.group('tzdhours'))
+        minutes = m.group('tzdminutes')
+        if minutes:
+            minutes = int(minutes)
+        else:
+            minutes = 0
+        offset = (hours*60 + minutes) * 60
+        if tzd[0] == '+':
+            return -offset
+        return offset
+
+    __date_re = ('(?P<year>\d\d\d\d)'
+                 '(?:(?P<dsep>-|)'
+                 '(?:(?P<julian>\d\d\d)'
+                 '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
+    __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
+    __tzd_rx = re.compile(__tzd_re)
+    __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
+                 '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
+                 + __tzd_re)
+    __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
+    __datetime_rx = re.compile(__datetime_re)
+    m = __datetime_rx.match(dateString)
+    if (m is None) or (m.group() != dateString): return
+    return _extract_date(m) + _extract_time(m) + (0, 0, 0)
+
+
+# Copied from feedparser.py
+# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# Modified to return a tuple instead of mktime
+#
+def _parse_date_rfc822(dateString):
+    '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
+    data = dateString.split()
+    if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
+        del data[0]
+    if len(data) == 4:
+        s = data[3]
+        i = s.find('+')
+        if i > 0:
+            data[3:] = [s[:i], s[i+1:]]
+        else:
+            data.append('')
+        dateString = " ".join(data)
+    if len(data) < 5:
+        dateString += ' 00:00:00 GMT'
+    return rfc822.parsedate_tz(dateString)
+
+# rfc822.py defines several time zones, but we define some extra ones.
+# 'ET' is equivalent to 'EST', etc.
+_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
+rfc822._timezones.update(_additional_timezones)
+
+
+class Calendar:
+    """
+    A collection of routines to input, parse and manipulate date and times.
+    The text can either be 'normal' date values or it can be human readable.
+    """
+
+    def __init__(self, constants=None):
+        """
+        Default constructor for the Calendar class.
+
+        @type  constants: object
+        @param constants: Instance of the class L{CalendarConstants}
+
+        @rtype:  object
+        @return: Calendar instance
+        """
+          # if a constants reference is not included, use default
+        if constants is None:
+            self.ptc = parsedatetime_consts.CalendarConstants()
+        else:
+            self.ptc = constants
+
+        self.CRE_SPECIAL   = re.compile(self.ptc.RE_SPECIAL,   re.IGNORECASE)
+        self.CRE_UNITS     = re.compile(self.ptc.RE_UNITS,     re.IGNORECASE)
+        self.CRE_QUNITS    = re.compile(self.ptc.RE_QUNITS,    re.IGNORECASE)
+        self.CRE_MODIFIER  = re.compile(self.ptc.RE_MODIFIER,  re.IGNORECASE)
+        self.CRE_MODIFIER2 = re.compile(self.ptc.RE_MODIFIER2, re.IGNORECASE)
+        self.CRE_TIMEHMS   = re.compile(self.ptc.RE_TIMEHMS,   re.IGNORECASE)
+        self.CRE_TIMEHMS2  = re.compile(self.ptc.RE_TIMEHMS2,  re.IGNORECASE)
+        self.CRE_DATE      = re.compile(self.ptc.RE_DATE,      re.IGNORECASE)
+        self.CRE_DATE2     = re.compile(self.ptc.RE_DATE2,     re.IGNORECASE)
+        self.CRE_DATE3     = re.compile(self.ptc.RE_DATE3,     re.IGNORECASE)
+        self.CRE_MONTH     = re.compile(self.ptc.RE_MONTH,     re.IGNORECASE)
+        self.CRE_WEEKDAY   = re.compile(self.ptc.RE_WEEKDAY,   re.IGNORECASE)
+        self.CRE_DAY       = re.compile(self.ptc.RE_DAY,       re.IGNORECASE)
+        self.CRE_TIME      = re.compile(self.ptc.RE_TIME,      re.IGNORECASE)
+        self.CRE_REMAINING = re.compile(self.ptc.RE_REMAINING, re.IGNORECASE)
+
+        #regex for date/time ranges
+        self.CRE_RTIMEHMS   = re.compile(self.ptc.RE_RTIMEHMS,  re.IGNORECASE)
+        self.CRE_RTIMEHMS2  = re.compile(self.ptc.RE_RTIMEHMS2,  re.IGNORECASE)
+        self.CRE_RDATE      = re.compile(self.ptc.RE_RDATE,      re.IGNORECASE)
+        self.CRE_RDATE3     = re.compile(self.ptc.RE_RDATE3,     re.IGNORECASE)
+
+        self.CRE_TIMERNG1      = re.compile(self.ptc.TIMERNG1, re.IGNORECASE)
+        self.CRE_TIMERNG2      = re.compile(self.ptc.TIMERNG2, re.IGNORECASE)
+        self.CRE_TIMERNG3      = re.compile(self.ptc.TIMERNG3, re.IGNORECASE)
+        self.CRE_DATERNG1      = re.compile(self.ptc.DATERNG1, re.IGNORECASE)
+        self.CRE_DATERNG2      = re.compile(self.ptc.DATERNG2, re.IGNORECASE)
+        self.CRE_DATERNG3      = re.compile(self.ptc.DATERNG3, re.IGNORECASE)
+
+        self.invalidFlag   = False  # Is set if the datetime string entered cannot be parsed at all
+        self.weekdyFlag    = False  # monday/tuesday/...
+        self.dateStdFlag   = False  # 07/21/06
+        self.dateStrFlag   = False  # July 21st, 2006
+        self.timeFlag      = False  # 5:50 
+        self.meridianFlag  = False  # am/pm
+        self.dayStrFlag    = False  # tomorrow/yesterday/today/..
+        self.timeStrFlag   = False  # lunch/noon/breakfast/...
+        self.modifierFlag  = False  # after/before/prev/next/..
+        self.modifier2Flag = False  # after/before/prev/next/..
+        self.unitsFlag     = False  # hrs/weeks/yrs/min/..
+        self.qunitsFlag    = False  # h/m/t/d..
+
+
+    def _convertUnitAsWords(self, unitText):
+        """
+        Converts text units into their number value
+
+        Five = 5
+        Twenty Five = 25
+        Two hundred twenty five = 225
+        Two thousand and twenty five = 2025
+        Two thousand twenty five = 2025
+
+        @type  unitText: string
+        @param unitText: number string
+
+        @rtype:  integer
+        @return: numerical value of unitText
+        """
+        # TODO: implement this
+        pass
+
+
+    def _buildTime(self, source, quantity, modifier, units):
+        """
+        Take quantity, modifier and unit strings and convert them into values.
+        Then calcuate the time and return the adjusted sourceTime
+
+        @type  source:   time
+        @param source:   time to use as the base (or source)
+        @type  quantity: string
+        @param quantity: quantity string
+        @type  modifier: string
+        @param modifier: how quantity and units modify the source time
+        @type  units:    string
+        @param units:    unit of the quantity (i.e. hours, days, months, etc)
+
+        @rtype:  timetuple
+        @return: timetuple of the calculated time
+        """
+        if _debug:
+            print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
+
+        if source is None:
+            source = time.localtime()
+
+        if quantity is None:
+            quantity = ''
+        else:
+            quantity = string.strip(quantity)
+
+        if len(quantity) == 0:
+            qty = 1
+        else:
+            try:
+                qty = int(quantity)
+            except ValueError:
+                qty = 0
+
+        if modifier in self.ptc.Modifiers:
+            qty = qty * self.ptc.Modifiers[modifier]
+
+            if units is None or units == '':
+                units = 'dy'
+
+        # plurals are handled by regex's (could be a bug tho)
+
+        if units in self.ptc.Units:
+            u = self.ptc.Units[units]
+        else:
+            u = 1
+
+        (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
+
+        start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
+        target = start
+
+        if units.startswith('y'):
+            target = self.inc(start, year=qty)
+        elif units.endswith('th') or units.endswith('ths'):
+            target = self.inc(start, month=qty)
+        else:
+            if units.startswith('d'):
+                target = start + datetime.timedelta(days=qty)
+            elif units.startswith('h'):
+                target = start + datetime.timedelta(hours=qty)
+            elif units.startswith('m'):
+                target = start + datetime.timedelta(minutes=qty)
+            elif units.startswith('s'):
+                target = start + datetime.timedelta(seconds=qty)
+            elif units.startswith('w'):
+                target = start + datetime.timedelta(weeks=qty)
+
+        if target != start:
+            self.invalidFlag = False
+
+        return target.timetuple()
+
+
+    def parseDate(self, dateString):
+        """
+        Parses strings like 05/28/200 or 04.21
+
+        @type  dateString: string
+        @param dateString: text to convert to a datetime
+
+        @rtype:  datetime
+        @return: calculated datetime value of dateString
+        """
+        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
+
+        s = dateString
+        m = self.CRE_DATE2.search(s)
+        if m is not None:
+            index = m.start()
+            mth   = int(s[:index])
+            s     = s[index + 1:]
+
+        m = self.CRE_DATE2.search(s)
+        if m is not None:
+            index = m.start()
+            dy    = int(s[:index])
+            yr    = int(s[index + 1:])
+            # TODO should this have a birthday epoch constraint?
+            if yr < 99:
+                yr += 2000
+        else:
+            dy = int(string.strip(s))
+
+        if mth <= 12 and dy <= self.ptc.DaysInMonthList[mth - 1]:
+            sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
+        else:
+            self.invalidFlag = True
+            sourceTime       = time.localtime() #return current time if date string is invalid
+
+        return sourceTime
+
+
+    def parseDateText(self, dateString):
+        """
+        Parses strings like "May 31st, 2006" or "Jan 1st" or "July 2006"
+
+        @type  dateString: string
+        @param dateString: text to convert to a datetime
+
+        @rtype:  datetime
+        @return: calculated datetime value of dateString
+        """
+        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
+
+        currentMth = mth
+        currentDy  = dy
+
+        s   = dateString.lower()
+        m   = self.CRE_DATE3.search(s)
+        mth = m.group('mthname')
+        mth = int(self.ptc.MthNames[mth])
+
+        if m.group('day') !=  None:
+            dy = int(m.group('day'))
+        else:
+            dy = 1
+
+        if m.group('year') !=  None:
+            yr = int(m.group('year'))
+        elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
+            # if that day and month have already passed in this year,
+            # then increment the year by 1
+            yr += 1
+
+        if dy <= self.ptc.DaysInMonthList[mth - 1]:
+            sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
+        else:
+              # Return current time if date string is invalid
+            self.invalidFlag = True
+            sourceTime       = time.localtime()
+
+        return sourceTime
+
+
+    def evalRanges(self,datetimeString,sourceTime=None):
+        """
+        Evaluates the strings with time or date ranges
+        """
+        startTime = ''
+        endTime   = ''
+        startDate = ''
+        endDate   = ''
+        rangeFlag = 0
+        sourceTime = None
+
+        s = string.strip(datetimeString.lower())
+
+        m = self.CRE_TIMERNG1.search(s)
+        if m is not None:
+            rangeFlag = 1
+        else:
+            m = self.CRE_TIMERNG2.search(s)
+            if m is not None:  
+                rangeFlag = 2
+            else:
+                m = self.CRE_TIMERNG3.search(s)
+                if m is not None:
+                    rangeFlag = 3
+                else:
+                    m = self.CRE_DATERNG1.search(s)
+                    if m is not None:
+                        rangeFlag = 4
+                    else:
+                        m = self.CRE_DATERNG2.search(s)
+                        if m is not None:
+                            rangeFlag = 5
+                        else:
+                            m = self.CRE_DATERNG3.search(s)
+                            if m is not None:
+                                rangeFlag = 6
+
+        if m is not None :
+            if (m.group() != s):
+                # capture remaining string
+                parseStr = m.group()
+                str1    = s[:m.start()]
+                str2    = s[m.end():]
+                s       = str1 + ' ' + str2
+                flag    = 1
+                sourceTime, flag = self.parse(s, sourceTime)
+                if flag == True:
+                    sourceTime = None
+            else:
+                parseStr = s
+
+
+        if rangeFlag == 1:
+            m = re.search('-',parseStr)
+            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+            endTime, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 2:
+            m = re.search('-',parseStr)
+            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+            endTime, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 3:
+            m = re.search('-',parseStr)
+
+            #capturing the meridian from the end time
+            ampm = re.search('a',parseStr)
+
+            #appending the meridian to the start time
+            if ampm is not None:
+                startTime, sflag = self.parse((parseStr[:m.start()]+'am'), sourceTime)
+            else:
+                startTime, sflag = self.parse((parseStr[:m.start()]+'pm'), sourceTime)
+            endTime, eflag   = self.parse(parseStr[(m.start()+1):], sourceTime)
+            if eflag is False and sflag is False:
+                return (startTime, endTime, False)
+
+        elif rangeFlag == 4:
+            m = re.search('-',parseStr)
+            startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+            endDate, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+
+        elif rangeFlag == 5:
+            m = re.search('-',parseStr)
+            endDate = parseStr[(m.start()+1):]
+
+            #capturing the year from the end date
+            date = self.CRE_DATE3.search(endDate)
+            endYear = date.group('year')
+
+            # appending the year to the start date if the start date does not have year information
+            # and the end date does. eg : "Aug 21 - Sep 4, 2007
+            if endYear is not None:
+                startDate = parseStr[:m.start()]
+                date = self.CRE_DATE3.search(startDate)
+                startYear = date.group('year')
+                if startYear is None:
+                    startDate += endYear
+            else:
+                startDate = parseStr[:m.start()]
+
+            startDate, sflag = self.parse(startDate, sourceTime)
+            endDate, eflag   = self.parse(endDate, sourceTime)
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+
+        elif rangeFlag == 6:
+            m = re.search('-',parseStr)
+
+            startDate = parseStr[:m.start()]
+
+            #capturing the month from the start date
+            mth = self.CRE_DATE3.search(startDate)
+            mth = mth.group('mthname')
+
+            # appending the month name to the end date
+            endDate = mth + parseStr[(m.start()+1):]
+
+            startDate, sflag = self.parse(startDate, sourceTime)
+            endDate, eflag   = self.parse(endDate, sourceTime)
+            if eflag is False and sflag is False:
+                return (startDate, endDate, False)
+        else :
+            sourceTime = time.localtime()
+            #if range is not found
+            return (sourceTime, sourceTime, True)
+
+
+    def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
+        """
+        Evaluate the modifier string and following text (passed in
+        as chunk1 and chunk2) and if they match any known modifiers
+        calculate the delta and apply it to sourceTime
+
+        @type  modifier: string
+        @param modifier: modifier text to apply to sourceTime
+        @type  chunk1:   string
+        @param chunk1:   first text chunk that followed modifier (if any)
+        @type  chunk2:   string
+        @param chunk2:   second text chunk that followed modifier (if any)
+        @type  sourceTime: datetime
+        @param sourceTime: datetime value to use as the base
+
+        @rtype:  tuple
+        @return: tuple of any remaining text and the modified sourceTime
+        """
+        offset = self.ptc.Modifiers[modifier]
+
+        if sourceTime is not None:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+        else:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+
+        # capture the units after the modifier and the remaining string after the unit
+        m = self.CRE_REMAINING.search(chunk2)
+        if m is not None:
+            index  = m.start() + 1
+            unit   = chunk2[:m.start()]
+            chunk2 = chunk2[index:]
+        else:
+            unit   = chunk2
+            chunk2 = ''
+
+        flag = False
+
+        if unit == self.ptc.Target_Text['month'] or \
+           unit == self.ptc.Target_Text['mth']:
+            if offset == 0:
+                dy        = self.ptc.DaysInMonthList[mth - 1]
+                sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
+            elif offset == 2:
+                # if day is the last day of the month, calculate the last day of the next month
+                if dy == self.ptc.DaysInMonthList[mth - 1]:
+                    dy = self.ptc.DaysInMonthList[mth]
+
+                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+                target     = self.inc(start, month=1)
+                sourceTime = target.timetuple()
+            else:
+                start      = datetime.datetime(yr, mth, 1, 9, 0, 0)
+                target     = self.inc(start, month=offset)
+                sourceTime = target.timetuple()
+
+            flag = True
+
+        if unit == self.ptc.Target_Text['week'] or \
+             unit == self.ptc.Target_Text['wk'] or \
+             unit == self.ptc.Target_Text['w']:
+            if offset == 0:
+                start      = datetime.datetime(yr, mth, dy, 17, 0, 0)
+                target     = start + datetime.timedelta(days=(4 - wd))
+                sourceTime = target.timetuple()
+            elif offset == 2:
+                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+                target     = start + datetime.timedelta(days=7)
+                sourceTime = target.timetuple()
+            else:
+                return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
+
+            flag = True
+
+        if unit == self.ptc.Target_Text['day'] or \
+            unit == self.ptc.Target_Text['dy'] or \
+            unit == self.ptc.Target_Text['d']:
+            if offset == 0:
+                sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
+            elif offset == 2:
+                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
+                target     = start + datetime.timedelta(days=1)
+                sourceTime = target.timetuple()
+            else:
+                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+                target     = start + datetime.timedelta(days=offset)
+                sourceTime = target.timetuple()
+
+            flag = True
+
+        if unit == self.ptc.Target_Text['hour'] or \
+           unit == self.ptc.Target_Text['hr']:
+            if offset == 0:
+                sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
+            else:
+                start      = datetime.datetime(yr, mth, dy, hr, 0, 0)
+                target     = start + datetime.timedelta(hours=offset)
+                sourceTime = target.timetuple()
+
+            flag = True
+
+        if unit == self.ptc.Target_Text['year'] or \
+             unit == self.ptc.Target_Text['yr'] or \
+             unit == self.ptc.Target_Text['y']:
+            if offset == 0:
+                sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
+            elif offset == 2:
+                sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
+            else:
+                sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
+
+            flag = True
+
+        if flag == False:
+            m = self.CRE_WEEKDAY.match(unit)
+            if m is not None:
+                wkdy = m.group()
+                wkdy = self.ptc.WeekDays[wkdy]
+
+                if offset == 0:
+                    diff       = wkdy - wd
+                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+                    target     = start + datetime.timedelta(days=diff)
+                    sourceTime = target.timetuple()
+                else:
+                    diff       = wkdy - wd
+                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+                    target     = start + datetime.timedelta(days=diff + 7 * offset)
+                    sourceTime = target.timetuple()
+
+                flag = True
+
+        if not flag:
+            m = self.CRE_TIME.match(unit)
+            if m is not None:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
+                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
+                target     = start + datetime.timedelta(days=offset)
+                sourceTime = target.timetuple()
+
+                flag              = True
+                self.modifierFlag = False
+
+        # if the word after next is a number, the string is likely
+        # to be something like "next 4 hrs" for which we have to
+        # combine the units with the rest of the string
+        if not flag:
+            if offset < 0:
+                # if offset is negative, the unit has to be made negative
+                unit = '-%s' % unit
+
+            chunk2 = '%s %s' % (unit, chunk2)
+
+        self.modifierFlag = False
+
+        return '%s %s' % (chunk1, chunk2), sourceTime
+
+
+    def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
+        """
+        Evaluate the modifier string and following text (passed in
+        as chunk1 and chunk2) and if they match any known modifiers
+        calculate the delta and apply it to sourceTime
+
+        @type  modifier:   string
+        @param modifier:   modifier text to apply to sourceTime
+        @type  chunk1:     string
+        @param chunk1:     first text chunk that followed modifier (if any)
+        @type  chunk2:     string
+        @param chunk2:     second text chunk that followed modifier (if any)
+        @type  sourceTime: datetime
+        @param sourceTime: datetime value to use as the base
+
+        @rtype:  tuple
+        @return: tuple of any remaining text and the modified sourceTime
+        """
+        offset = self.ptc.Modifiers[modifier]
+        digit  = r'\d+'
+
+        if sourceTime is not None:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+        else:
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+
+        self.modifier2Flag = False
+
+        # If the string after the negative modifier starts with
+        # digits, then it is likely that the string is similar to
+        # " before 3 days" or 'evening prior to 3 days'.
+        # In this case, the total time is calculated by subtracting
+        # '3 days' from the current date.
+        # So, we have to identify the quantity and negate it before
+        # parsing the string.
+        # This is not required for strings not starting with digits
+        # since the string is enough to calculate the sourceTime
+        if offset < 0:
+            m = re.match(digit, string.strip(chunk2))
+            if m is not None:
+                qty    = int(m.group()) * -1
+                chunk2 = chunk2[m.end():]
+                chunk2 = '%d%s' % (qty, chunk2)
+
+        sourceTime, flag = self.parse(chunk2, sourceTime)
+
+        if chunk1 != '':
+            if offset < 0:
+                m = re.match(digit, string.strip(chunk1))
+                if m is not None:
+                    qty    = int(m.group()) * -1
+                    chunk1 = chunk1[m.end():]
+                    chunk1 = '%d%s' % (qty, chunk1)
+
+            sourceTime, flag = self.parse(chunk1, sourceTime)
+
+        return '', sourceTime
+
+
+    def _evalString(self, datetimeString, sourceTime=None):
+        """
+        Calculate the datetime based on flags set by the L{parse()} routine
+
+        Examples handled::
+            RFC822, W3CDTF formatted dates
+            HH:MM[:SS][ am/pm]
+            MM/DD/YYYY
+            DD MMMM YYYY
+
+        @type  datetimeString: string
+        @param datetimeString: text to try and parse as more "traditional" date/time text
+        @type  sourceTime:     datetime
+        @param sourceTime:     datetime value to use as the base
+
+        @rtype:  datetime
+        @return: calculated datetime value or current datetime if not parsed
+        """
+        s = string.strip(datetimeString)
+
+          # Given string date is a RFC822 date
+        if sourceTime is None:
+            sourceTime = _parse_date_rfc822(s)
+
+          # Given string date is a W3CDTF date
+        if sourceTime is None:
+            sourceTime = _parse_date_w3dtf(s)
+
+        if sourceTime is None:
+            s = s.lower()
+
+          # Given string is in the format HH:MM(:SS)(am/pm)
+        if self.meridianFlag:
+            if sourceTime is None:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+            else:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+
+            m = self.CRE_TIMEHMS2.search(s)
+            if m is not None:
+                dt = s[:m.start('meridian')].strip()
+                if len(dt) <= 2:
+                    hr  = int(dt)
+                    mn  = 0
+                    sec = 0
+                else:
+                    hr, mn, sec = _extract_time(m)
+
+                if hr == 24:
+                    hr = 0
+
+                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
+                meridian   = m.group('meridian')
+
+                if (re.compile("a").search(meridian)) and hr == 12:
+                    sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
+                if (re.compile("p").search(meridian)) and hr < 12:
+                    sourceTime = (yr, mth, dy, hr+12, mn, sec, wd, yd, isdst)
+
+              # invalid time
+            if hr > 24 or mn > 59 or sec > 59:
+                sourceTime       = time.localtime()
+                self.invalidFlag = True
+
+            self.meridianFlag = False
+
+          # Given string is in the format HH:MM(:SS)
+        if self.timeFlag:
+            if sourceTime is None:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+            else:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+
+            m = self.CRE_TIMEHMS.search(s)
+            if m is not None:
+                hr, mn, sec = _extract_time(m)
+            if hr == 24:
+                hr = 0
+
+            if hr > 24 or mn > 59 or sec > 59:
+                # invalid time
+                sourceTime = time.localtime()
+                self.invalidFlag = True
+            else:
+                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
+
+            self.timeFlag = False
+
+          # Given string is in the format 07/21/2006
+        if self.dateStdFlag:
+            sourceTime       = self.parseDate(s)
+            self.dateStdFlag = False
+
+          # Given string is in the format  "May 23rd, 2005"
+        if self.dateStrFlag:
+            sourceTime       = self.parseDateText(s)
+            self.dateStrFlag = False
+
+          # Given string is a weekday
+        if self.weekdyFlag:
+            yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
+            start = datetime.datetime(yr, mth, dy, hr, mn, sec)
+            wkDy  = self.ptc.WeekDays[s]
+
+            if wkDy > wd:
+                qty    = wkDy - wd
+                target = start + datetime.timedelta(days=qty)
+                wd     = wkDy
+            else:
+                qty    = 6 - wd + wkDy + 1
+                target = start + datetime.timedelta(days=qty)
+                wd     = wkDy
+
+            sourceTime      = target.timetuple()
+            self.weekdyFlag = False
+
+          # Given string is a natural language time string like lunch, midnight, etc
+        if self.timeStrFlag:
+            if sourceTime is None:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
+            else:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+
+            sources = { 'now':       (yr, mth, dy, hr, mn, sec, wd, yd, isdst),
+                        'noon':      (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
+                        'lunch':     (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
+                        'morning':   (yr, mth, dy,  6,  0,   0, wd, yd, isdst),
+                        'breakfast': (yr, mth, dy,  8,  0,   0, wd, yd, isdst),
+                        'dinner':    (yr, mth, dy, 19,  0,   0, wd, yd, isdst),
+                        'evening':   (yr, mth, dy, 18,  0,   0, wd, yd, isdst),
+                        'midnight':  (yr, mth, dy,  0,  0,   0, wd, yd, isdst),
+                        'night':     (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
+                        'tonight':   (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
+                      }
+
+            if s in sources:
+                sourceTime = sources[s]
+            else:
+                sourceTime       = time.localtime()
+                self.invalidFlag = True
+
+            self.timeStrFlag = False
+
+           # Given string is a natural language date string like today, tomorrow..
+        if self.dayStrFlag:
+            if sourceTime is None:
+                sourceTime = time.localtime()
+
+            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
+
+            sources = { 'tomorrow':   1,
+                        'today':      0,
+                        'yesterday': -1,
+                       }
+
+            start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
+            target     = start + datetime.timedelta(days=sources[s])
+            sourceTime = target.timetuple()
+
+            self.dayStrFlag = False
+
+          # Given string is a time string with units like "5 hrs 30 min"
+        if self.unitsFlag:
+            modifier = ''  # TODO
+
+            if sourceTime is None:
+                sourceTime = time.localtime()
+
+            m = self.CRE_UNITS.search(s)
+            if m is not None:
+                units    = m.group('units')
+                quantity = s[:m.start('units')]
+
+            sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
+            self.unitsFlag = False
+
+          # Given string is a time string with single char units like "5 h 30 m"
+        if self.qunitsFlag:
+            modifier = ''  # TODO
+
+            if sourceTime is None:
+                sourceTime = time.localtime()
+
+            m = self.CRE_QUNITS.search(s)
+            if m is not None:
+                units    = m.group('qunits')
+                quantity = s[:m.start('qunits')]
+
+            sourceTime      = self._buildTime(sourceTime, quantity, modifier, units)
+            self.qunitsFlag = False
+
+          # Given string does not match anything
+        if sourceTime is None:
+            sourceTime       = time.localtime()
+            self.invalidFlag = True
+
+        return sourceTime
+
+
+    def parse(self, datetimeString, sourceTime=None):
+        """
+        Splits the L{datetimeString} into tokens, finds the regex patters
+        that match and then calculates a datetime value from the chunks
+
+        if L{sourceTime} is given then the datetime value will be calcualted
+        from that datetime, otherwise from the current datetime.
+
+        @type  datetimeString: string
+        @param datetimeString: datetime text to evaluate
+        @type  sourceTime:     datetime
+        @param sourceTime:     datetime value to use as the base
+
+        @rtype:  tuple
+        @return: tuple of any remaining text and the modified sourceTime
+        """
+        s         = string.strip(datetimeString.lower())
+        dateStr   = ''
+        parseStr  = ''
+        totalTime = sourceTime
+
+        self.invalidFlag = False
+
+        if s == '' :
+            if sourceTime is not None:
+                return (sourceTime, False)
+            else:
+                return (time.localtime(), True)
+
+        while len(s) > 0:
+            flag   = False
+            chunk1 = ''
+            chunk2 = ''
+
+            if _debug:
+                print 'parse (top of loop): [%s][%s]' % (s, parseStr)
+
+            if parseStr == '':
+                # Modifier like next\prev..
+                m = self.CRE_MODIFIER.search(s)
+                if m is not None:
+                    self.modifierFlag = True
+                    if (m.group('modifier') != s):
+                        # capture remaining string
+                        parseStr = m.group('modifier')
+                        chunk1   = string.strip(s[:m.start('modifier')])
+                        chunk2   = string.strip(s[m.end('modifier'):])
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Modifier like from\after\prior..
+                m = self.CRE_MODIFIER2.search(s)
+                if m is not None:
+                    self.modifier2Flag = True
+                    if (m.group('modifier') != s):
+                        # capture remaining string
+                        parseStr = m.group('modifier')
+                        chunk1   = string.strip(s[:m.start('modifier')])
+                        chunk2   = string.strip(s[m.end('modifier'):])
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # String date format
+                m = self.CRE_DATE3.search(s)
+                if m is not None:
+                    self.dateStrFlag = True
+                    if (m.group('date') != s):
+                        # capture remaining string
+                        parseStr = m.group('date')
+                        chunk1   = s[:m.start('date')]
+                        chunk2   = s[m.end('date'):]
+                        s        = '%s %s' % (chunk1, chunk2)
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Standard date format
+                m = self.CRE_DATE.search(s)
+                if m is not None:
+                    self.dateStdFlag = True
+                    if (m.group('date') != s):
+                        # capture remaining string
+                        parseStr = m.group('date')
+                        chunk1   = s[:m.start('date')]
+                        chunk2   = s[m.end('date'):]
+                        s        = '%s %s' % (chunk1, chunk2)
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Natural language day strings
+                m = self.CRE_DAY.search(s)
+                if m is not None:
+                    self.dayStrFlag = True
+                    if (m.group('day') != s):
+                        # capture remaining string
+                        parseStr = m.group('day')
+                        chunk1   = s[:m.start('day')]
+                        chunk2   = s[m.end('day'):]
+                        s        = '%s %s' % (chunk1, chunk2)
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Quantity + Units
+                m = self.CRE_UNITS.search(s)
+                if m is not None:
+                    self.unitsFlag = True
+                    if (m.group('qty') != s):
+                        # capture remaining string
+                        parseStr = m.group('qty')
+                        chunk1   = s[:m.start('qty')].strip()
+                        chunk2   = s[m.end('qty'):].strip()
+
+                        if chunk1[-1:] == '-':
+                            parseStr = '-%s' % parseStr
+                            chunk1   = chunk1[:-1]
+
+                        s    = '%s %s' % (chunk1, chunk2)
+                        flag = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Quantity + Units
+                m = self.CRE_QUNITS.search(s)
+                if m is not None:
+                    self.qunitsFlag = True
+                    if (m.group('qty') != s):
+                        # capture remaining string
+                        parseStr = m.group('qty')
+                        chunk1   = s[:m.start('qty')].strip()
+                        chunk2   = s[m.end('qty'):].strip()
+
+                        if chunk1[-1:] == '-':
+                            parseStr = '-%s' % parseStr
+                            chunk1   = chunk1[:-1]
+
+                        s    = '%s %s' % (chunk1, chunk2)
+                        flag = True
+                    else:
+                        parseStr = s 
+
+            if parseStr == '':
+                # Weekday
+                m = self.CRE_WEEKDAY.search(s)
+                if m is not None:
+                    self.weekdyFlag = True
+                    if (m.group('weekday') != s):
+                        # capture remaining string
+                        parseStr = m.group()
+                        chunk1   = s[:m.start('weekday')]
+                        chunk2   = s[m.end('weekday'):]
+                        s        = '%s %s' % (chunk1, chunk2)
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # Natural language time strings
+                m = self.CRE_TIME.search(s)
+                if m is not None:
+                    self.timeStrFlag = True
+                    if (m.group('time') != s):
+                        # capture remaining string
+                        parseStr = m.group('time')
+                        chunk1   = s[:m.start('time')]
+                        chunk2   = s[m.end('time'):]
+                        s        = '%s %s' % (chunk1, chunk2)
+                        flag     = True
+                    else:
+                        parseStr = s
+
+            if parseStr == '':
+                # HH:MM(:SS) am/pm time strings
+                m = self.CRE_TIMEHMS2.search(s)
+                if m is not None:
+                    self.meridianFlag = True
+                    if m.group('minutes') is not None:
+                        if m.group('seconds') is not None:
+                            parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
+                        else:
+                            parseStr = '%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('meridian'))
+                    else:
+                        parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
+
+                    chunk1 = s[:m.start('hours')]
+                    chunk2 = s[m.end('meridian'):]
+
+                    s    = '%s %s' % (chunk1, chunk2)
+                    flag = True
+
+            if parseStr == '':
+                # HH:MM(:SS) time strings
+                m = self.CRE_TIMEHMS.search(s)
+                if m is not None:
+                    self.timeFlag = True
+                    if m.group('seconds') is not None:
+                        parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
+                        chunk1   = s[:m.start('hours')]
+                        chunk2   = s[m.end('seconds'):]
+                    else:
+                        parseStr = '%s:%s' % (m.group('hours'), m.group('minutes'))
+                        chunk1   = s[:m.start('hours')]
+                        chunk2   = s[m.end('minutes'):]
+
+                    s    = '%s %s' % (chunk1, chunk2)
+                    flag = True
+
+            # if string does not match any regex, empty string to come out of the while loop
+            if not flag:
+                s = ''
+
+            if _debug:
+                print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
+                print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
+                       (self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
+                print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
+                       (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
+
+            # evaluate the matched string
+            if parseStr != '':
+                if self.modifierFlag == True:
+                    t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
+
+                    return self.parse(t, totalTime)
+
+                elif self.modifier2Flag == True:
+                    s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
+                else:
+                    totalTime = self._evalString(parseStr, totalTime)
+                    parseStr  = ''
+
+        # String is not parsed at all
+        if totalTime is None or totalTime == sourceTime:
+            totalTime        = time.localtime()
+            self.invalidFlag = True
+
+        return (totalTime, self.invalidFlag)
+
+
+    def inc(self, source, month=None, year=None):
+        """
+        Takes the given date, or current date if none is passed, and
+        increments it according to the values passed in by month
+        and/or year.
+
+        This routine is needed because the timedelta() routine does
+        not allow for month or year increments.
+
+        @type  source: datetime
+        @param source: datetime value to increment
+        @type  month:  integer
+        @param month:  optional number of months to increment
+        @type  year:   integer
+        @param year:   optional number of years to increment
+
+        @rtype:  datetime
+        @return: L{source} incremented by the number of months and/or years
+        """
+        yr  = source.year
+        mth = source.month
+
+        if year:
+            try:
+                yi = int(year)
+            except ValueError:
+                yi = 0
+
+            yr += yi
+
+        if month:
+            try:
+                mi = int(month)
+            except ValueError:
+                mi = 0
+
+            m = abs(mi)
+            y = m / 12      # how many years are in month increment
+            m = m % 12      # get remaining months
+
+            if mi < 0:
+                mth = mth - m           # sub months from start month
+                if mth < 1:             # cross start-of-year?
+                    y   -= 1            #   yes - decrement year
+                    mth += 12           #         and fix month
+            else:
+                mth = mth + m           # add months to start month
+                if mth > 12:            # cross end-of-year?
+                    y   += 1            #   yes - increment year
+                    mth -= 12           #         and fix month
+
+            yr += y
+
+        d = source.replace(year=yr, month=mth)
+
+        return source + (d - source)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/parsedatetime/pdtc.py	Wed Aug 23 20:42:35 2006 +0200
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+
+"""
+CalendarConstants defines all constants used by parsedatetime.py.
+"""
+
+__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+__author__       = 'Mike Taylor <http://code-bear.com>'
+__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
+                   ]
+
+
+class CalendarConstants:
+    def __init__(self):
+        self.Locale = 'American'
+
+        self.TIMESEP      = ':'
+
+        self.RE_SPECIAL   = r'(?P<special>^[in|on|of|at]+)\s+'
+        self.RE_UNITS     = r'(?P<qty>(-?\d+\s*(?P<units>((hour|hr|minute|min|second|sec|day|dy|week|wk|month|mth|year|yr)s?))))'
+        self.RE_QUNITS    = r'(?P<qty>(-?\d+\s?(?P<qunits>h|m|s|d|w|m|y)(\s|,|$)))'
+        self.RE_MODIFIER  = r'(?P<modifier>(previous|prev|last|next|this|eo|(end\sof)|(in\sa)))'
+        self.RE_MODIFIER2 = r'(?P<modifier>(from|before|after|ago|prior))'
+        self.RE_TIMEHMS   = r'(?P<hours>\d\d?)(?P<tsep>:|)(?P<minutes>\d\d)(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
+        self.RE_TIMEHMS2  = r'(?P<hours>(\d\d?))((?P<tsep>:|)(?P<minutes>(\d\d?))(?:(?P=tsep)(?P<seconds>\d\d?(?:[.,]\d+)?))?)?\s?(?P<meridian>(am|pm|a.m.|p.m.|a|p))'
+        self.RE_DATE      = r'(?P<date>\d+([/.\\]\d+)+)'
+        self.RE_DATE2     = r'[/.\\]'
+        self.RE_DATE3     = r'(?P<date>((?P<mthname>(january|february|march|april|may|june|july|august|september|october|november|december))\s?((?P<day>\d\d?)(\s|rd|st|nd|th|,|$)+)?(?P<year>\d\d\d\d)?))'
+        self.RE_MONTH     = r'(?P<month>((?P<mthname>(january|february|march|april|may|june|july|august|september|october|november|december))(\s?(?P<year>(\d\d\d\d)))?))'
+        self.RE_WEEKDAY   = r'(?P<weekday>(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun))'
+        self.RE_DAY       = r'(?P<day>(today|tomorrow|yesterday))'
+        self.RE_TIME      = r'\s*(?P<time>(morning|breakfast|noon|lunch|evening|midnight|tonight|dinner|night|now))' 
+        self.RE_REMAINING = r'\s+'
+
+        # Regex for date/time ranges
+        self.RE_RTIMEHMS   = r'(\d\d?):(\d\d)(:(\d\d))?'
+        self.RE_RTIMEHMS2  = r'(\d\d?)(:(\d\d?))?(:(\d\d?))?\s?(am|pm|a.m.|p.m.|a|p)'
+        self.RE_RDATE      = r'(\d+([/.\\]\d+)+)'
+        self.RE_RDATE3     = r'((((january|february|march|april|may|june|july|august|september|october|november|december))\s?((\d\d?)(\s|rd|st|nd|th|,|$)+)?(\d\d\d\d)?))'
+        self.DATERNG1 = self.RE_RDATE + r'\s?-\s?' + self.RE_RDATE                      #"06/07/06 - 08/09/06"
+        self.DATERNG2 = self.RE_RDATE3 + r'\s?-\s?' + self.RE_RDATE3                    #"march 31 - june 1st, 2006"
+        self.DATERNG3 = self.RE_RDATE3 + r'\s?' + r'-' + r'\s?(\d\d?)\s?(rd|st|nd|th)?' #"march 1rd -13th"
+        self.TIMERNG1 = self.RE_RTIMEHMS2 + r'\s?-\s?'+ self.RE_RTIMEHMS2  #"4:00:55 pm - 5:90:44 am",'4p-5p'
+        self.TIMERNG2 = self.RE_RTIMEHMS + r'\s?-\s?'+ self.RE_RTIMEHMS    #"4:00 - 5:90 ","4:55:55-3:44:55"
+        self.TIMERNG3 = r'\d\d?\s?-\s?'+ self.RE_RTIMEHMS2                 #"4-5pm "
+
+          # Used to adjust the returned date before/after the source
+
+        self.Modifiers = { 'from':       1,
+                           'before':    -1,
+                           'after':      1,
+                           'ago':        1,
+                           'prior':     -1,
+                           'prev':      -1,
+                           'last':      -1,
+                           'next':       1,
+                           'this':       0,
+                           'previous':  -1,
+                           'in a':       2,
+                           'end of':     0,
+                           'eo':         0,
+                        }
+
+        self.Second =   1
+        self.Minute =  60 * self.Second
+        self.Hour   =  60 * self.Minute
+        self.Day    =  24 * self.Hour
+        self.Week   =   7 * self.Day
+        self.Month  =  30 * self.Day
+        self.Year   = 365 * self.Day
+
+        self.WeekDays = { 'monday':    0,
+                          'mon':       0,
+                          'tuesday':   1,
+                          'tue':       1,
+                          'wednesday': 2,
+                          'wed':       2,
+                          'thursday':  3,
+                          'thu':       3,
+                          'friday':    4,
+                          'fri':       4,
+                          'saturday':  5,
+                          'sat':       5,
+                          'sunday':    6,
+                          'sun':       6,
+                        }
+
+          # dictionary to allow for locale specific text
+          # NOTE: The keys are the localized values - the parsing
+          #       code will be using Target_Text using the values
+          #       extracted *from* the user's input
+
+        self.Target_Text = { 'datesep':   '-',
+                             'timesep':   ':',
+                             'day':       'day',
+                             'dy':        'dy',
+                             'd':         'd',
+                             'week':      'week',
+                             'wk':        'wk',
+                             'w':         'w',
+                             'month':     'month',
+                             'mth':       'mth',
+                             'year':      'year',
+                             'yr':        'yr',
+                             'y':         'y',
+                             'hour':      'hour',
+                             'hr':        'hr',
+                             'h':         'h',
+                             'minute':    'minute',
+                             'min':       'min',
+                             'm':         'm',
+                             'second':    'second',
+                             'sec':       'sec',
+                             's':         's',
+                             'now':       'now',
+                             'noon':      'noon',
+                             'morning':   'morning',
+                             'evening':   'evening',
+                             'breakfast': 'breakfast',
+                             'lunch':     'lunch',
+                             'dinner':    'dinner',
+                             'monday':    'monday',
+                             'mon':       'mon',
+                             'tuesday':   'tuesday',
+                             'tue':       'tue',
+                             'wednesday': 'wednesday',
+                             'wed':       'wed',
+                             'thursday':  'thursday',
+                             'thu':       'thu',
+                             'friday':    'friday',
+                             'fri':       'fri',
+                             'saturday':  'saturday',
+                             'sat':       'sat',
+                             'sunday':    'sunday',
+                             'sun':       'sun',
+                             'january':   'january',
+                             'jan':       'jan',
+                             'febuary':   'febuary',
+                             'feb':       'feb',
+                             'march':     'march',
+                             'mar':       'mar',
+                             'april':     'april',
+                             'apr':       'apr',
+                             'may':       'may',
+                             'may':       'may',
+                             'june':      'june',
+                             'jun':       'jun',
+                             'july':      'july',
+                             'jul':       'jul',
+                             'august':    'august',
+                             'aug':       'aug',
+                             'september': 'september',
+                             'sept':      'sep',
+                             'october':   'october',
+                             'oct':       'oct',
+                             'november':  'november',
+                             'nov':       'nov',
+                             'december':  'december',
+                             'dec':       'dec',
+                           }
+
+          # FIXME: there *has* to be a standard routine that does this
+
+        self.DOW_Text = [self.Target_Text['mon'],
+                         self.Target_Text['tue'],
+                         self.Target_Text['wed'],
+                         self.Target_Text['thu'],
+                         self.Target_Text['fri'],
+                         self.Target_Text['sat'],
+                         self.Target_Text['sun'],
+                        ]
+
+        self.DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
+        self.DaysInMonth = {}
+        self.DaysInMonth[self.Target_Text['january']]   = self.DaysInMonthList[0]
+        self.DaysInMonth[self.Target_Text['febuary']]   = self.DaysInMonthList[1]
+        self.DaysInMonth[self.Target_Text['march']]     = self.DaysInMonthList[2]
+        self.DaysInMonth[self.Target_Text['april']]     = self.DaysInMonthList[3]
+        self.DaysInMonth[self.Target_Text['may']]       = self.DaysInMonthList[4]
+        self.DaysInMonth[self.Target_Text['june']]      = self.DaysInMonthList[5]
+        self.DaysInMonth[self.Target_Text['july']]      = self.DaysInMonthList[6]
+        self.DaysInMonth[self.Target_Text['august']]    = self.DaysInMonthList[7]
+        self.DaysInMonth[self.Target_Text['september']] = self.DaysInMonthList[8]
+        self.DaysInMonth[self.Target_Text['october']]   = self.DaysInMonthList[9]
+        self.DaysInMonth[self.Target_Text['november']]  = self.DaysInMonthList[10]
+        self.DaysInMonth[self.Target_Text['december']]  = self.DaysInMonthList[11]
+
+        self.Month_Text = [ self.Target_Text['january'],
+                            self.Target_Text['febuary'],
+                            self.Target_Text['march'],
+                            self.Target_Text['april'],
+                            self.Target_Text['may'],
+                            self.Target_Text['june'],
+                            self.Target_Text['july'],
+                            self.Target_Text['august'],
+                            self.Target_Text['september'],
+                            self.Target_Text['october'],
+                            self.Target_Text['november'],
+                            self.Target_Text['december'],
+                          ]
+
+
+        self.MthNames = { 'january':    1,
+                          'february':   2,
+                          'march':      3,
+                          'april':      4,
+                          'may' :       5,
+                          'june':       6,
+                          'july':       7,
+                          'august':     8,
+                          'september':  9,
+                          'october':   10,
+                          'november':  11,
+                          'december':  12,
+                        }
+
+
+
+          # This looks hokey - but it is a nice simple way to get
+          # the proper unit value and it has the advantage that
+          # later I can morph it into something localized.
+          # Any trailing s will be removed before lookup.
+
+        self.Units = {}
+        self.Units[self.Target_Text['second']] = self.Second
+        self.Units[self.Target_Text['sec']]    = self.Second
+        self.Units[self.Target_Text['s']]      = self.Second
+        self.Units[self.Target_Text['minute']] = self.Minute
+        self.Units[self.Target_Text['min']]    = self.Minute
+        self.Units[self.Target_Text['m']]      = self.Minute
+        self.Units[self.Target_Text['hour']]   = self.Hour
+        self.Units[self.Target_Text['hr']]     = self.Hour
+        self.Units[self.Target_Text['h']]      = self.Hour
+        self.Units[self.Target_Text['day']]    = self.Day
+        self.Units[self.Target_Text['dy']]     = self.Day
+        self.Units[self.Target_Text['d']]      = self.Day
+        self.Units[self.Target_Text['week']]   = self.Week
+        self.Units[self.Target_Text['wk']]     = self.Week
+        self.Units[self.Target_Text['w']]      = self.Week
+        self.Units[self.Target_Text['month']]  = self.Month
+        self.Units[self.Target_Text['mth']]    = self.Month
+        self.Units[self.Target_Text['year']]   = self.Year
+        self.Units[self.Target_Text['yr']]     = self.Year
+        self.Units[self.Target_Text['y']]      = self.Year
+
+        self.Units_Text = { 'one':        1,
+                            'two':        2,
+                            'three':      3,
+                            'four':       4,
+                            'five':       5,
+                            'six':        6,
+                            'seven':      7,
+                            'eight':      8,
+                            'nine':       9,
+                            'ten':       10,
+                            'eleven':    11,
+                            'twelve':    12,
+                            'thirteen':  13,
+                            'fourteen':  14,
+                            'fifteen':   15,
+                            'sixteen':   16,
+                            'seventeen': 17,
+                            'eighteen':  18,
+                            'nineteen':  19,
+                            'twenty':    20,
+                            'thirty':    30,
+                            'forty':     40,
+                            'fifty':     50,
+                            'sixty':     60,
+                            'seventy':   70,
+                            'eighty':    80,
+                            'ninety':    90,
+                            'half':      0.5,
+                            'quarter':  0.25,
+                         }
+
--- a/MoinMoin/support/xapwrap/index.py	Wed Aug 23 20:40:55 2006 +0200
+++ b/MoinMoin/support/xapwrap/index.py	Wed Aug 23 20:42:35 2006 +0200
@@ -562,7 +562,8 @@
                batchSize = MAX_DOCS_TO_RETURN,
                sortIndex = None, sortAscending = True,
                sortByRelevence = False,
-               valuesWanted = None):
+               valuesWanted = None,
+               collapseKey = None):
         """
         Search an index.
 
@@ -586,6 +587,8 @@
         # the only thing we use sortKey for is to set sort index
         if sortKey is not None:
             sortIndex = self.indexValueMap[sortKey]
+        if collapseKey is not None:
+            collapseKey = self.indexValueMap[collapseKey]
 
         # once you call set_sorting on an Enquire instance, there is no
         # way to resort it by relevence, so we have to open a new
@@ -612,11 +615,16 @@
                     del self._searchSessions[qString]
                     self._searchSessions[qString] = (self.enquire(q), None)
                     enq, lastIndexSortedBy = self._searchSessions[qString]
-            if sortIndex is not None:
+            if sortByRelevence is not None and sortIndex is not None:
+                enq.set_sort_by_relevance_then_value(sortIndex, not sortAscending)
+            elif sortIndex is not None:
                 # It seems that we have the opposite definition of sort ascending
                 # than Xapian so we invert the ascending flag!
                 enq.set_sort_by_value(sortIndex, not sortAscending)
 
+            if collapseKey is not None:
+                enq.set_collapse_key(collapseKey)
+
             self._searchSessions[qString] = (enq, sortIndex)
 
             mset = enq.get_mset(startingIndex, batchSize)
--- a/docs/CHANGES.fpletz	Wed Aug 23 20:40:55 2006 +0200
+++ b/docs/CHANGES.fpletz	Wed Aug 23 20:42:35 2006 +0200
@@ -267,3 +267,5 @@
         xapian_index_history
     * implemented optional history search in advanced ui, defaults to off
 
+SOC END
+