changeset 5185:0a6fe22644e3

updated parsedatetime to 0.8.7
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 04 Oct 2009 19:32:01 +0200
parents 0970ed47d2cd
children edd5719d5f07
files MoinMoin/support/parsedatetime/__init__.py MoinMoin/support/parsedatetime/parsedatetime.py MoinMoin/support/parsedatetime/parsedatetime_consts.py
diffstat 3 files changed, 1291 insertions(+), 481 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/parsedatetime/__init__.py	Sun Oct 04 17:47:54 2009 +0200
+++ b/MoinMoin/support/parsedatetime/__init__.py	Sun Oct 04 19:32:01 2009 +0200
@@ -1,6 +1,17 @@
-version = '0.7'
-author  = 'Mike Taylor <http://code-bear.com>'
-license = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
+"""
+parsedatetime.py contains the C{Calendar} class where the C{parse()}
+method can be found.
+
+parsedatetime_consts.py contains the C{Constants} class that builds the
+various regex values using locale information if available.
+"""
+
+version = '0.8.7'
+author  = 'Mike Taylor and Darshana Chhajed'
+license = """
+Copyright (c) 2004-2008 Mike Taylor
+Copyright (c) 2006-2008 Darshana Chhajed
+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.
--- a/MoinMoin/support/parsedatetime/parsedatetime.py	Sun Oct 04 17:47:54 2009 +0200
+++ b/MoinMoin/support/parsedatetime/parsedatetime.py	Sun Oct 04 19:32:01 2009 +0200
@@ -4,7 +4,10 @@
 Parse human-readable date/time text.
 """
 
-__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
+__license__ = """
+Copyright (c) 2004-2008 Mike Taylor
+Copyright (c) 2006-2008 Darshana Chhajed
+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.
@@ -18,20 +21,20 @@
 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 re
+import time
+import datetime
+import rfc822
 import parsedatetime_consts
 
 
 # Copied from feedparser.py
-# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# 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'))
@@ -57,7 +60,7 @@
                     day = 31
             elif jday < julian:
                 if day + diff < 28:
-                   day = day + diff
+                    day = day + diff
                 else:
                     month = month + 1
         return year, month, day
@@ -74,8 +77,9 @@
             day = 1
     return year, month, day
 
-# Copied from feedparser.py 
-# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# 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:
@@ -94,14 +98,15 @@
 
 
 # Copied from feedparser.py
-# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# 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
+#   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
@@ -142,7 +147,8 @@
 
 
 # Copied from feedparser.py
-# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
+# Universal Feedparser
+# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 # Modified to return a tuple instead of mktime
 #
 def _parse_date_rfc822(dateString):
@@ -164,7 +170,9 @@
 
 # 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}
+_additional_timezones = {'AT': -400, 'ET': -500,
+                         'CT': -600, 'MT': -700,
+                         'PT': -800}
 rfc822._timezones.update(_additional_timezones)
 
 
@@ -176,13 +184,13 @@
 
     def __init__(self, constants=None):
         """
-        Default constructor for the Calendar class.
+        Default constructor for the L{Calendar} class.
 
         @type  constants: object
-        @param constants: Instance of the class L{CalendarConstants}
+        @param constants: Instance of the class L{parsedatetime_consts.Constants}
 
         @rtype:  object
-        @return: Calendar instance
+        @return: L{Calendar} instance
         """
           # if a constants reference is not included, use default
         if constants is None:
@@ -190,40 +198,10 @@
         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.timeStdFlag   = False  # 5:50 
         self.meridianFlag  = False  # am/pm
         self.dayStrFlag    = False  # tomorrow/yesterday/today/..
         self.timeStrFlag   = False  # lunch/noon/breakfast/...
@@ -232,6 +210,9 @@
         self.unitsFlag     = False  # hrs/weeks/yrs/min/..
         self.qunitsFlag    = False  # h/m/t/d..
 
+        self.timeFlag      = 0
+        self.dateFlag      = 0
+
 
     def _convertUnitAsWords(self, unitText):
         """
@@ -244,7 +225,7 @@
         Two thousand twenty five = 2025
 
         @type  unitText: string
-        @param unitText: number string
+        @param unitText: number text to convert
 
         @rtype:  integer
         @return: numerical value of unitText
@@ -255,8 +236,8 @@
 
     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
+        Take C{quantity}, C{modifier} and C{unit} strings and convert them into values.
+        After converting, calcuate the time and return the adjusted sourceTime.
 
         @type  source:   time
         @param source:   time to use as the base (or source)
@@ -267,8 +248,8 @@
         @type  units:    string
         @param units:    unit of the quantity (i.e. hours, days, months, etc)
 
-        @rtype:  timetuple
-        @return: timetuple of the calculated time
+        @rtype:  struct_time
+        @return: C{struct_time} of the calculated time
         """
         if _debug:
             print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
@@ -279,7 +260,7 @@
         if quantity is None:
             quantity = ''
         else:
-            quantity = string.strip(quantity)
+            quantity = quantity.strip()
 
         if len(quantity) == 0:
             qty = 1
@@ -297,81 +278,127 @@
 
         # plurals are handled by regex's (could be a bug tho)
 
-        (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
+        (yr, mth, dy, hr, mn, sec, _, _, _) = source
 
         start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
         target = start
 
         if units.startswith('y'):
-            target = self.inc(start, year=qty)
+            target        = self.inc(start, year=qty)
+            self.dateFlag = 1
         elif units.endswith('th') or units.endswith('ths'):
-            target = self.inc(start, month=qty)
+            target        = self.inc(start, month=qty)
+            self.dateFlag = 1
         else:
             if units.startswith('d'):
-                target = start + datetime.timedelta(days=qty)
+                target        = start + datetime.timedelta(days=qty)
+                self.dateFlag = 1
             elif units.startswith('h'):
-                target = start + datetime.timedelta(hours=qty)
+                target        = start + datetime.timedelta(hours=qty)
+                self.timeFlag = 2
             elif units.startswith('m'):
-                target = start + datetime.timedelta(minutes=qty)
+                target        = start + datetime.timedelta(minutes=qty)
+                self.timeFlag = 2
             elif units.startswith('s'):
-                target = start + datetime.timedelta(seconds=qty)
+                target        = start + datetime.timedelta(seconds=qty)
+                self.timeFlag = 2
             elif units.startswith('w'):
-                target = start + datetime.timedelta(weeks=qty)
-
-        if target != start:
-            self.invalidFlag = False
+                target        = start + datetime.timedelta(weeks=qty)
+                self.dateFlag = 1
 
         return target.timetuple()
 
 
     def parseDate(self, dateString):
         """
-        Parses strings like 05/28/200 or 04.21
+        Parse short-form date strings::
+
+            '05/28/2006' or '04.21'
 
         @type  dateString: string
-        @param dateString: text to convert to a datetime
+        @param dateString: text to convert to a C{datetime}
 
-        @rtype:  datetime
-        @return: calculated datetime value of dateString
+        @rtype:  struct_time
+        @return: calculated C{struct_time} value of dateString
         """
         yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
+        # values pulled from regex's will be stored here and later
+        # assigned to mth, dy, yr based on information from the locale
+        # -1 is used as the marker value because we want zero values
+        # to be passed thru so they can be flagged as errors later
+        v1 = -1
+        v2 = -1
+        v3 = -1
+
         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)
+        m = self.ptc.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
+            v1    = int(s[:index])
+            s     = s[index + 1:]
+
+        m = self.ptc.CRE_DATE2.search(s)
+        if m is not None:
+            index = m.start()
+            v2    = int(s[:index])
+            v3    = int(s[index + 1:])
         else:
-            dy = int(string.strip(s))
+            v2 = int(s.strip())
 
-        if (mth > 0 and mth <= 12) and (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
+        v = [ v1, v2, v3 ]
+        d = { 'm': mth, 'd': dy, 'y': yr }
+
+        for i in range(0, 3):
+            n = v[i]
+            c = self.ptc.dp_order[i]
+            if n >= 0:
+                d[c] = n
+
+        # if the year is not specified and the date has already
+        # passed, increment the year
+        if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
+            yr = d['y'] + 1
+        else:
+            yr  = d['y']
+
+        mth = d['m']
+        dy  = d['d']
+
+        # birthday epoch constraint
+        if yr < self.ptc.BirthdayEpoch:
+            yr += 2000
+        elif yr < 100:
+            yr += 1900
+
+        if _debug:
+            print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr)
+
+        if (mth > 0 and mth <= 12) and \
+           (dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)):
             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
+            self.dateFlag = 0
+            self.timeFlag = 0
+            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"
+        Parse long-form date strings::
+
+            'May 31st, 2006'
+            'Jan 1st'
+            'July 2006'
 
         @type  dateString: string
         @param dateString: text to convert to a datetime
 
-        @rtype:  datetime
-        @return: calculated datetime value of dateString
+        @rtype:  struct_time
+        @return: calculated C{struct_time} value of dateString
         """
         yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
