Splits the given C{datetimeString} into tokens, finds the regex
        patterns that match and then calculates a C{struct_time} value from
        the chunks.

        If C{sourceTime} is given then the C{struct_time} value will be
        calculated from that value, otherwise from the current date/time.

        If the C{datetimeString} is parsed and date/time value found then
        the second item of the returned tuple will be a flag to let you know
        what kind of C{struct_time} value is being returned::

            0 = not parsed at all
            1 = parsed as a C{date}
            2 = parsed as a C{time}
            3 = parsed as a C{datetime}

        @type  datetimeString: string
        @param datetimeString: date/time text to evaluate
        @type  sourceTime:     struct_time
        @param sourceTime:     C{struct_time} value to use as the base

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

def parse(self, datetimeString, sourceTime=None):
        """
        Splits the given C{datetimeString} into tokens, finds the regex
        patterns that match and then calculates a C{struct_time} value from
        the chunks.

        If C{sourceTime} is given then the C{struct_time} value will be
        calculated from that value, otherwise from the current date/time.

        If the C{datetimeString} is parsed and date/time value found then
        the second item of the returned tuple will be a flag to let you know
        what kind of C{struct_time} value is being returned::

            0 = not parsed at all
            1 = parsed as a C{date}
            2 = parsed as a C{time}
            3 = parsed as a C{datetime}

        @type  datetimeString: string
        @param datetimeString: date/time text to evaluate
        @type  sourceTime:     struct_time
        @param sourceTime:     C{struct_time} value to use as the base

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

        if sourceTime:
            if isinstance(sourceTime, datetime.datetime):
                if _debug:
                    print 'coercing datetime to timetuple'
                sourceTime = sourceTime.timetuple()
            else:
                if not isinstance(sourceTime, time.struct_time) and \
                   not isinstance(sourceTime, tuple):
                    raise Exception('sourceTime is not a struct_time')

        s         = datetimeString.strip().lower()
        parseStr  = ''
        totalTime = sourceTime

        if s == '' :
            if sourceTime is not None:
                return (sourceTime, self.dateFlag + self.timeFlag)
            else:
                return (time.localtime(), 0)

        self.timeFlag = 0
        self.dateFlag = 0

        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.ptc.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   = s[:m.start('modifier')].strip()
                        chunk2   = s[m.end('modifier'):].strip()
                        flag     = True
                    else:
                        parseStr = s

            if parseStr == '':
                # Modifier like from\after\prior..
                m = self.ptc.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   = s[:m.start('modifier')].strip()
                        chunk2   = s[m.end('modifier'):].strip()
                        flag     = True
                    else:
                        parseStr = s

            if parseStr == '':
                valid_date = False
                for match in self.ptc.CRE_DATE3.finditer(s):
                    # to prevent "HH:MM(:SS) time strings" expressions from triggering
                    # this regex, we checks if the month field exists in the searched 
                    # expression, if it doesn't exist, the date field is not valid
                    if match.group('mthname'):
                        m = self.ptc.CRE_DATE3.search(s, match.start())
                        valid_date = True
                        break

                # String date format
                if valid_date:
                    self.dateStrFlag = True
                    self.dateFlag    = 1
                    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.ptc.CRE_DATE.search(s)
                if m is not None:
                    self.dateStdFlag = True
                    self.dateFlag    = 1
                    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.ptc.CRE_DAY.search(s)
                if m is not None:
                    self.dayStrFlag = True
                    self.dateFlag   = 1
                    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.ptc.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.ptc.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.ptc.CRE_WEEKDAY.search(s)
                if m is not None:
                    gv = m.group('weekday')
                    if s not in self.ptc.dayOffsets:
                        self.weekdyFlag = True
                        self.dateFlag   = 1
                        if (gv != s):
                            # capture remaining string
                            parseStr = gv
                            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.ptc.CRE_TIME.search(s)
                if m is not None:
                    self.timeStrFlag = True
                    self.timeFlag    = 2
                    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.ptc.CRE_TIMEHMS2.search(s)
                if m is not None:
                    self.meridianFlag = True
                    self.timeFlag     = 2
                    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.ptc.CRE_TIMEHMS.search(s)
                if m is not None:
                    self.timeStdFlag = True
                    self.timeFlag    = 2
                    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 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
                       (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, 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)
                    # t is the unparsed part of the chunks.
                    # If it is not date/time, return current
                    # totalTime as it is; else return the output
                    # after parsing t.
                    if (t != '') and (t != None):
                        tempDateFlag       = self.dateFlag
                        tempTimeFlag       = self.timeFlag
                        (totalTime2, flag) = self.parse(t, totalTime)

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

                            return (totalTime, self.dateFlag + self.timeFlag)
                        else:
                            return (totalTime2, self.dateFlag + self.timeFlag)

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

                    if invalidFlag == True:
                        self.dateFlag = 0
                        self.timeFlag = 0

                else:
                    totalTime = self._evalString(parseStr, totalTime)
                    parseStr  = ''

        # String is not parsed at all
        if totalTime is None or totalTime == sourceTime:
            totalTime     = time.localtime()
            self.dateFlag = 0
            self.timeFlag = 0

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