@@ -379,7 +406,7 @@
         currentDy  = dy
 
         s   = dateString.lower()
-        m   = self.CRE_DATE3.search(s)
+        m   = self.ptc.CRE_DATE3.search(s)
         mth = m.group('mthname')
         mth = self.ptc.MonthOffsets[mth]
 
@@ -390,32 +417,41 @@
 
         if m.group('year') !=  None:
             yr = int(m.group('year'))
+
+            # birthday epoch constraint
+            if yr < self.ptc.BirthdayEpoch:
+                yr += 2000
+            elif yr < 100:
+                yr += 1900
+
         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 > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
-            sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
+        if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr):
+            sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
         else:
-              # Return current time if date string is invalid
-            self.invalidFlag = True
-            sourceTime       = time.localtime()
+            # Return current time if date string is invalid
+            self.dateFlag = 0
+            self.timeFlag = 0
+            sourceTime    = time.localtime()
 
         return sourceTime
 
 
     def evalRanges(self, datetimeString, sourceTime=None):
         """
-        Evaluates the strings with time or date ranges
+        Evaluate the C{datetimeString} text and determine if
+        it represents a date or time range.
 
         @type  datetimeString: string
         @param datetimeString: datetime text to evaluate
-        @type  sourceTime:     datetime
-        @param sourceTime:     datetime value to use as the base
+        @type  sourceTime:     struct_time
+        @param sourceTime:     C{struct_time} value to use as the base
 
         @rtype:  tuple
-        @return: tuple of the start datetime, end datetime and the invalid flag
+        @return: tuple of: start datetime, end datetime and the invalid flag
         """
         startTime = ''
         endTime   = ''
@@ -423,31 +459,39 @@
         endDate   = ''
         rangeFlag = 0
 
-        s = string.strip(datetimeString.lower())
+        s = datetimeString.strip().lower()
 
-        m = self.CRE_TIMERNG1.search(s)
+        if self.ptc.rangeSep in s:
+            s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep)
+            s = s.replace('  ', ' ')
+
+        m = self.ptc.CRE_TIMERNG1.search(s)
         if m is not None:
             rangeFlag = 1
         else:
-            m = self.CRE_TIMERNG2.search(s)
+            m = self.ptc.CRE_TIMERNG2.search(s)
             if m is not None:
                 rangeFlag = 2
             else:
-                m = self.CRE_TIMERNG3.search(s)
+                m = self.ptc.CRE_TIMERNG4.search(s)
                 if m is not None:
-                    rangeFlag = 3
+                    rangeFlag = 7
                 else:
-                    m = self.CRE_DATERNG1.search(s)
+                    m = self.ptc.CRE_TIMERNG3.search(s)
                     if m is not None:
-                        rangeFlag = 4
+                        rangeFlag = 3
                     else:
-                        m = self.CRE_DATERNG2.search(s)
+                        m = self.ptc.CRE_DATERNG1.search(s)
                         if m is not None:
-                            rangeFlag = 5
+                            rangeFlag = 4
                         else:
-                            m = self.CRE_DATERNG3.search(s)
+                            m = self.ptc.CRE_DATERNG2.search(s)
                             if m is not None:
-                                rangeFlag = 6
+                                rangeFlag = 5
+                            else:
+                                m = self.ptc.CRE_DATERNG3.search(s)
+                                if m is not None:
+                                    rangeFlag = 6
 
         if _debug:
             print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
@@ -463,37 +507,32 @@
 
                 sourceTime, flag = self.parse(s, sourceTime)
 
-                if flag == True:
+                if flag == 0:
                     sourceTime = None
             else:
                 parseStr = s
 
         if rangeFlag == 1:
-            # FIXME hardcoded seperator
-            m                = re.search('-', parseStr)
+            m                = re.search(self.ptc.rangeSep, 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)
+            if (eflag != 0)  and (sflag != 0):
+                return (startTime, endTime, 2)
 
         elif rangeFlag == 2:
-            # FIXME hardcoded seperator
-            m                = re.search('-', parseStr)
-            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
+            m                = re.search(self.ptc.rangeSep, 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)
+            if (eflag != 0)  and (sflag != 0):
+                return (startTime, endTime, 2)
 
-        elif rangeFlag == 3:
-            # FIXME hardcoded seperator
-            m = re.search('-', parseStr)
-
+        elif rangeFlag == 3 or rangeFlag == 7:
+            m = re.search(self.ptc.rangeSep, parseStr)
             # capturing the meridian from the end time
-            # FIXME hardcoded meridian
             if self.ptc.usesMeridian:
-                ampm = re.search('a', parseStr)
+                ampm = re.search(self.ptc.am[0], parseStr)
 
                 # appending the meridian to the start time
                 if ampm is not None:
@@ -505,54 +544,51 @@
 
             endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
 
-            if eflag is False and sflag is False:
-                return (startTime, endTime, False)
+            if (eflag != 0)  and (sflag != 0):
+                return (startTime, endTime, 2)
 
         elif rangeFlag == 4:
-            # FIXME hardcoded seperator
-            m                = re.search('-', parseStr)
+            m                = re.search(self.ptc.rangeSep, 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)
+            if (eflag != 0)  and (sflag != 0):
+                return (startDate, endDate, 1)
 
         elif rangeFlag == 5:
-            # FIXME hardcoded seperator
-            m       = re.search('-', parseStr)
+            m       = re.search(self.ptc.rangeSep, parseStr)
             endDate = parseStr[(m.start() + 1):]
 
             # capturing the year from the end date
-            date    = self.CRE_DATE3.search(endDate)
+            date    = self.ptc.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)
+                startDate = (parseStr[:m.start()]).strip()
+                date      = self.ptc.CRE_DATE3.search(startDate)
                 startYear = date.group('year')
 
                 if startYear is None:
-                    startDate += endYear
+                    startDate = 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)
+            if (eflag != 0)  and (sflag != 0):
+                return (startDate, endDate, 1)
 
         elif rangeFlag == 6:
-            # FIXME hardcoded seperator
-            m = re.search('-', parseStr)
+            m = re.search(self.ptc.rangeSep, parseStr)
 
             startDate = parseStr[:m.start()]
 
             # capturing the month from the start date
-            mth = self.CRE_DATE3.search(startDate)
+            mth = self.ptc.CRE_DATE3.search(startDate)
             mth = mth.group('mthname')
 
             # appending the month name to the end date
@@ -561,20 +597,92 @@
             startDate, sflag = self.parse(startDate, sourceTime)
             endDate, eflag   = self.parse(endDate, sourceTime)
 
-            if eflag is False and sflag is False:
-                return (startDate, endDate, False)
+            if (eflag != 0)  and (sflag != 0):
+                return (startDate, endDate, 1)
         else:
             # if range is not found
             sourceTime = time.localtime()
 
-            return (sourceTime, sourceTime, True)
+            return (sourceTime, sourceTime, 0)
+
+
+    def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle):
+        """
+        Based on the C{style} and C{currentDayStyle} determine what
+        day-of-week value is to be returned.
+
+        @type  wd:              integer
+        @param wd:              day-of-week value for the current day
+        @type  wkdy:            integer
+        @param wkdy:            day-of-week value for the parsed day
+        @type  offset:          integer
+        @param offset:          offset direction for any modifiers (-1, 0, 1)
+        @type  style:           integer
+        @param style:           normally the value set in C{Constants.DOWParseStyle}
+        @type  currentDayStyle: integer
+        @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}
+
+        @rtype:  integer
+        @return: calculated day-of-week
+        """
+        if offset == 1:
+            # modifier is indicating future week eg: "next".
+            # DOW is calculated as DOW of next week
+            diff = 7 - wd + wkdy
+
+        elif offset == -1:
+            # modifier is indicating past week eg: "last","previous"
+            # DOW is calculated as DOW of previous week
+            diff = wkdy - wd - 7
+
+        elif offset == 0:
+            # modifier is indiacting current week eg: "this"
+            # DOW is calculated as DOW of this week
+            diff = wkdy - wd
+
+        elif offset == 2:
+            # no modifier is present.
+            # i.e. string to be parsed is just DOW
+            if style == 1:
+                # next occurance of the DOW is calculated
+                if currentDayStyle == True:
+                    if wkdy >= wd:
+                        diff = wkdy - wd
+                    else:
+                        diff = 7 - wd + wkdy
+                else:
+                    if wkdy > wd:
+                        diff = wkdy - wd
+                    else:
+                        diff = 7 - wd + wkdy
+
+            elif style == -1:
+                # last occurance of the DOW is calculated
+                if currentDayStyle == True:
+                    if wkdy <= wd:
+                        diff = wkdy - wd
+                    else:
+                        diff = wkdy - wd - 7
+                else:
+                    if wkdy < wd:
+                        diff = wkdy - wd
+                    else:
+                        diff = wkdy - wd - 7
+            else:
+                # occurance of the DOW in the current week is calculated
+                diff = wkdy - wd
+
+        if _debug:
+            print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
+
+        return diff
 
 
     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
+        Evaluate the C{modifier} string and following text (passed in
+        as C{chunk1} and C{chunk2}) and if they match any known modifiers
+        calculate the delta and apply it to C{sourceTime}.
 
         @type  modifier:   string
         @param modifier:   modifier text to apply to sourceTime
@@ -582,11 +690,11 @@
         @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
+        @type  sourceTime: struct_time
+        @param sourceTime: C{struct_time} value to use as the base
 
         @rtype:  tuple
-        @return: tuple of any remaining text and the modified sourceTime
+        @return: tuple of: remaining text and the modified sourceTime
         """
         offset = self.ptc.Modifiers[modifier]
 
@@ -595,8 +703,9 @@
         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)
+        # capture the units after the modifier and the remaining
+        # string after the unit
+        m = self.ptc.CRE_REMAINING.search(chunk2)
         if m is not None:
             index  = m.start() + 1
             unit   = chunk2[:m.start()]
@@ -608,14 +717,16 @@
         flag = False
 
         if unit == 'month' or \
-           unit == 'mth':
+           unit == 'mth' or \
+           unit == 'm':
             if offset == 0:
-                dy         = self.ptc.DaysInMonthList[mth - 1]
+                dy         = self.ptc.daysInMonth(mth, yr)
                 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]
+                # if day is the last day of the month, calculate the last day
+                # of the next month
+                if dy == self.ptc.daysInMonth(mth, yr):
+                    dy = self.ptc.daysInMonth(mth + 1, yr)
 
                 start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
                 target     = self.inc(start, month=1)
@@ -626,6 +737,7 @@
                 sourceTime = target.timetuple()
 
             flag = True
+            self.dateFlag = 1
 
         if unit == 'week' or \
              unit == 'wk' or \
@@ -641,13 +753,15 @@
             else:
                 return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
 
-            flag = True
+            flag          = True
+            self.dateFlag = 1
 
         if unit == 'day' or \
             unit == 'dy' or \
             unit == 'd':
             if offset == 0:
-                sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
+                sourceTime    = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
+                self.timeFlag = 2
             elif offset == 2:
                 start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
                 target     = start + datetime.timedelta(days=1)
@@ -657,7 +771,8 @@
                 target     = start + datetime.timedelta(days=offset)
                 sourceTime = target.timetuple()
 
-            flag = True
+            flag          = True
+            self.dateFlag = 1
 
         if unit == 'hour' or \
            unit == 'hr':
@@ -668,7 +783,8 @@
                 target     = start + datetime.timedelta(hours=offset)
                 sourceTime = target.timetuple()
 
-            flag = True
+            flag          = True
+            self.timeFlag = 2
 
         if unit == 'year' or \
              unit == 'yr' or \
@@ -680,41 +796,67 @@
             else:
                 sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
 
-            flag = True
+            flag          = True
+            self.dateFlag = 1
 
         if flag == False:
-            m = self.CRE_WEEKDAY.match(unit)
+            m = self.ptc.CRE_WEEKDAY.match(unit)
             if m is not None:
-                wkdy = m.group()
-                wkdy = self.ptc.WeekdayOffsets[wkdy]
+                wkdy          = m.group()
+                self.dateFlag = 1
 
-                if offset == 0:
-                    diff       = wkdy - wd
+                if modifier == 'eod':
+                    # Calculate the  upcoming weekday
+                    self.modifierFlag = False
+                    (sourceTime, _)   = self.parse(wkdy, sourceTime)
+                    sources           = self.ptc.buildSources(sourceTime)
+                    self.timeFlag     = 2
+
+                    if modifier in sources:
+                        sourceTime = sources[modifier]
+
+                else:
+                    wkdy       = self.ptc.WeekdayOffsets[wkdy]
+                    diff       = self._CalculateDOWDelta(wd, wkdy, offset,
+                                                         self.ptc.DOWParseStyle,
+                                                         self.ptc.CurrentDOWParseStyle)
                     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
+                flag          = True
+                self.dateFlag = 1
 
         if not flag:
-            m = self.CRE_TIME.match(unit)
+            m = self.ptc.CRE_TIME.match(unit)
             if m is not None:
-                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
+                self.modifierFlag = False
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
+
                 start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
                 target     = start + datetime.timedelta(days=offset)
                 sourceTime = target.timetuple()
-
-                flag              = True
+                flag       = True
+            else:
                 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
+                # check if the remaining text is parsable and if so,
+                # use it as the base time for the modifier source time
+                t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
+
+                if flag2 != 0:
+                    sourceTime = t
+
+                sources = self.ptc.buildSources(sourceTime)
+
+                if modifier in sources:
+                    sourceTime    = sources[modifier]
+                    flag          = True
+                    self.timeFlag = 2
+
+        # if the word after next is a number, the string is more than likely
+        # to be "next 4 hrs" which we will 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
@@ -724,66 +866,81 @@
 
         self.modifierFlag = False
 
-        return '%s %s' % (chunk1, chunk2), sourceTime
-
+        #return '%s %s' % (chunk1, chunk2), sourceTime
+        return '%s' % 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
+        Evaluate the C{modifier} string and following text (passed in
+        as C{chunk1} and C{chunk2}) and if they match any known modifiers
+        calculate the delta and apply it to C{sourceTime}.
 
         @type  modifier:   string
-        @param modifier:   modifier text to apply to sourceTime
+        @param modifier:   modifier text to apply to C{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
+        @type  sourceTime: struct_time
+        @param sourceTime: C{struct_time} value to use as the base
 
         @rtype:  tuple
-        @return: tuple of any remaining text and the modified sourceTime
+        @return: tuple of: 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)
+        # 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 chunk2 != '':
+            if offset < 0:
+                m = re.match(digit, chunk2.strip())
+                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)
+            sourceTime, flag1 = self.parse(chunk2, sourceTime)
+            if flag1 == 0:
+                flag1 = True
+            else:
+                flag1 = False
+            flag2 = False
+        else:
+            flag1 = False
 
         if chunk1 != '':
             if offset < 0:
-                m = re.match(digit, string.strip(chunk1))
+                m = re.search(digit, chunk1.strip())
                 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)
+            tempDateFlag       = self.dateFlag
+            tempTimeFlag       = self.timeFlag
+            sourceTime2, flag2 = self.parse(chunk1, sourceTime)
+        else:
+            return sourceTime, (flag1 and flag2)
 
-        return '', sourceTime
+        # if chunk1 is not a datetime and chunk2 is then do not use datetime
+        # value returned by parsing chunk1
+        if not (flag1 == False and flag2 == 0):
+            sourceTime = sourceTime2
+        else:
+            self.timeFlag = tempTimeFlag
+            self.dateFlag = tempDateFlag
+
+        return sourceTime, (flag1 and flag2)
 
 
     def _evalString(self, datetimeString, sourceTime=None):
@@ -797,35 +954,50 @@
             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
+        @param datetimeString: text to try and parse as more "traditional"
+                               date/time text
+        @type  sourceTime:     struct_time
+        @param sourceTime:     C{struct_time} value to use as the base
 
         @rtype:  datetime
-        @return: calculated datetime value or current datetime if not parsed
+        @return: calculated C{struct_time} value or current C{struct_time}
+                 if not parsed
         """
-        s   = string.strip(datetimeString)
+        s   = datetimeString.strip()
         now = time.localtime()
 
-          # Given string date is a RFC822 date
+        # 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 not None:
+                (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
+                self.dateFlag = 1
+
+                if (hr != 0) and (mn != 0) and (sec != 0):
+                    self.timeFlag = 2
+
+                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
+
+        # Given string date is a W3CDTF date
         if sourceTime is None:
             sourceTime = _parse_date_w3dtf(s)
 
+            if sourceTime is not None:
+                self.dateFlag = 1
+                self.timeFlag = 2
+
         if sourceTime is None:
             s = s.lower()
 
-          # Given string is in the format HH:MM(:SS)(am/pm)
+        # 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) = now
             else:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
-            m = self.CRE_TIMEHMS2.search(s)
+            m = self.ptc.CRE_TIMEHMS2.search(s)
             if m is not None:
                 dt = s[:m.start('meridian')].strip()
                 if len(dt) <= 2:
@@ -851,19 +1023,20 @@
 
               # invalid time
             if hr > 24 or mn > 59 or sec > 59:
-                sourceTime       = now
-                self.invalidFlag = True
+                sourceTime    = now
+                self.dateFlag = 0
+                self.timeFlag = 0
 
             self.meridianFlag = False
 
           # Given string is in the format HH:MM(:SS)
-        if self.timeFlag:
+        if self.timeStdFlag:
             if sourceTime is None:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
             else:
                 (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
-            m = self.CRE_TIMEHMS.search(s)
+            m = self.ptc.CRE_TIMEHMS.search(s)
             if m is not None:
                 hr, mn, sec = _extract_time(m)
             if hr == 24:
@@ -871,58 +1044,64 @@
 
             if hr > 24 or mn > 59 or sec > 59:
                 # invalid time
-                sourceTime       = now
-                self.invalidFlag = True
+                sourceTime    = now
+                self.dateFlag = 0
+                self.timeFlag = 0
             else:
                 sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
-            self.timeFlag = False
+            self.timeStdFlag = False
 
-          # Given string is in the format 07/21/2006
+        # 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"
+        # Given string is in the format  "May 23rd, 2005"
         if self.dateStrFlag:
             sourceTime       = self.parseDateText(s)
             self.dateStrFlag = False
 
-          # Given string is a weekday
+        # Given string is a weekday
         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.WeekdayOffsets[s]
+            wkdy  = self.ptc.WeekdayOffsets[s]
 
-            if wkDy > wd:
-                qty    = wkDy - wd
-                target = start + datetime.timedelta(days=qty)
-                wd     = wkDy
+            if wkdy > wd:
+                qty = self._CalculateDOWDelta(wd, wkdy, 2,
+                                              self.ptc.DOWParseStyle,
+                                              self.ptc.CurrentDOWParseStyle)
             else:
-                qty    = 6 - wd + wkDy + 1
-                target = start + datetime.timedelta(days=qty)
-                wd     = wkDy
+                qty = self._CalculateDOWDelta(wd, wkdy, 2,
+                                              self.ptc.DOWParseStyle,
+                                              self.ptc.CurrentDOWParseStyle)
+
+            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
+        # Given string is a natural language time string like
+        # lunch, midnight, etc
         if self.timeStrFlag:
             if s in self.ptc.re_values['now']:
                 sourceTime = now
             else:
-                sources = self.ptc.buildSources(now)
+                sources = self.ptc.buildSources(sourceTime)
 
                 if s in sources:
                     sourceTime = sources[s]
                 else:
-                    sourceTime       = now
-                    self.invalidFlag = True
+                    sourceTime    = now
+                    self.dateFlag = 0
+                    self.timeFlag = 0
 
             self.timeStrFlag = False
 
-           # Given string is a natural language date string like today, tomorrow..
+        # Given string is a natural language date string like today, tomorrow..
         if self.dayStrFlag:
             if sourceTime is None:
                 sourceTime = now
@@ -940,14 +1119,14 @@
 
             self.dayStrFlag = False
 
-          # Given string is a time string with units like "5 hrs 30 min"
+        # Given string is a time string with units like "5 hrs 30 min"
         if self.unitsFlag:
             modifier = ''  # TODO
 
             if sourceTime is None:
                 sourceTime = now
 
-            m = self.CRE_UNITS.search(s)
+            m = self.ptc.CRE_UNITS.search(s)
             if m is not None:
                 units    = m.group('units')
                 quantity = s[:m.start('units')]
@@ -955,14 +1134,14 @@
             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"
+        # 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 = now
 
-            m = self.CRE_QUNITS.search(s)
+            m = self.ptc.CRE_QUNITS.search(s)
             if m is not None:
                 units    = m.group('qunits')
                 quantity = s[:m.start('qunits')]
@@ -972,40 +1151,62 @@
 
           # Given string does not match anything
         if sourceTime is None:
-            sourceTime       = now
-            self.invalidFlag = True
+            sourceTime    = now
+            self.dateFlag = 0
+            self.timeFlag = 0
 
         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
+        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 L{sourceTime} is given then the datetime value will be calcualted
-        from that datetime, otherwise from the current datetime.
+        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: datetime text to evaluate
-        @type  sourceTime:     datetime
-        @param sourceTime:     datetime value to use as the base
+        @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 any remaining text and the modified sourceTime
+        @return: tuple of: modified C{sourceTime} and the result flag
         """
-        s         = string.strip(datetimeString.lower())
-        dateStr   = ''
+
+        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
 
-        self.invalidFlag = False
-
         if s == '' :
             if sourceTime is not None:
-                return (sourceTime, False)
+                return (sourceTime, self.dateFlag + self.timeFlag)
             else:
-                return (time.localtime(), True)
+                return (time.localtime(), 0)
+
+        self.timeFlag = 0
+        self.dateFlag = 0
 
         while len(s) > 0:
             flag   = False
@@ -1017,37 +1218,47 @@
 
             if parseStr == '':
                 # Modifier like next\prev..
-                m = self.CRE_MODIFIER.search(s)
+                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   = string.strip(s[:m.start('modifier')])
-                        chunk2   = string.strip(s[m.end('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.CRE_MODIFIER2.search(s)
+                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   = string.strip(s[:m.start('modifier')])
-                        chunk2   = string.strip(s[m.end('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
-                m = self.CRE_DATE3.search(s)
-                if m is not None:
+                if valid_date:
                     self.dateStrFlag = True
+                    self.dateFlag    = 1
                     if (m.group('date') != s):
                         # capture remaining string
                         parseStr = m.group('date')
@@ -1060,9 +1271,10 @@
 
             if parseStr == '':
                 # Standard date format
-                m = self.CRE_DATE.search(s)
+                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')
@@ -1075,9 +1287,10 @@
 
             if parseStr == '':
                 # Natural language day strings
-                m = self.CRE_DAY.search(s)
+                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')
@@ -1090,7 +1303,7 @@
 
             if parseStr == '':
                 # Quantity + Units
-                m = self.CRE_UNITS.search(s)
+                m = self.ptc.CRE_UNITS.search(s)
                 if m is not None:
                     self.unitsFlag = True
                     if (m.group('qty') != s):
@@ -1110,9 +1323,10 @@
 
             if parseStr == '':
                 # Quantity + Units
-                m = self.CRE_QUNITS.search(s)
+                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')
@@ -1130,24 +1344,28 @@
 
             if parseStr == '':
                 # Weekday
-                m = self.CRE_WEEKDAY.search(s)
+                m = self.ptc.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
+                    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.CRE_TIME.search(s)
+                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')
@@ -1160,16 +1378,23 @@
 
             if parseStr == '':
                 # HH:MM(:SS) am/pm time strings
-                m = self.CRE_TIMEHMS2.search(s)
+                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'))
+                            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'))
+                            parseStr = '%s:%s %s' % (m.group('hours'),
+                                                     m.group('minutes'),
+                                                     m.group('meridian'))
                     else:
-                        parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
+                        parseStr = '%s %s' % (m.group('hours'),
+                                              m.group('meridian'))
 
                     chunk1 = s[:m.start('hours')]
                     chunk2 = s[m.end('meridian'):]
@@ -1179,29 +1404,34 @@
 
             if parseStr == '':
                 # HH:MM(:SS) time strings
-                m = self.CRE_TIMEHMS.search(s)
+                m = self.ptc.CRE_TIMEHMS.search(s)
                 if m is not None:
-                    self.timeFlag = True
+                    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'))
+                        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'))
+                        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 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 '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)
 
@@ -1209,44 +1439,65 @@
             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)
 
-                    return 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:
-                    s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
+                    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.invalidFlag = True
+            totalTime     = time.localtime()
+            self.dateFlag = 0
+            self.timeFlag = 0
 
-        return (totalTime, self.invalidFlag)
+        return (totalTime, self.dateFlag + self.timeFlag)
 
 
     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.
+        Takes the given C{source} 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.
+        This routine is needed because Python's C{timedelta()} function
+        does not allow for month or year increments.
 
-        @type  source: datetime
-        @param source: datetime value to increment
+        @type  source: struct_time
+        @param source: C{struct_time} 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
+        @return: C{source} incremented by the number of months and/or years
         """
         yr  = source.year
         mth = source.month
+        dy  = source.day
 
         if year:
             try:
@@ -1279,7 +1530,12 @@
 
             yr += y
 
-        d = source.replace(year=yr, month=mth)
+            # if the day ends up past the last day of
+            # the new month, set it to the last day
+            if dy > self.ptc.daysInMonth(mth, yr):
+                dy = self.ptc.daysInMonth(mth, yr)
+
+        d = source.replace(year=yr, month=mth, day=dy)
 
         return source + (d - source)
 
--- a/MoinMoin/support/parsedatetime/parsedatetime_consts.py	Sun Oct 04 17:47:54 2009 +0200
+++ b/MoinMoin/support/parsedatetime/parsedatetime_consts.py	Sun Oct 04 19:32:01 2009 +0200
@@ -1,10 +1,18 @@
 #!/usr/bin/env python
 
 """
-The Constants class defines all constants used by parsedatetime.py.
+parsedatetime constants and helper functions to determine
+regex values from Locale information if present.
+
+Also contains the internal Locale classes to give some sane
+defaults if PyICU is not found.
 """
 
-__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
+__license__ = """
+Copyright (c) 2004-2008 Mike Taylor
+Copyright (c) 2006-2008 Darshana Chhajed
+Copyright (c)      2007 Bernd Zeimetz <bzed@debian.org>
+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.
@@ -18,10 +26,6 @@
 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>',
-                   ]
-
 
 try:
     import PyICU as pyicu
@@ -29,32 +33,34 @@
     pyicu = None
 
 
-import string
-import datetime, time
+import datetime
+import calendar
+import time
+import re
 
 
 class pdtLocale_en:
     """
     en_US Locale constants
 
-    This class will be used to initialize C{Constants} if PyICU is not located.
+    This class will be used to initialize L{Constants} if PyICU is not located.
 
     Defined as class variables are the lists and strings needed by parsedatetime
-    to evaluate strings in English (US)
+    to evaluate strings for USA
     """
 
     localeID      = 'en_US'   # don't use a unicode string
-    dateSep       = u'/'
-    timeSep       = u':'
+    dateSep       = [ u'/', 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',
+    Weekdays      = [ u'monday', u'tuesday', u'wednesday',
+                      u'thursday', u'friday', u'saturday', u'sunday',
                     ]
-    shortWeekdays = [ u'sun', u'mon', u'tues',
-                      u'wed', u'thu', u'fri', u'sat',
+    shortWeekdays = [ u'mon', u'tues', u'wed',
+                      u'thu', u'fri', u'sat', u'sun',
                     ]
     Months        = [ u'january', u'february', u'march',
                       u'april',   u'may',      u'june',
@@ -77,6 +83,8 @@
                       'short':  'h:mm a',
                     }
 
+    dp_order = [ u'm', u'd', u'y' ]
+
       # this will be added to re_consts later
     units = { 'seconds': [ 'second', 'sec' ],
               'minutes': [ 'minute', 'min' ],
@@ -88,12 +96,117 @@
             }
 
       # 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' ],
+    re_consts     = { 'specials':       'in|on|of|at',
+                      'timeseperator':  ':',
+                      'rangeseperator': '-',
+                      '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,
+                  'previous':  -1,
+                  'in a':       2,
+                  'end of':     0,
+                  'eod':        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 },
+                      'eod':       { 'hr': 17, 'mn': 0, 'sec': 0 },
+                    }
+
+
+class pdtLocale_au:
+    """
+    en_AU Locale constants
+
+    This class will be used to initialize L{Constants} if PyICU is not located.
+
+    Defined as class variables are the lists and strings needed by parsedatetime
+    to evaluate strings for Australia
+    """
+
+    localeID      = 'en_AU'   # don't use a unicode string
+    dateSep       = [ u'-', u'/' ]
+    timeSep       = [ u':' ]
+    meridian      = [ u'AM', u'PM' ]
+    usesMeridian  = True
+    uses24        = False
+
+    Weekdays      = [ u'monday', u'tuesday', u'wednesday',
+                      u'thursday', u'friday', u'saturday', u'sunday',
+                    ]
+    shortWeekdays = [ u'mon', u'tues', u'wed',
+                      u'thu', u'fri', u'sat', u'sun',
+                    ]
+    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, d MMMM yyyy',
+                      'long':   'd MMMM yyyy',
+                      'medium': 'dd/MM/yyyy',
+                      'short':  'd/MM/yy',
+                    }
+    timeFormats   = { 'full':   'h:mm:ss a z',
+                      'long':   'h:mm:ss a',
+                      'medium': 'h:mm:ss a',
+                      'short':  'h:mm a',
+                    }
+
+    dp_order = [ u'd', u'm', u'y' ]
+
+      # 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':  ':',
+                      'rangeseperator': '-',
+                      '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
@@ -105,7 +218,6 @@
                   'prev':      -1,
                   'last':      -1,
                   'next':       1,
-                  'this':       0,
                   'previous':  -1,
                   'in a':       2,
                   'end of':     0,
@@ -130,6 +242,7 @@
                       'midnight':  { 'hr':  0, 'mn': 0, 'sec': 0 },
                       'night':     { 'hr': 21, 'mn': 0, 'sec': 0 },
                       'tonight':   { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      'eod':       { 'hr': 17, 'mn': 0, 'sec': 0 },
                     }
 
 
@@ -137,7 +250,7 @@
     """
     es Locale constants
 
-    This class will be used to initialize C{Constants} if PyICU is not located.
+    This class will be used to initialize L{Constants} if PyICU is not located.
 
     Defined as class variables are the lists and strings needed by parsedatetime
     to evaluate strings in Spanish
@@ -146,17 +259,17 @@
     """
 
     localeID      = 'es'   # don't use a unicode string
-    dateSep       = u'/'
-    timeSep       = u':'
+    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',
+    Weekdays      = [ u'lunes', u'martes', u'mi\xe9rcoles',
+                      u'jueves', u'viernes', u's\xe1bado', u'domingo',
                     ]
-    shortWeekdays = [ 'dom', u'lun', u'mar',
-                      u'mi\xe9', u'jue', u'vie', u's\xe1b',
+    shortWeekdays = [ u'lun', u'mar', u'mi\xe9',
+                      u'jue', u'vie', u's\xe1b', u'dom',
                     ]
     Months        = [ u'enero', u'febrero', u'marzo',
                       u'abril', u'mayo', u'junio',
@@ -179,6 +292,8 @@
                       'short':  "HH:mm",
                     }
 
+    dp_order = [ u'd', u'm', u'y' ]
+
       # this will be added to re_consts later
     units = { 'seconds': [ 'second', 'sec' ],
               'minutes': [ 'minute', 'min' ],
@@ -190,28 +305,28 @@
             }
 
       # 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' ],
+    re_consts     = { 'specials':       'in|on|of|at',
+                      'timeseperator':  timeSep,
+                      'dateseperator':  dateSep,
+                      'rangeseperator': '-',
+                      '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,
+    modifiers = { 'from':      1,
+                  'before':   -1,
+                  'after':     1,
+                  'ago':       1,
+                  'prior':    -1,
+                  'prev':     -1,
+                  'last':     -1,
+                  'next':      1,
+                  'previous': -1,
+                  'in a':      2,
+                  'end of':    0,
+                  'eo':        0,
                 }
 
     dayoffsets = { 'tomorrow':   1,
@@ -223,72 +338,214 @@
       # 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 },
+    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 },
+                      'eod':       { 'hr': 17, 'mn': 0, 'sec': 0 },
+                    }
+
+
+class pdtLocale_de:
+    """
+    de_DE Locale constants
+
+    This class will be used to initialize L{Constants} if PyICU is not located.
+
+    Contributed by Debian parsedatetime package maintainer Bernd Zeimetz <bzed@debian.org>
+
+    Defined as class variables are the lists and strings needed by parsedatetime
+    to evaluate strings for German
+    """
+
+    localeID      = 'de_DE'   # don't use a unicode string
+    dateSep       = [ u'.' ]
+    timeSep       = [ u':' ]
+    meridian      = [ ]
+    usesMeridian  = False
+    uses24        = True
+
+    Weekdays      = [ u'montag', u'dienstag', u'mittwoch',
+                      u'donnerstag', u'freitag', u'samstag', u'sonntag',
+                    ]
+    shortWeekdays = [ u'mo', u'di', u'mi',
+                      u'do', u'fr', u'sa', u'so',
+                    ]
+    Months        = [ u'januar',  u'februar',  u'm\xe4rz',
+                      u'april',   u'mai',      u'juni',
+                      u'juli',    u'august',   u'september',
+                      u'oktober', u'november', u'dezember',
+                    ]
+    shortMonths   = [ u'jan', u'feb', u'mrz',
+                      u'apr', u'mai', u'jun',
+                      u'jul', u'aug', u'sep',
+                      u'okt', u'nov', u'dez',
+                    ]
+    dateFormats   = { 'full':   u'EEEE, d. MMMM yyyy',
+                      'long':   u'd. MMMM yyyy',
+                      'medium': u'dd.MM.yyyy',
+                      'short':  u'dd.MM.yy'
+                    }
+
+    timeFormats   = { 'full':   u'HH:mm:ss v',
+                      'long':   u'HH:mm:ss z',
+                      'medium': u'HH:mm:ss',
+                      'short':  u'HH:mm'
+                    }
+
+    dp_order = [ u'd', u'm', u'y' ]
+
+      # this will be added to re_consts later
+    units = { 'seconds': [ 'sekunden', 'sek',  's' ],
+              'minutes': [ 'minuten',  'min' , 'm' ],
+              'hours':   [ 'stunden',  'std',  'h' ],
+              'days':    [ 'tage',     't' ],
+              'weeks':   [ 'wochen',   'w' ],
+              'months':  [ 'monate' ], #the short version would be a capital M,
+                                       #as I understand it we can't distinguis
+                                       #between m for minutes and M for months.
+              'years':   [ 'jahre',    'j' ],
+            }
+
+      # text constants to be used by regex's later
+    re_consts     = { 'specials':       'am|dem|der|im|in|den|zum',
+                      'timeseperator':  ':',
+                      'rangeseperator': '-',
+                      'daysuffix':      '',
+                      'qunits':         'h|m|s|t|w|m|j',
+                      'now':            [ 'jetzt' ],
+                    }
+
+      # Used to adjust the returned date before/after the source
+      #still looking for insight on how to translate all of them to german.
+    modifiers = { u'from':         1,
+                  u'before':      -1,
+                  u'after':        1,
+                  u'vergangener': -1,
+                  u'vorheriger':  -1,
+                  u'prev':        -1,
+                  u'letzter':     -1,
+                  u'n\xe4chster':  1,
+                  u'dieser':       0,
+                  u'previous':    -1,
+                  u'in a':         2,
+                  u'end of':       0,
+                  u'eod':          0,
+                  u'eo':           0,
+                }
+
+     #morgen/abermorgen does not work, see http://code.google.com/p/parsedatetime/issues/detail?id=19
+    dayoffsets = { u'morgen':        1,
+                   u'heute':         0,
+                   u'gestern':      -1,
+                   u'vorgestern':   -2,
+                   u'\xfcbermorgen': 2,
+                 }
+
+      # 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    = { u'mittag':      { 'hr': 12, 'mn': 0, 'sec': 0 },
+                      u'mittags':     { 'hr': 12, 'mn': 0, 'sec': 0 },
+                      u'mittagessen': { 'hr': 12, 'mn': 0, 'sec': 0 },
+                      u'morgen':      { 'hr':  6, 'mn': 0, 'sec': 0 },
+                      u'morgens':     { 'hr':  6, 'mn': 0, 'sec': 0 },
+                      u'fr\e4hst\xe4ck': { 'hr':  8, 'mn': 0, 'sec': 0 },
+                      u'abendessen':  { 'hr': 19, 'mn': 0, 'sec': 0 },
+                      u'abend':       { 'hr': 18, 'mn': 0, 'sec': 0 },
+                      u'abends':      { 'hr': 18, 'mn': 0, 'sec': 0 },
+                      u'mitternacht': { 'hr':  0, 'mn': 0, 'sec': 0 },
+                      u'nacht':       { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      u'nachts':      { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      u'heute abend': { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      u'heute nacht': { 'hr': 21, 'mn': 0, 'sec': 0 },
+                      u'feierabend':  { 'hr': 17, 'mn': 0, 'sec': 0 },
                     }
 
 
 pdtLocales = { 'en_US': pdtLocale_en,
-               'es':    pdtLocale_es,
+               'en_AU': pdtLocale_au,
+               'es_ES': pdtLocale_es,
+               'de_DE': pdtLocale_de,
              }
 
 
 def _initLocale(ptc):
     """
     Helper function to initialize the different lists and strings
-    from either PyICU or one of the locale pdt Locales and store
+    from either PyICU or one of the internal 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)
+    def lcase(x):
+        return x.lower()
 
-        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())
+    if pyicu and ptc.usePyICU:
+        ptc.icuLocale = None
+
+        if ptc.localeID is not None:
+            ptc.icuLocale = pyicu.Locale(ptc.localeID)
+
+        if ptc.icuLocale is None:
+            for id in range(0, len(ptc.fallbackLocales)):
+                ptc.localeID  = ptc.fallbackLocales[id]
+                ptc.icuLocale = pyicu.Locale(ptc.localeID)
+
+                if ptc.icuLocale is not None:
+                    break
+
+        ptc.icuSymbols = pyicu.DateFormatSymbols(ptc.icuLocale)
+
+          # grab ICU list of weekdays, skipping first entry which
+          # is always blank
+        wd  = map(lcase, ptc.icuSymbols.getWeekdays()[1:])
+        swd = map(lcase, ptc.icuSymbols.getShortWeekdays()[1:])
+
+          # store them in our list with Monday first (ICU puts Sunday first)
+        ptc.Weekdays      = wd[1:] + wd[0:1]
+        ptc.shortWeekdays = swd[1:] + swd[0:1]
+        ptc.Months        = map(lcase, ptc.icuSymbols.getMonths())
+        ptc.shortMonths   = map(lcase, 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(),
-                            }
+        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'
+            for id in range(0, len(ptc.fallbackLocales)):
+                ptc.localeID  = ptc.fallbackLocales[id]
 
-        ptc.locale = pdtLocales[ptc.localeID]
+                if ptc.localeID in pdtLocales:
+                    break
+
+        ptc.locale   = pdtLocales[ptc.localeID]
+        ptc.usePyICU = False
 
         ptc.Weekdays      = ptc.locale.Weekdays
         ptc.shortWeekdays = ptc.locale.shortWeekdays
@@ -297,7 +554,6 @@
         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
       #
@@ -323,10 +579,16 @@
         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)
+      # escape any regex special characters that may be found
+    wd   = tuple(map(re.escape, ptc.Weekdays))
+    swd  = tuple(map(re.escape, ptc.shortWeekdays))
+    mth  = tuple(map(re.escape, ptc.Months))
+    smth = tuple(map(re.escape, ptc.shortMonths))
+
+    ptc.re_values['months']      = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % mth
+    ptc.re_values['shortmonths'] = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % smth
+    ptc.re_values['days']        = '%s|%s|%s|%s|%s|%s|%s' % wd
+    ptc.re_values['shortdays']   = '%s|%s|%s|%s|%s|%s|%s' % swd
 
     l = []
     for unit in units:
@@ -341,56 +603,78 @@
     Helper function to initialize the single character constants
     and other symbols needed.
     """
-    ptc.timeSep  = u':'
-    ptc.dateSep  = u'/'
+    ptc.timeSep  = [ u':' ]
+    ptc.dateSep  = [ u'/' ]
     ptc.meridian = [ u'AM', u'PM' ]
 
     ptc.usesMeridian = True
     ptc.uses24       = False
 
-    if pyicu:
+    if pyicu and ptc.usePyICU:
         am = u''
         pm = u''
+        ts = ''
 
-          # 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)
+        # ICU doesn't seem to provide directly the
+        # date or time seperator - so we have to
+        # figure it out
         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'
+        # '11:45 AM' or '11:45'
+        s = o.format(datetime.datetime(2003, 10, 30, 11, 45))
 
-        s = s.replace('11', '').replace('45', '')                   # ': AM' or ':'
+        # ': AM' or ':'
+        s = s.replace('11', '').replace('45', '')
 
         if len(s) > 0:
-            ptc.timeSep = s[0]
+            ts = 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'
+            # '23:45 AM' or '23:45'
+            am = s[1:].strip()
+            s  = o.format(datetime.datetime(2003, 10, 30, 23, 45))
 
             if ptc.uses24:
                 s = s.replace('23', '')
             else:
                 s = s.replace('11', '')
 
-            pm = s.replace('45', '').replace(ptc.timeSep, '').strip()  # 'PM' or ''
+            # 'PM' or ''
+            pm = s.replace('45', '').replace(ts, '').strip()
 
+        ptc.timeSep  = [ ts ]
         ptc.meridian = [ am, pm ]
 
+        o = ptc.icu_df['short']
+        s = o.format(datetime.datetime(2003, 10, 30, 11, 45))
+        s = s.replace('10', '').replace('30', '').replace('03', '').replace('2003', '')
+
+        if len(s) > 0:
+            ds = s[0]
+        else:
+            ds = '/'
+
+        ptc.dateSep = [ ds ]
+        s           = ptc.dateFormats['short']
+        l           = s.lower().split(ds)
+        dp_order    = []
+
+        for s in l:
+            if len(s) > 0:
+                dp_order.append(s[:1])
+
+        ptc.dp_order = dp_order
     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
+        ptc.dp_order     = ptc.locale.dp_order
 
       # build am and pm lists to contain
       # original case, lowercase and first-char
@@ -431,45 +715,127 @@
     # 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_DATE4     = r'''(?P<date>(((?P<day>\d\d?)(?P<suffix>%(daysuffix)s)?(,)?(\s)?)
+                                      (?P<mthname>(%(months)s|%(shortmonths)s))\s?
+                                      (?P<year>\d\d(\d\d)?)?
+                                    )
+                           )''' % ptc.re_values
+
+    # I refactored DATE3 to fix Issue 16 http://code.google.com/p/parsedatetime/issues/detail?id=16
+    # I suspect the final line was for a trailing time - but testing shows it's not needed
+    # ptc.RE_DATE3     = r'''(?P<date>((?P<mthname>(%(months)s|%(shortmonths)s))\s?
+    #                                  ((?P<day>\d\d?)(\s?|%(daysuffix)s|$)+)?
+    #                                  (,\s?(?P<year>\d\d(\d\d)?))?))
+    #                        (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values
+    ptc.RE_DATE3     = r'''(?P<date>(
+                                     (((?P<mthname>(%(months)s|%(shortmonths)s))|
+                                     ((?P<day>\d\d?)(?P<suffix>%(daysuffix)s)?))(\s)?){1,2}
+                                     ((,)?(\s)?(?P<year>\d\d(\d\d)?))?
+                                    )
+                           )''' % ptc.re_values
+    ptc.RE_MONTH     = r'''(\s?|^)
+                           (?P<month>(
+                                      (?P<mthname>(%(months)s|%(shortmonths)s))
+                                      (\s?(?P<year>(\d\d\d\d)))?
+                                     ))
+                           (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values
+    ptc.RE_WEEKDAY   = r'''(\s?|^)
+                           (?P<weekday>(%(days)s|%(shortdays)s))
+                           (\s?|$|[^0-9a-zA-Z])''' % 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
+    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'''(\s?|^)
+                           (?P<modifier>
+                            (previous|prev|last|next|eod|eo|(end\sof)|(in\sa)))''' % ptc.re_values
+    ptc.RE_MODIFIER2 = r'''(\s?|^)
+                           (?P<modifier>
+                            (from|before|after|ago|prior))
+                           (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values
+    ptc.RE_TIMEHMS   = r'''(\s?|^)
+                           (?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
+    dateSeps = ''.join(ptc.dateSep) + '.'
+
+    ptc.RE_DATE      = r'''(\s?|^)
+                           (?P<date>(\d\d?[%s]\d\d?([%s]\d\d(\d\d)?)?))
+                           (\s?|$|[^0-9a-zA-Z])''' % (dateSeps, dateSeps)
+    ptc.RE_DATE2     = r'[%s]' % dateSeps
+    ptc.RE_DAY       = r'''(\s?|^)
+                           (?P<day>(today|tomorrow|yesterday))
+                           (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values
+    ptc.RE_DAY2      = r'''(?P<day>\d\d?)|(?P<suffix>%(daysuffix)s)
+                        ''' % ptc.re_values
+    ptc.RE_TIME      = r'''(\s?|^)
+                           (?P<time>(morning|breakfast|noon|lunch|evening|midnight|tonight|dinner|night|now))
+                           (\s?|$|[^0-9a-zA-Z])''' % 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
+    # Regex for date/time ranges
+    ptc.RE_RTIMEHMS  = r'''(\s?|^)
+                           (\d\d?)%(timeseperator)s
+                           (\d\d)
+                           (%(timeseperator)s(\d\d))?
+                           (\s?|$)''' % ptc.re_values
+    ptc.RE_RTIMEHMS2 = r'''(\s?|^)
+                           (\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 "
+    ptc.RE_RDATE  = r'(\d+([%s]\d+)+)' % dateSeps
+    ptc.RE_RDATE3 = r'''((((%(months)s))\s?
+                         ((\d\d?)
+                          (\s?|%(daysuffix)s|$)+)?
+                         (,\s?\d\d\d\d)?))''' % ptc.re_values
+
+    # "06/07/06 - 08/09/06"
+    ptc.DATERNG1 = ptc.RE_RDATE + r'\s?%(rangeseperator)s\s?' + ptc.RE_RDATE
+    ptc.DATERNG1 = ptc.DATERNG1 % ptc.re_values
+
+    # "march 31 - june 1st, 2006"
+    ptc.DATERNG2 = ptc.RE_RDATE3 + r'\s?%(rangeseperator)s\s?' + ptc.RE_RDATE3
+    ptc.DATERNG2 = ptc.DATERNG2 % ptc.re_values
+
+    # "march 1rd -13th"
+    ptc.DATERNG3 = ptc.RE_RDATE3 + r'\s?%(rangeseperator)s\s?(\d\d?)\s?(rd|st|nd|th)?'
+    ptc.DATERNG3 = ptc.DATERNG3 % ptc.re_values
+
+    # "4:00:55 pm - 5:90:44 am", '4p-5p'
+    ptc.TIMERNG1 = ptc.RE_RTIMEHMS2 + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2
+    ptc.TIMERNG1 = ptc.TIMERNG1 % ptc.re_values
+
+    # "4:00 - 5:90 ", "4:55:55-3:44:55"
+    ptc.TIMERNG2 = ptc.RE_RTIMEHMS + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS
+    ptc.TIMERNG2 = ptc.TIMERNG2 % ptc.re_values
+
+    # "4-5pm "
+    ptc.TIMERNG3 = r'\d\d?\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2
+    ptc.TIMERNG3 = ptc.TIMERNG3 % ptc.re_values
+
+    # "4:30-5pm "
+    ptc.TIMERNG4 = ptc.RE_RTIMEHMS + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2
+    ptc.TIMERNG4 = ptc.TIMERNG4 % ptc.re_values
 
 
 def _initConstants(ptc):
@@ -477,7 +843,7 @@
     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
+      # lists are in the same order and Mon..Sun (Python style)
     ptc.WeekdayOffsets = {}
 
     o = 0
@@ -492,42 +858,55 @@
       # 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
 
+    # ptc.DaySuffixes = ptc.re_consts['daysuffix'].split('|')
+
 
 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 present, then the class will first try to get PyICU
+    to return a locale specified by C{localeID}.  If either C{localeID} is
+    None or if the locale does not exist within PyICU, then each of the
+    locales defined in C{fallbackLocales} is tried in order.
 
-    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.
+    If PyICU is not present or none of the specified locales can be used,
+    then the class will initialize itself to the en_US locale.
+
+    if PyICU is not present or not requested, only the locales defined by
+    C{pdtLocales} will be searched.
     """
-    def __init__(self, localeID=None, usePyICU=True):
-        if localeID is None:
-            self.localeID = 'en_US'
-        else:
-            self.localeID = localeID
+    def __init__(self, localeID=None, usePyICU=True, fallbackLocales=['en_US']):
+        self.localeID        = localeID
+        self.fallbackLocales = fallbackLocales
+
+        if 'en_US' not in self.fallbackLocales:
+            self.fallbackLocales.append('en_US')
 
           # define non-locale specific constants
 
         self.locale   = None
         self.usePyICU = usePyICU
 
+        # starting cache of leap years
+        # daysInMonth will add to this if during
+        # runtime it gets a request for a year not found
+        self._leapYears = [ 1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944,
+                            1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988,
+                            1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032,
+                            2036, 2040, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076,
+                            2080, 2084, 2088, 2092, 2096 ]
+
         self.Second =   1
         self.Minute =  60 * self.Second
         self.Hour   =  60 * self.Minute
@@ -536,13 +915,174 @@
         self.Month  =  30 * self.Day
         self.Year   = 365 * self.Day
 
-        self.DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+        self.rangeSep = u'-'
+
+        self._DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
+        self.BirthdayEpoch = 50
+
+        # DOWParseStyle controls how we parse "Tuesday"
+        # If the current day was Thursday and the text to parse is "Tuesday"
+        # then the following table shows how each style would be returned
+        # -1, 0, +1
+        #
+        # Current day marked as ***
+        #
+        #          Sun Mon Tue Wed Thu Fri Sat
+        # week -1
+        # current         -1,0     ***
+        # week +1          +1
+        #
+        # If the current day was Monday and the text to parse is "Tuesday"
+        # then the following table shows how each style would be returned
+        # -1, 0, +1
+        #
+        #          Sun Mon Tue Wed Thu Fri Sat
+        # week -1           -1
+        # current      *** 0,+1
+        # week +1
+
+        self.DOWParseStyle = 1
+
+        # CurrentDOWParseStyle controls how we parse "Friday"
+        # If the current day was Friday and the text to parse is "Friday"
+        # then the following table shows how each style would be returned
+        # True/False. This also depends on DOWParseStyle.
+        #
+        # Current day marked as ***
+        #
+        # DOWParseStyle = 0
+        #          Sun Mon Tue Wed Thu Fri Sat
+        # week -1
+        # current                      T,F
+        # week +1
+        #
+        # DOWParseStyle = -1
+        #          Sun Mon Tue Wed Thu Fri Sat
+        # week -1                       F
+        # current                       T
+        # week +1
+        #
+        # DOWParseStyle = +1
+        #
+        #          Sun Mon Tue Wed Thu Fri Sat
+        # week -1
+        # current                       T
+        # week +1                       F
+
+        self.CurrentDOWParseStyle = False
+
+        # initalize attributes to empty values to ensure
+        # they are defined
+        self.re_sources     = None
+        self.re_values      = None
+        self.Modifiers      = None
+        self.dayOffsets     = None
+        self.WeekdayOffsets = None
+        self.MonthOffsets   = None
+        self.dateSep        = None
+        self.timeSep        = None
+        self.am             = None
+        self.pm             = None
+        self.meridian       = None
+        self.usesMeridian   = None
+        self.uses24         = None
+        self.dp_order       = None
+
+        self.RE_DATE4     = r''
+        self.RE_DATE3     = r''
+        self.RE_MONTH     = r''
+        self.RE_WEEKDAY   = r''
+        self.RE_SPECIAL   = r''
+        self.RE_UNITS     = r''
+        self.RE_QUNITS    = r''
+        self.RE_MODIFIER  = r''
+        self.RE_MODIFIER2 = r''
+        self.RE_TIMEHMS   = r''
+        self.RE_TIMEHMS2  = r''
+        self.RE_DATE      = r''
+        self.RE_DATE2     = r''
+        self.RE_DAY       = r''
+        self.RE_DAY2      = r''
+        self.RE_TIME      = r''
+        self.RE_REMAINING = r''
+        self.RE_RTIMEHMS  = r''
+        self.RE_RTIMEHMS2 = r''
+        self.RE_RDATE     = r''
+        self.RE_RDATE3    = r''
+        self.DATERNG1     = r''
+        self.DATERNG2     = r''
+        self.DATERNG3     = r''
+        self.TIMERNG1     = r''
+        self.TIMERNG2     = r''
+        self.TIMERNG3     = r''
+        self.TIMERNG4     = r''
 
         _initLocale(self)
         _initConstants(self)
         _initSymbols(self)
         _initPatterns(self)
 
+        self.re_option = re.IGNORECASE + re.VERBOSE
+        self.cre_source = { 'CRE_SPECIAL':   self.RE_SPECIAL,
+                            'CRE_UNITS':     self.RE_UNITS,
+                            'CRE_QUNITS':    self.RE_QUNITS,
+                            'CRE_MODIFIER':  self.RE_MODIFIER,
+                            'CRE_MODIFIER2': self.RE_MODIFIER2,
+                            'CRE_TIMEHMS':   self.RE_TIMEHMS,
+                            'CRE_TIMEHMS2':  self.RE_TIMEHMS2,
+                            'CRE_DATE':      self.RE_DATE,
+                            'CRE_DATE2':     self.RE_DATE2,
+                            'CRE_DATE3':     self.RE_DATE3,
+                            'CRE_DATE4':     self.RE_DATE4,
+                            'CRE_MONTH':     self.RE_MONTH,
+                            'CRE_WEEKDAY':   self.RE_WEEKDAY,
+                            'CRE_DAY':       self.RE_DAY,
+                            'CRE_DAY2':      self.RE_DAY2,
+                            'CRE_TIME':      self.RE_TIME,
+                            'CRE_REMAINING': self.RE_REMAINING,
+                            'CRE_RTIMEHMS':  self.RE_RTIMEHMS,
+                            'CRE_RTIMEHMS2': self.RE_RTIMEHMS2,
+                            'CRE_RDATE':     self.RE_RDATE,
+                            'CRE_RDATE3':    self.RE_RDATE3,
+                            'CRE_TIMERNG1':  self.TIMERNG1,
+                            'CRE_TIMERNG2':  self.TIMERNG2,
+                            'CRE_TIMERNG3':  self.TIMERNG3,
+                            'CRE_TIMERNG4':  self.TIMERNG4,
+                            'CRE_DATERNG1':  self.DATERNG1,
+                            'CRE_DATERNG2':  self.DATERNG2,
+                            'CRE_DATERNG3':  self.DATERNG3,
+                          }
+        self.cre_keys = self.cre_source.keys()
+
+
+    def __getattr__(self, name):
+        if name in self.cre_keys:
+            value = re.compile(self.cre_source[name], self.re_option)
+            setattr(self, name, value)
+            return value
+        else:
+            raise AttributeError, name
+
+    def daysInMonth(self, month, year):
+        """
+        Take the given month (1-12) and a given year (4 digit) return
+        the number of days in the month adjusting for leap year as needed
+        """
+        result = None
+
+        if month > 0 and month <= 12:
+            result = self._DaysInMonthList[month - 1]
+
+            if month == 2:
+                if year in self._leapYears:
+                    result += 1
+                else:
+                    if calendar.isleap(year):
+                        self._leapYears.append(year)
+                        result += 1
+
+        return result
 
     def buildSources(self, sourceTime=None):
         """
@@ -563,10 +1103,13 @@
                      'hr': hr, 'mn':  mn,  'sec': sec, }
 
         for item in self.re_sources:
-            values = self.re_sources[item]
+            values = {}
+            source = self.re_sources[item]
 
             for key in defaults.keys():
-                if not key in values:
+                if key in source:
+                    values[key] = source[key]
+                else:
                     values[key] = defaults[key]
 
             sources[item] = ( values['yr'], values['mth'], values['dy'],