~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/support/parsedatetime/pdt.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mfrom: (0.9.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080622211713-fpo2zrq3s5dfecxg
Tags: 1.7.0-3
Simplify /etc/moin/wikilist format: "USER URL" (drop unneeded middle
CONFIG_DIR that was wrongly advertised as DATA_DIR).  Make
moin-mass-migrate handle both formats and warn about deprecation of
the old one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
"""
 
4
Parse human-readable date/time text.
 
5
"""
 
6
 
 
7
__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
 
8
 
 
9
Licensed under the Apache License, Version 2.0 (the "License");
 
10
you may not use this file except in compliance with the License.
 
11
You may obtain a copy of the License at
 
12
 
 
13
   http://www.apache.org/licenses/LICENSE-2.0
 
14
 
 
15
Unless required by applicable law or agreed to in writing, software
 
16
distributed under the License is distributed on an "AS IS" BASIS,
 
17
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
18
See the License for the specific language governing permissions and
 
19
limitations under the License.
 
20
"""
 
21
__author__       = 'Mike Taylor <http://code-bear.com>'
 
22
__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
 
23
                   ]
 
24
 
 
25
_debug = False
 
26
 
 
27
 
 
28
import string, re, time
 
29
import datetime, calendar, rfc822
 
30
import parsedatetime_consts
 
31
 
 
32
 
 
33
# Copied from feedparser.py
 
34
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
35
# Originally a def inside of _parse_date_w3dtf()
 
36
def _extract_date(m):
 
37
    year = int(m.group('year'))
 
38
    if year < 100:
 
39
        year = 100 * int(time.gmtime()[0] / 100) + int(year)
 
40
    if year < 1000:
 
41
        return 0, 0, 0
 
42
    julian = m.group('julian')
 
43
    if julian:
 
44
        julian = int(julian)
 
45
        month = julian / 30 + 1
 
46
        day = julian % 30 + 1
 
47
        jday = None
 
48
        while jday != julian:
 
49
            t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
 
50
            jday = time.gmtime(t)[-2]
 
51
            diff = abs(jday - julian)
 
52
            if jday > julian:
 
53
                if diff < day:
 
54
                    day = day - diff
 
55
                else:
 
56
                    month = month - 1
 
57
                    day = 31
 
58
            elif jday < julian:
 
59
                if day + diff < 28:
 
60
                   day = day + diff
 
61
                else:
 
62
                    month = month + 1
 
63
        return year, month, day
 
64
    month = m.group('month')
 
65
    day = 1
 
66
    if month is None:
 
67
        month = 1
 
68
    else:
 
69
        month = int(month)
 
70
        day = m.group('day')
 
71
        if day:
 
72
            day = int(day)
 
73
        else:
 
74
            day = 1
 
75
    return year, month, day
 
76
 
 
77
# Copied from feedparser.py 
 
78
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
79
# Originally a def inside of _parse_date_w3dtf()
 
80
def _extract_time(m):
 
81
    if not m:
 
82
        return 0, 0, 0
 
83
    hours = m.group('hours')
 
84
    if not hours:
 
85
        return 0, 0, 0
 
86
    hours = int(hours)
 
87
    minutes = int(m.group('minutes'))
 
88
    seconds = m.group('seconds')
 
89
    if seconds:
 
90
        seconds = int(seconds)
 
91
    else:
 
92
        seconds = 0
 
93
    return hours, minutes, seconds
 
94
 
 
95
 
 
96
# Copied from feedparser.py
 
97
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
98
# Modified to return a tuple instead of mktime
 
99
#
 
100
# Original comment:
 
101
#       W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
 
102
#       Drake and licensed under the Python license.  Removed all range checking
 
103
#       for month, day, hour, minute, and second, since mktime will normalize
 
104
#       these later
 
105
def _parse_date_w3dtf(dateString):
 
106
    # the __extract_date and __extract_time methods were
 
107
    # copied-out so they could be used by my code --bear
 
108
    def __extract_tzd(m):
 
109
        '''Return the Time Zone Designator as an offset in seconds from UTC.'''
 
110
        if not m:
 
111
            return 0
 
112
        tzd = m.group('tzd')
 
113
        if not tzd:
 
114
            return 0
 
115
        if tzd == 'Z':
 
116
            return 0
 
117
        hours = int(m.group('tzdhours'))
 
118
        minutes = m.group('tzdminutes')
 
119
        if minutes:
 
120
            minutes = int(minutes)
 
121
        else:
 
122
            minutes = 0
 
123
        offset = (hours*60 + minutes) * 60
 
124
        if tzd[0] == '+':
 
125
            return -offset
 
126
        return offset
 
127
 
 
128
    __date_re = ('(?P<year>\d\d\d\d)'
 
129
                 '(?:(?P<dsep>-|)'
 
130
                 '(?:(?P<julian>\d\d\d)'
 
131
                 '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?')
 
132
    __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
 
133
    __tzd_rx = re.compile(__tzd_re)
 
134
    __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
 
135
                 '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
 
136
                 + __tzd_re)
 
137
    __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
 
138
    __datetime_rx = re.compile(__datetime_re)
 
139
    m = __datetime_rx.match(dateString)
 
140
    if (m is None) or (m.group() != dateString): return
 
141
    return _extract_date(m) + _extract_time(m) + (0, 0, 0)
 
142
 
 
143
 
 
144
# Copied from feedparser.py
 
145
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
146
# Modified to return a tuple instead of mktime
 
147
#
 
148
def _parse_date_rfc822(dateString):
 
149
    '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
 
150
    data = dateString.split()
 
151
    if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
 
152
        del data[0]
 
153
    if len(data) == 4:
 
154
        s = data[3]
 
155
        i = s.find('+')
 
156
        if i > 0:
 
157
            data[3:] = [s[:i], s[i+1:]]
 
158
        else:
 
159
            data.append('')
 
160
        dateString = " ".join(data)
 
161
    if len(data) < 5:
 
162
        dateString += ' 00:00:00 GMT'
 
163
    return rfc822.parsedate_tz(dateString)
 
164
 
 
165
# rfc822.py defines several time zones, but we define some extra ones.
 
166
# 'ET' is equivalent to 'EST', etc.
 
167
_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
 
168
rfc822._timezones.update(_additional_timezones)
 
169
 
 
170
 
 
171
class Calendar:
 
172
    """
 
173
    A collection of routines to input, parse and manipulate date and times.
 
174
    The text can either be 'normal' date values or it can be human readable.
 
175
    """
 
176
 
 
177
    def __init__(self, constants=None):
 
178
        """
 
179
        Default constructor for the Calendar class.
 
180
 
 
181
        @type  constants: object
 
182
        @param constants: Instance of the class L{CalendarConstants}
 
183
 
 
184
        @rtype:  object
 
185
        @return: Calendar instance
 
186
        """
 
187
          # if a constants reference is not included, use default
 
188
        if constants is None:
 
189
            self.ptc = parsedatetime_consts.CalendarConstants()
 
190
        else:
 
191
            self.ptc = constants
 
192
 
 
193
        self.CRE_SPECIAL   = re.compile(self.ptc.RE_SPECIAL,   re.IGNORECASE)
 
194
        self.CRE_UNITS     = re.compile(self.ptc.RE_UNITS,     re.IGNORECASE)
 
195
        self.CRE_QUNITS    = re.compile(self.ptc.RE_QUNITS,    re.IGNORECASE)
 
196
        self.CRE_MODIFIER  = re.compile(self.ptc.RE_MODIFIER,  re.IGNORECASE)
 
197
        self.CRE_MODIFIER2 = re.compile(self.ptc.RE_MODIFIER2, re.IGNORECASE)
 
198
        self.CRE_TIMEHMS   = re.compile(self.ptc.RE_TIMEHMS,   re.IGNORECASE)
 
199
        self.CRE_TIMEHMS2  = re.compile(self.ptc.RE_TIMEHMS2,  re.IGNORECASE)
 
200
        self.CRE_DATE      = re.compile(self.ptc.RE_DATE,      re.IGNORECASE)
 
201
        self.CRE_DATE2     = re.compile(self.ptc.RE_DATE2,     re.IGNORECASE)
 
202
        self.CRE_DATE3     = re.compile(self.ptc.RE_DATE3,     re.IGNORECASE)
 
203
        self.CRE_MONTH     = re.compile(self.ptc.RE_MONTH,     re.IGNORECASE)
 
204
        self.CRE_WEEKDAY   = re.compile(self.ptc.RE_WEEKDAY,   re.IGNORECASE)
 
205
        self.CRE_DAY       = re.compile(self.ptc.RE_DAY,       re.IGNORECASE)
 
206
        self.CRE_TIME      = re.compile(self.ptc.RE_TIME,      re.IGNORECASE)
 
207
        self.CRE_REMAINING = re.compile(self.ptc.RE_REMAINING, re.IGNORECASE)
 
208
 
 
209
        #regex for date/time ranges
 
210
        self.CRE_RTIMEHMS   = re.compile(self.ptc.RE_RTIMEHMS,  re.IGNORECASE)
 
211
        self.CRE_RTIMEHMS2  = re.compile(self.ptc.RE_RTIMEHMS2,  re.IGNORECASE)
 
212
        self.CRE_RDATE      = re.compile(self.ptc.RE_RDATE,      re.IGNORECASE)
 
213
        self.CRE_RDATE3     = re.compile(self.ptc.RE_RDATE3,     re.IGNORECASE)
 
214
 
 
215
        self.CRE_TIMERNG1      = re.compile(self.ptc.TIMERNG1, re.IGNORECASE)
 
216
        self.CRE_TIMERNG2      = re.compile(self.ptc.TIMERNG2, re.IGNORECASE)
 
217
        self.CRE_TIMERNG3      = re.compile(self.ptc.TIMERNG3, re.IGNORECASE)
 
218
        self.CRE_DATERNG1      = re.compile(self.ptc.DATERNG1, re.IGNORECASE)
 
219
        self.CRE_DATERNG2      = re.compile(self.ptc.DATERNG2, re.IGNORECASE)
 
220
        self.CRE_DATERNG3      = re.compile(self.ptc.DATERNG3, re.IGNORECASE)
 
221
 
 
222
        self.invalidFlag   = False  # Is set if the datetime string entered cannot be parsed at all
 
223
        self.weekdyFlag    = False  # monday/tuesday/...
 
224
        self.dateStdFlag   = False  # 07/21/06
 
225
        self.dateStrFlag   = False  # July 21st, 2006
 
226
        self.timeFlag      = False  # 5:50 
 
227
        self.meridianFlag  = False  # am/pm
 
228
        self.dayStrFlag    = False  # tomorrow/yesterday/today/..
 
229
        self.timeStrFlag   = False  # lunch/noon/breakfast/...
 
230
        self.modifierFlag  = False  # after/before/prev/next/..
 
231
        self.modifier2Flag = False  # after/before/prev/next/..
 
232
        self.unitsFlag     = False  # hrs/weeks/yrs/min/..
 
233
        self.qunitsFlag    = False  # h/m/t/d..
 
234
 
 
235
 
 
236
    def _convertUnitAsWords(self, unitText):
 
237
        """
 
238
        Converts text units into their number value
 
239
 
 
240
        Five = 5
 
241
        Twenty Five = 25
 
242
        Two hundred twenty five = 225
 
243
        Two thousand and twenty five = 2025
 
244
        Two thousand twenty five = 2025
 
245
 
 
246
        @type  unitText: string
 
247
        @param unitText: number string
 
248
 
 
249
        @rtype:  integer
 
250
        @return: numerical value of unitText
 
251
        """
 
252
        # TODO: implement this
 
253
        pass
 
254
 
 
255
 
 
256
    def _buildTime(self, source, quantity, modifier, units):
 
257
        """
 
258
        Take quantity, modifier and unit strings and convert them into values.
 
259
        Then calcuate the time and return the adjusted sourceTime
 
260
 
 
261
        @type  source:   time
 
262
        @param source:   time to use as the base (or source)
 
263
        @type  quantity: string
 
264
        @param quantity: quantity string
 
265
        @type  modifier: string
 
266
        @param modifier: how quantity and units modify the source time
 
267
        @type  units:    string
 
268
        @param units:    unit of the quantity (i.e. hours, days, months, etc)
 
269
 
 
270
        @rtype:  timetuple
 
271
        @return: timetuple of the calculated time
 
272
        """
 
273
        if _debug:
 
274
            print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
 
275
 
 
276
        if source is None:
 
277
            source = time.localtime()
 
278
 
 
279
        if quantity is None:
 
280
            quantity = ''
 
281
        else:
 
282
            quantity = string.strip(quantity)
 
283
 
 
284
        if len(quantity) == 0:
 
285
            qty = 1
 
286
        else:
 
287
            try:
 
288
                qty = int(quantity)
 
289
            except ValueError:
 
290
                qty = 0
 
291
 
 
292
        if modifier in self.ptc.Modifiers:
 
293
            qty = qty * self.ptc.Modifiers[modifier]
 
294
 
 
295
            if units is None or units == '':
 
296
                units = 'dy'
 
297
 
 
298
        # plurals are handled by regex's (could be a bug tho)
 
299
 
 
300
        if units in self.ptc.Units:
 
301
            u = self.ptc.Units[units]
 
302
        else:
 
303
            u = 1
 
304
 
 
305
        (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
 
306
 
 
307
        start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
 
308
        target = start
 
309
 
 
310
        if units.startswith('y'):
 
311
            target = self.inc(start, year=qty)
 
312
        elif units.endswith('th') or units.endswith('ths'):
 
313
            target = self.inc(start, month=qty)
 
314
        else:
 
315
            if units.startswith('d'):
 
316
                target = start + datetime.timedelta(days=qty)
 
317
            elif units.startswith('h'):
 
318
                target = start + datetime.timedelta(hours=qty)
 
319
            elif units.startswith('m'):
 
320
                target = start + datetime.timedelta(minutes=qty)
 
321
            elif units.startswith('s'):
 
322
                target = start + datetime.timedelta(seconds=qty)
 
323
            elif units.startswith('w'):
 
324
                target = start + datetime.timedelta(weeks=qty)
 
325
 
 
326
        if target != start:
 
327
            self.invalidFlag = False
 
328
 
 
329
        return target.timetuple()
 
330
 
 
331
 
 
332
    def parseDate(self, dateString):
 
333
        """
 
334
        Parses strings like 05/28/200 or 04.21
 
335
 
 
336
        @type  dateString: string
 
337
        @param dateString: text to convert to a datetime
 
338
 
 
339
        @rtype:  datetime
 
340
        @return: calculated datetime value of dateString
 
341
        """
 
342
        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
343
 
 
344
        s = dateString
 
345
        m = self.CRE_DATE2.search(s)
 
346
        if m is not None:
 
347
            index = m.start()
 
348
            mth   = int(s[:index])
 
349
            s     = s[index + 1:]
 
350
 
 
351
        m = self.CRE_DATE2.search(s)
 
352
        if m is not None:
 
353
            index = m.start()
 
354
            dy    = int(s[:index])
 
355
            yr    = int(s[index + 1:])
 
356
            # TODO should this have a birthday epoch constraint?
 
357
            if yr < 99:
 
358
                yr += 2000
 
359
        else:
 
360
            dy = int(string.strip(s))
 
361
 
 
362
        if mth <= 12 and dy <= self.ptc.DaysInMonthList[mth - 1]:
 
363
            sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
364
        else:
 
365
            self.invalidFlag = True
 
366
            sourceTime       = time.localtime() #return current time if date string is invalid
 
367
 
 
368
        return sourceTime
 
369
 
 
370
 
 
371
    def parseDateText(self, dateString):
 
372
        """
 
373
        Parses strings like "May 31st, 2006" or "Jan 1st" or "July 2006"
 
374
 
 
375
        @type  dateString: string
 
376
        @param dateString: text to convert to a datetime
 
377
 
 
378
        @rtype:  datetime
 
379
        @return: calculated datetime value of dateString
 
380
        """
 
381
        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
382
 
 
383
        currentMth = mth
 
384
        currentDy  = dy
 
385
 
 
386
        s   = dateString.lower()
 
387
        m   = self.CRE_DATE3.search(s)
 
388
        mth = m.group('mthname')
 
389
        mth = int(self.ptc.MthNames[mth])
 
390
 
 
391
        if m.group('day') !=  None:
 
392
            dy = int(m.group('day'))
 
393
        else:
 
394
            dy = 1
 
395
 
 
396
        if m.group('year') !=  None:
 
397
            yr = int(m.group('year'))
 
398
        elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
 
399
            # if that day and month have already passed in this year,
 
400
            # then increment the year by 1
 
401
            yr += 1
 
402
 
 
403
        if dy <= self.ptc.DaysInMonthList[mth - 1]:
 
404
            sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
 
405
        else:
 
406
              # Return current time if date string is invalid
 
407
            self.invalidFlag = True
 
408
            sourceTime       = time.localtime()
 
409
 
 
410
        return sourceTime
 
411
 
 
412
 
 
413
    def evalRanges(self,datetimeString,sourceTime=None):
 
414
        """
 
415
        Evaluates the strings with time or date ranges
 
416
        """
 
417
        startTime = ''
 
418
        endTime   = ''
 
419
        startDate = ''
 
420
        endDate   = ''
 
421
        rangeFlag = 0
 
422
        sourceTime = None
 
423
 
 
424
        s = string.strip(datetimeString.lower())
 
425
 
 
426
        m = self.CRE_TIMERNG1.search(s)
 
427
        if m is not None:
 
428
            rangeFlag = 1
 
429
        else:
 
430
            m = self.CRE_TIMERNG2.search(s)
 
431
            if m is not None:  
 
432
                rangeFlag = 2
 
433
            else:
 
434
                m = self.CRE_TIMERNG3.search(s)
 
435
                if m is not None:
 
436
                    rangeFlag = 3
 
437
                else:
 
438
                    m = self.CRE_DATERNG1.search(s)
 
439
                    if m is not None:
 
440
                        rangeFlag = 4
 
441
                    else:
 
442
                        m = self.CRE_DATERNG2.search(s)
 
443
                        if m is not None:
 
444
                            rangeFlag = 5
 
445
                        else:
 
446
                            m = self.CRE_DATERNG3.search(s)
 
447
                            if m is not None:
 
448
                                rangeFlag = 6
 
449
 
 
450
        if m is not None :
 
451
            if (m.group() != s):
 
452
                # capture remaining string
 
453
                parseStr = m.group()
 
454
                str1    = s[:m.start()]
 
455
                str2    = s[m.end():]
 
456
                s       = str1 + ' ' + str2
 
457
                flag    = 1
 
458
                sourceTime, flag = self.parse(s, sourceTime)
 
459
                if flag == True:
 
460
                    sourceTime = None
 
461
            else:
 
462
                parseStr = s
 
463
 
 
464
 
 
465
        if rangeFlag == 1:
 
466
            m = re.search('-',parseStr)
 
467
            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
 
468
            endTime, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
 
469
            if eflag is False and sflag is False:
 
470
                return (startTime, endTime, False)
 
471
 
 
472
        elif rangeFlag == 2:
 
473
            m = re.search('-',parseStr)
 
474
            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
 
475
            endTime, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
 
476
            if eflag is False and sflag is False:
 
477
                return (startTime, endTime, False)
 
478
 
 
479
        elif rangeFlag == 3:
 
480
            m = re.search('-',parseStr)
 
481
 
 
482
            #capturing the meridian from the end time
 
483
            ampm = re.search('a',parseStr)
 
484
 
 
485
            #appending the meridian to the start time
 
486
            if ampm is not None:
 
487
                startTime, sflag = self.parse((parseStr[:m.start()]+'am'), sourceTime)
 
488
            else:
 
489
                startTime, sflag = self.parse((parseStr[:m.start()]+'pm'), sourceTime)
 
490
            endTime, eflag   = self.parse(parseStr[(m.start()+1):], sourceTime)
 
491
            if eflag is False and sflag is False:
 
492
                return (startTime, endTime, False)
 
493
 
 
494
        elif rangeFlag == 4:
 
495
            m = re.search('-',parseStr)
 
496
            startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime)
 
497
            endDate, eflag   = self.parse((parseStr[(m.start()+1):]), sourceTime)
 
498
            if eflag is False and sflag is False:
 
499
                return (startDate, endDate, False)
 
500
 
 
501
        elif rangeFlag == 5:
 
502
            m = re.search('-',parseStr)
 
503
            endDate = parseStr[(m.start()+1):]
 
504
 
 
505
            #capturing the year from the end date
 
506
            date = self.CRE_DATE3.search(endDate)
 
507
            endYear = date.group('year')
 
508
 
 
509
            # appending the year to the start date if the start date does not have year information
 
510
            # and the end date does. eg : "Aug 21 - Sep 4, 2007
 
511
            if endYear is not None:
 
512
                startDate = parseStr[:m.start()]
 
513
                date = self.CRE_DATE3.search(startDate)
 
514
                startYear = date.group('year')
 
515
                if startYear is None:
 
516
                    startDate += endYear
 
517
            else:
 
518
                startDate = parseStr[:m.start()]
 
519
 
 
520
            startDate, sflag = self.parse(startDate, sourceTime)
 
521
            endDate, eflag   = self.parse(endDate, sourceTime)
 
522
            if eflag is False and sflag is False:
 
523
                return (startDate, endDate, False)
 
524
 
 
525
        elif rangeFlag == 6:
 
526
            m = re.search('-',parseStr)
 
527
 
 
528
            startDate = parseStr[:m.start()]
 
529
 
 
530
            #capturing the month from the start date
 
531
            mth = self.CRE_DATE3.search(startDate)
 
532
            mth = mth.group('mthname')
 
533
 
 
534
            # appending the month name to the end date
 
535
            endDate = mth + parseStr[(m.start()+1):]
 
536
 
 
537
            startDate, sflag = self.parse(startDate, sourceTime)
 
538
            endDate, eflag   = self.parse(endDate, sourceTime)
 
539
            if eflag is False and sflag is False:
 
540
                return (startDate, endDate, False)
 
541
        else :
 
542
            sourceTime = time.localtime()
 
543
            #if range is not found
 
544
            return (sourceTime, sourceTime, True)
 
545
 
 
546
 
 
547
    def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
 
548
        """
 
549
        Evaluate the modifier string and following text (passed in
 
550
        as chunk1 and chunk2) and if they match any known modifiers
 
551
        calculate the delta and apply it to sourceTime
 
552
 
 
553
        @type  modifier: string
 
554
        @param modifier: modifier text to apply to sourceTime
 
555
        @type  chunk1:   string
 
556
        @param chunk1:   first text chunk that followed modifier (if any)
 
557
        @type  chunk2:   string
 
558
        @param chunk2:   second text chunk that followed modifier (if any)
 
559
        @type  sourceTime: datetime
 
560
        @param sourceTime: datetime value to use as the base
 
561
 
 
562
        @rtype:  tuple
 
563
        @return: tuple of any remaining text and the modified sourceTime
 
564
        """
 
565
        offset = self.ptc.Modifiers[modifier]
 
566
 
 
567
        if sourceTime is not None:
 
568
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
569
        else:
 
570
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
571
 
 
572
        # capture the units after the modifier and the remaining string after the unit
 
573
        m = self.CRE_REMAINING.search(chunk2)
 
574
        if m is not None:
 
575
            index  = m.start() + 1
 
576
            unit   = chunk2[:m.start()]
 
577
            chunk2 = chunk2[index:]
 
578
        else:
 
579
            unit   = chunk2
 
580
            chunk2 = ''
 
581
 
 
582
        flag = False
 
583
 
 
584
        if unit == self.ptc.Target_Text['month'] or \
 
585
           unit == self.ptc.Target_Text['mth']:
 
586
            if offset == 0:
 
587
                dy        = self.ptc.DaysInMonthList[mth - 1]
 
588
                sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
 
589
            elif offset == 2:
 
590
                # if day is the last day of the month, calculate the last day of the next month
 
591
                if dy == self.ptc.DaysInMonthList[mth - 1]:
 
592
                    dy = self.ptc.DaysInMonthList[mth]
 
593
 
 
594
                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
595
                target     = self.inc(start, month=1)
 
596
                sourceTime = target.timetuple()
 
597
            else:
 
598
                start      = datetime.datetime(yr, mth, 1, 9, 0, 0)
 
599
                target     = self.inc(start, month=offset)
 
600
                sourceTime = target.timetuple()
 
601
 
 
602
            flag = True
 
603
 
 
604
        if unit == self.ptc.Target_Text['week'] or \
 
605
             unit == self.ptc.Target_Text['wk'] or \
 
606
             unit == self.ptc.Target_Text['w']:
 
607
            if offset == 0:
 
608
                start      = datetime.datetime(yr, mth, dy, 17, 0, 0)
 
609
                target     = start + datetime.timedelta(days=(4 - wd))
 
610
                sourceTime = target.timetuple()
 
611
            elif offset == 2:
 
612
                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
613
                target     = start + datetime.timedelta(days=7)
 
614
                sourceTime = target.timetuple()
 
615
            else:
 
616
                return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
 
617
 
 
618
            flag = True
 
619
 
 
620
        if unit == self.ptc.Target_Text['day'] or \
 
621
            unit == self.ptc.Target_Text['dy'] or \
 
622
            unit == self.ptc.Target_Text['d']:
 
623
            if offset == 0:
 
624
                sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
 
625
            elif offset == 2:
 
626
                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
 
627
                target     = start + datetime.timedelta(days=1)
 
628
                sourceTime = target.timetuple()
 
629
            else:
 
630
                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
631
                target     = start + datetime.timedelta(days=offset)
 
632
                sourceTime = target.timetuple()
 
633
 
 
634
            flag = True
 
635
 
 
636
        if unit == self.ptc.Target_Text['hour'] or \
 
637
           unit == self.ptc.Target_Text['hr']:
 
638
            if offset == 0:
 
639
                sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst)
 
640
            else:
 
641
                start      = datetime.datetime(yr, mth, dy, hr, 0, 0)
 
642
                target     = start + datetime.timedelta(hours=offset)
 
643
                sourceTime = target.timetuple()
 
644
 
 
645
            flag = True
 
646
 
 
647
        if unit == self.ptc.Target_Text['year'] or \
 
648
             unit == self.ptc.Target_Text['yr'] or \
 
649
             unit == self.ptc.Target_Text['y']:
 
650
            if offset == 0:
 
651
                sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst)
 
652
            elif offset == 2:
 
653
                sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst)
 
654
            else:
 
655
                sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
 
656
 
 
657
            flag = True
 
658
 
 
659
        if flag == False:
 
660
            m = self.CRE_WEEKDAY.match(unit)
 
661
            if m is not None:
 
662
                wkdy = m.group()
 
663
                wkdy = self.ptc.WeekDays[wkdy]
 
664
 
 
665
                if offset == 0:
 
666
                    diff       = wkdy - wd
 
667
                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
668
                    target     = start + datetime.timedelta(days=diff)
 
669
                    sourceTime = target.timetuple()
 
670
                else:
 
671
                    diff       = wkdy - wd
 
672
                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
673
                    target     = start + datetime.timedelta(days=diff + 7 * offset)
 
674
                    sourceTime = target.timetuple()
 
675
 
 
676
                flag = True
 
677
 
 
678
        if not flag:
 
679
            m = self.CRE_TIME.match(unit)
 
680
            if m is not None:
 
681
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
 
682
                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
 
683
                target     = start + datetime.timedelta(days=offset)
 
684
                sourceTime = target.timetuple()
 
685
 
 
686
                flag              = True
 
687
                self.modifierFlag = False
 
688
 
 
689
        # if the word after next is a number, the string is likely
 
690
        # to be something like "next 4 hrs" for which we have to
 
691
        # combine the units with the rest of the string
 
692
        if not flag:
 
693
            if offset < 0:
 
694
                # if offset is negative, the unit has to be made negative
 
695
                unit = '-%s' % unit
 
696
 
 
697
            chunk2 = '%s %s' % (unit, chunk2)
 
698
 
 
699
        self.modifierFlag = False
 
700
 
 
701
        return '%s %s' % (chunk1, chunk2), sourceTime
 
702
 
 
703
 
 
704
    def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
 
705
        """
 
706
        Evaluate the modifier string and following text (passed in
 
707
        as chunk1 and chunk2) and if they match any known modifiers
 
708
        calculate the delta and apply it to sourceTime
 
709
 
 
710
        @type  modifier:   string
 
711
        @param modifier:   modifier text to apply to sourceTime
 
712
        @type  chunk1:     string
 
713
        @param chunk1:     first text chunk that followed modifier (if any)
 
714
        @type  chunk2:     string
 
715
        @param chunk2:     second text chunk that followed modifier (if any)
 
716
        @type  sourceTime: datetime
 
717
        @param sourceTime: datetime value to use as the base
 
718
 
 
719
        @rtype:  tuple
 
720
        @return: tuple of any remaining text and the modified sourceTime
 
721
        """
 
722
        offset = self.ptc.Modifiers[modifier]
 
723
        digit  = r'\d+'
 
724
 
 
725
        if sourceTime is not None:
 
726
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
727
        else:
 
728
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
729
 
 
730
        self.modifier2Flag = False
 
731
 
 
732
        # If the string after the negative modifier starts with
 
733
        # digits, then it is likely that the string is similar to
 
734
        # " before 3 days" or 'evening prior to 3 days'.
 
735
        # In this case, the total time is calculated by subtracting
 
736
        # '3 days' from the current date.
 
737
        # So, we have to identify the quantity and negate it before
 
738
        # parsing the string.
 
739
        # This is not required for strings not starting with digits
 
740
        # since the string is enough to calculate the sourceTime
 
741
        if offset < 0:
 
742
            m = re.match(digit, string.strip(chunk2))
 
743
            if m is not None:
 
744
                qty    = int(m.group()) * -1
 
745
                chunk2 = chunk2[m.end():]
 
746
                chunk2 = '%d%s' % (qty, chunk2)
 
747
 
 
748
        sourceTime, flag = self.parse(chunk2, sourceTime)
 
749
 
 
750
        if chunk1 != '':
 
751
            if offset < 0:
 
752
                m = re.match(digit, string.strip(chunk1))
 
753
                if m is not None:
 
754
                    qty    = int(m.group()) * -1
 
755
                    chunk1 = chunk1[m.end():]
 
756
                    chunk1 = '%d%s' % (qty, chunk1)
 
757
 
 
758
            sourceTime, flag = self.parse(chunk1, sourceTime)
 
759
 
 
760
        return '', sourceTime
 
761
 
 
762
 
 
763
    def _evalString(self, datetimeString, sourceTime=None):
 
764
        """
 
765
        Calculate the datetime based on flags set by the L{parse()} routine
 
766
 
 
767
        Examples handled::
 
768
            RFC822, W3CDTF formatted dates
 
769
            HH:MM[:SS][ am/pm]
 
770
            MM/DD/YYYY
 
771
            DD MMMM YYYY
 
772
 
 
773
        @type  datetimeString: string
 
774
        @param datetimeString: text to try and parse as more "traditional" date/time text
 
775
        @type  sourceTime:     datetime
 
776
        @param sourceTime:     datetime value to use as the base
 
777
 
 
778
        @rtype:  datetime
 
779
        @return: calculated datetime value or current datetime if not parsed
 
780
        """
 
781
        s = string.strip(datetimeString)
 
782
 
 
783
          # Given string date is a RFC822 date
 
784
        if sourceTime is None:
 
785
            sourceTime = _parse_date_rfc822(s)
 
786
 
 
787
          # Given string date is a W3CDTF date
 
788
        if sourceTime is None:
 
789
            sourceTime = _parse_date_w3dtf(s)
 
790
 
 
791
        if sourceTime is None:
 
792
            s = s.lower()
 
793
 
 
794
          # Given string is in the format HH:MM(:SS)(am/pm)
 
795
        if self.meridianFlag:
 
796
            if sourceTime is None:
 
797
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
798
            else:
 
799
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
800
 
 
801
            m = self.CRE_TIMEHMS2.search(s)
 
802
            if m is not None:
 
803
                dt = s[:m.start('meridian')].strip()
 
804
                if len(dt) <= 2:
 
805
                    hr  = int(dt)
 
806
                    mn  = 0
 
807
                    sec = 0
 
808
                else:
 
809
                    hr, mn, sec = _extract_time(m)
 
810
 
 
811
                if hr == 24:
 
812
                    hr = 0
 
813
 
 
814
                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
815
                meridian   = m.group('meridian')
 
816
 
 
817
                if (re.compile("a").search(meridian)) and hr == 12:
 
818
                    sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst)
 
819
                if (re.compile("p").search(meridian)) and hr < 12:
 
820
                    sourceTime = (yr, mth, dy, hr+12, mn, sec, wd, yd, isdst)
 
821
 
 
822
              # invalid time
 
823
            if hr > 24 or mn > 59 or sec > 59:
 
824
                sourceTime       = time.localtime()
 
825
                self.invalidFlag = True
 
826
 
 
827
            self.meridianFlag = False
 
828
 
 
829
          # Given string is in the format HH:MM(:SS)
 
830
        if self.timeFlag:
 
831
            if sourceTime is None:
 
832
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
833
            else:
 
834
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
835
 
 
836
            m = self.CRE_TIMEHMS.search(s)
 
837
            if m is not None:
 
838
                hr, mn, sec = _extract_time(m)
 
839
            if hr == 24:
 
840
                hr = 0
 
841
 
 
842
            if hr > 24 or mn > 59 or sec > 59:
 
843
                # invalid time
 
844
                sourceTime = time.localtime()
 
845
                self.invalidFlag = True
 
846
            else:
 
847
                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
848
 
 
849
            self.timeFlag = False
 
850
 
 
851
          # Given string is in the format 07/21/2006
 
852
        if self.dateStdFlag:
 
853
            sourceTime       = self.parseDate(s)
 
854
            self.dateStdFlag = False
 
855
 
 
856
          # Given string is in the format  "May 23rd, 2005"
 
857
        if self.dateStrFlag:
 
858
            sourceTime       = self.parseDateText(s)
 
859
            self.dateStrFlag = False
 
860
 
 
861
          # Given string is a weekday
 
862
        if self.weekdyFlag:
 
863
            yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
 
864
            start = datetime.datetime(yr, mth, dy, hr, mn, sec)
 
865
            wkDy  = self.ptc.WeekDays[s]
 
866
 
 
867
            if wkDy > wd:
 
868
                qty    = wkDy - wd
 
869
                target = start + datetime.timedelta(days=qty)
 
870
                wd     = wkDy
 
871
            else:
 
872
                qty    = 6 - wd + wkDy + 1
 
873
                target = start + datetime.timedelta(days=qty)
 
874
                wd     = wkDy
 
875
 
 
876
            sourceTime      = target.timetuple()
 
877
            self.weekdyFlag = False
 
878
 
 
879
          # Given string is a natural language time string like lunch, midnight, etc
 
880
        if self.timeStrFlag:
 
881
            if sourceTime is None:
 
882
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
 
883
            else:
 
884
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
885
 
 
886
            sources = { 'now':       (yr, mth, dy, hr, mn, sec, wd, yd, isdst),
 
887
                        'noon':      (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
 
888
                        'lunch':     (yr, mth, dy, 12,  0,   0, wd, yd, isdst),
 
889
                        'morning':   (yr, mth, dy,  6,  0,   0, wd, yd, isdst),
 
890
                        'breakfast': (yr, mth, dy,  8,  0,   0, wd, yd, isdst),
 
891
                        'dinner':    (yr, mth, dy, 19,  0,   0, wd, yd, isdst),
 
892
                        'evening':   (yr, mth, dy, 18,  0,   0, wd, yd, isdst),
 
893
                        'midnight':  (yr, mth, dy,  0,  0,   0, wd, yd, isdst),
 
894
                        'night':     (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
 
895
                        'tonight':   (yr, mth, dy, 21,  0,   0, wd, yd, isdst),
 
896
                      }
 
897
 
 
898
            if s in sources:
 
899
                sourceTime = sources[s]
 
900
            else:
 
901
                sourceTime       = time.localtime()
 
902
                self.invalidFlag = True
 
903
 
 
904
            self.timeStrFlag = False
 
905
 
 
906
           # Given string is a natural language date string like today, tomorrow..
 
907
        if self.dayStrFlag:
 
908
            if sourceTime is None:
 
909
                sourceTime = time.localtime()
 
910
 
 
911
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
 
912
 
 
913
            sources = { 'tomorrow':   1,
 
914
                        'today':      0,
 
915
                        'yesterday': -1,
 
916
                       }
 
917
 
 
918
            start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
 
919
            target     = start + datetime.timedelta(days=sources[s])
 
920
            sourceTime = target.timetuple()
 
921
 
 
922
            self.dayStrFlag = False
 
923
 
 
924
          # Given string is a time string with units like "5 hrs 30 min"
 
925
        if self.unitsFlag:
 
926
            modifier = ''  # TODO
 
927
 
 
928
            if sourceTime is None:
 
929
                sourceTime = time.localtime()
 
930
 
 
931
            m = self.CRE_UNITS.search(s)
 
932
            if m is not None:
 
933
                units    = m.group('units')
 
934
                quantity = s[:m.start('units')]
 
935
 
 
936
            sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
 
937
            self.unitsFlag = False
 
938
 
 
939
          # Given string is a time string with single char units like "5 h 30 m"
 
940
        if self.qunitsFlag:
 
941
            modifier = ''  # TODO
 
942
 
 
943
            if sourceTime is None:
 
944
                sourceTime = time.localtime()
 
945
 
 
946
            m = self.CRE_QUNITS.search(s)
 
947
            if m is not None:
 
948
                units    = m.group('qunits')
 
949
                quantity = s[:m.start('qunits')]
 
950
 
 
951
            sourceTime      = self._buildTime(sourceTime, quantity, modifier, units)
 
952
            self.qunitsFlag = False
 
953
 
 
954
          # Given string does not match anything
 
955
        if sourceTime is None:
 
956
            sourceTime       = time.localtime()
 
957
            self.invalidFlag = True
 
958
 
 
959
        return sourceTime
 
960
 
 
961
 
 
962
    def parse(self, datetimeString, sourceTime=None):
 
963
        """
 
964
        Splits the L{datetimeString} into tokens, finds the regex patters
 
965
        that match and then calculates a datetime value from the chunks
 
966
 
 
967
        if L{sourceTime} is given then the datetime value will be calcualted
 
968
        from that datetime, otherwise from the current datetime.
 
969
 
 
970
        @type  datetimeString: string
 
971
        @param datetimeString: datetime text to evaluate
 
972
        @type  sourceTime:     datetime
 
973
        @param sourceTime:     datetime value to use as the base
 
974
 
 
975
        @rtype:  tuple
 
976
        @return: tuple of any remaining text and the modified sourceTime
 
977
        """
 
978
        s         = string.strip(datetimeString.lower())
 
979
        dateStr   = ''
 
980
        parseStr  = ''
 
981
        totalTime = sourceTime
 
982
 
 
983
        self.invalidFlag = False
 
984
 
 
985
        if s == '' :
 
986
            if sourceTime is not None:
 
987
                return (sourceTime, False)
 
988
            else:
 
989
                return (time.localtime(), True)
 
990
 
 
991
        while len(s) > 0:
 
992
            flag   = False
 
993
            chunk1 = ''
 
994
            chunk2 = ''
 
995
 
 
996
            if _debug:
 
997
                print 'parse (top of loop): [%s][%s]' % (s, parseStr)
 
998
 
 
999
            if parseStr == '':
 
1000
                # Modifier like next\prev..
 
1001
                m = self.CRE_MODIFIER.search(s)
 
1002
                if m is not None:
 
1003
                    self.modifierFlag = True
 
1004
                    if (m.group('modifier') != s):
 
1005
                        # capture remaining string
 
1006
                        parseStr = m.group('modifier')
 
1007
                        chunk1   = string.strip(s[:m.start('modifier')])
 
1008
                        chunk2   = string.strip(s[m.end('modifier'):])
 
1009
                        flag     = True
 
1010
                    else:
 
1011
                        parseStr = s
 
1012
 
 
1013
            if parseStr == '':
 
1014
                # Modifier like from\after\prior..
 
1015
                m = self.CRE_MODIFIER2.search(s)
 
1016
                if m is not None:
 
1017
                    self.modifier2Flag = True
 
1018
                    if (m.group('modifier') != s):
 
1019
                        # capture remaining string
 
1020
                        parseStr = m.group('modifier')
 
1021
                        chunk1   = string.strip(s[:m.start('modifier')])
 
1022
                        chunk2   = string.strip(s[m.end('modifier'):])
 
1023
                        flag     = True
 
1024
                    else:
 
1025
                        parseStr = s
 
1026
 
 
1027
            if parseStr == '':
 
1028
                # String date format
 
1029
                m = self.CRE_DATE3.search(s)
 
1030
                if m is not None:
 
1031
                    self.dateStrFlag = True
 
1032
                    if (m.group('date') != s):
 
1033
                        # capture remaining string
 
1034
                        parseStr = m.group('date')
 
1035
                        chunk1   = s[:m.start('date')]
 
1036
                        chunk2   = s[m.end('date'):]
 
1037
                        s        = '%s %s' % (chunk1, chunk2)
 
1038
                        flag     = True
 
1039
                    else:
 
1040
                        parseStr = s
 
1041
 
 
1042
            if parseStr == '':
 
1043
                # Standard date format
 
1044
                m = self.CRE_DATE.search(s)
 
1045
                if m is not None:
 
1046
                    self.dateStdFlag = True
 
1047
                    if (m.group('date') != s):
 
1048
                        # capture remaining string
 
1049
                        parseStr = m.group('date')
 
1050
                        chunk1   = s[:m.start('date')]
 
1051
                        chunk2   = s[m.end('date'):]
 
1052
                        s        = '%s %s' % (chunk1, chunk2)
 
1053
                        flag     = True
 
1054
                    else:
 
1055
                        parseStr = s
 
1056
 
 
1057
            if parseStr == '':
 
1058
                # Natural language day strings
 
1059
                m = self.CRE_DAY.search(s)
 
1060
                if m is not None:
 
1061
                    self.dayStrFlag = True
 
1062
                    if (m.group('day') != s):
 
1063
                        # capture remaining string
 
1064
                        parseStr = m.group('day')
 
1065
                        chunk1   = s[:m.start('day')]
 
1066
                        chunk2   = s[m.end('day'):]
 
1067
                        s        = '%s %s' % (chunk1, chunk2)
 
1068
                        flag     = True
 
1069
                    else:
 
1070
                        parseStr = s
 
1071
 
 
1072
            if parseStr == '':
 
1073
                # Quantity + Units
 
1074
                m = self.CRE_UNITS.search(s)
 
1075
                if m is not None:
 
1076
                    self.unitsFlag = True
 
1077
                    if (m.group('qty') != s):
 
1078
                        # capture remaining string
 
1079
                        parseStr = m.group('qty')
 
1080
                        chunk1   = s[:m.start('qty')].strip()
 
1081
                        chunk2   = s[m.end('qty'):].strip()
 
1082
 
 
1083
                        if chunk1[-1:] == '-':
 
1084
                            parseStr = '-%s' % parseStr
 
1085
                            chunk1   = chunk1[:-1]
 
1086
 
 
1087
                        s    = '%s %s' % (chunk1, chunk2)
 
1088
                        flag = True
 
1089
                    else:
 
1090
                        parseStr = s
 
1091
 
 
1092
            if parseStr == '':
 
1093
                # Quantity + Units
 
1094
                m = self.CRE_QUNITS.search(s)
 
1095
                if m is not None:
 
1096
                    self.qunitsFlag = True
 
1097
                    if (m.group('qty') != s):
 
1098
                        # capture remaining string
 
1099
                        parseStr = m.group('qty')
 
1100
                        chunk1   = s[:m.start('qty')].strip()
 
1101
                        chunk2   = s[m.end('qty'):].strip()
 
1102
 
 
1103
                        if chunk1[-1:] == '-':
 
1104
                            parseStr = '-%s' % parseStr
 
1105
                            chunk1   = chunk1[:-1]
 
1106
 
 
1107
                        s    = '%s %s' % (chunk1, chunk2)
 
1108
                        flag = True
 
1109
                    else:
 
1110
                        parseStr = s 
 
1111
 
 
1112
            if parseStr == '':
 
1113
                # Weekday
 
1114
                m = self.CRE_WEEKDAY.search(s)
 
1115
                if m is not None:
 
1116
                    self.weekdyFlag = True
 
1117
                    if (m.group('weekday') != s):
 
1118
                        # capture remaining string
 
1119
                        parseStr = m.group()
 
1120
                        chunk1   = s[:m.start('weekday')]
 
1121
                        chunk2   = s[m.end('weekday'):]
 
1122
                        s        = '%s %s' % (chunk1, chunk2)
 
1123
                        flag     = True
 
1124
                    else:
 
1125
                        parseStr = s
 
1126
 
 
1127
            if parseStr == '':
 
1128
                # Natural language time strings
 
1129
                m = self.CRE_TIME.search(s)
 
1130
                if m is not None:
 
1131
                    self.timeStrFlag = True
 
1132
                    if (m.group('time') != s):
 
1133
                        # capture remaining string
 
1134
                        parseStr = m.group('time')
 
1135
                        chunk1   = s[:m.start('time')]
 
1136
                        chunk2   = s[m.end('time'):]
 
1137
                        s        = '%s %s' % (chunk1, chunk2)
 
1138
                        flag     = True
 
1139
                    else:
 
1140
                        parseStr = s
 
1141
 
 
1142
            if parseStr == '':
 
1143
                # HH:MM(:SS) am/pm time strings
 
1144
                m = self.CRE_TIMEHMS2.search(s)
 
1145
                if m is not None:
 
1146
                    self.meridianFlag = True
 
1147
                    if m.group('minutes') is not None:
 
1148
                        if m.group('seconds') is not None:
 
1149
                            parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
 
1150
                        else:
 
1151
                            parseStr = '%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('meridian'))
 
1152
                    else:
 
1153
                        parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
 
1154
 
 
1155
                    chunk1 = s[:m.start('hours')]
 
1156
                    chunk2 = s[m.end('meridian'):]
 
1157
 
 
1158
                    s    = '%s %s' % (chunk1, chunk2)
 
1159
                    flag = True
 
1160
 
 
1161
            if parseStr == '':
 
1162
                # HH:MM(:SS) time strings
 
1163
                m = self.CRE_TIMEHMS.search(s)
 
1164
                if m is not None:
 
1165
                    self.timeFlag = True
 
1166
                    if m.group('seconds') is not None:
 
1167
                        parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
 
1168
                        chunk1   = s[:m.start('hours')]
 
1169
                        chunk2   = s[m.end('seconds'):]
 
1170
                    else:
 
1171
                        parseStr = '%s:%s' % (m.group('hours'), m.group('minutes'))
 
1172
                        chunk1   = s[:m.start('hours')]
 
1173
                        chunk2   = s[m.end('minutes'):]
 
1174
 
 
1175
                    s    = '%s %s' % (chunk1, chunk2)
 
1176
                    flag = True
 
1177
 
 
1178
            # if string does not match any regex, empty string to come out of the while loop
 
1179
            if not flag:
 
1180
                s = ''
 
1181
 
 
1182
            if _debug:
 
1183
                print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
 
1184
                print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
 
1185
                       (self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
 
1186
                print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
 
1187
                       (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
 
1188
 
 
1189
            # evaluate the matched string
 
1190
            if parseStr != '':
 
1191
                if self.modifierFlag == True:
 
1192
                    t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
 
1193
 
 
1194
                    return self.parse(t, totalTime)
 
1195
 
 
1196
                elif self.modifier2Flag == True:
 
1197
                    s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
 
1198
                else:
 
1199
                    totalTime = self._evalString(parseStr, totalTime)
 
1200
                    parseStr  = ''
 
1201
 
 
1202
        # String is not parsed at all
 
1203
        if totalTime is None or totalTime == sourceTime:
 
1204
            totalTime        = time.localtime()
 
1205
            self.invalidFlag = True
 
1206
 
 
1207
        return (totalTime, self.invalidFlag)
 
1208
 
 
1209
 
 
1210
    def inc(self, source, month=None, year=None):
 
1211
        """
 
1212
        Takes the given date, or current date if none is passed, and
 
1213
        increments it according to the values passed in by month
 
1214
        and/or year.
 
1215
 
 
1216
        This routine is needed because the timedelta() routine does
 
1217
        not allow for month or year increments.
 
1218
 
 
1219
        @type  source: datetime
 
1220
        @param source: datetime value to increment
 
1221
        @type  month:  integer
 
1222
        @param month:  optional number of months to increment
 
1223
        @type  year:   integer
 
1224
        @param year:   optional number of years to increment
 
1225
 
 
1226
        @rtype:  datetime
 
1227
        @return: L{source} incremented by the number of months and/or years
 
1228
        """
 
1229
        yr  = source.year
 
1230
        mth = source.month
 
1231
 
 
1232
        if year:
 
1233
            try:
 
1234
                yi = int(year)
 
1235
            except ValueError:
 
1236
                yi = 0
 
1237
 
 
1238
            yr += yi
 
1239
 
 
1240
        if month:
 
1241
            try:
 
1242
                mi = int(month)
 
1243
            except ValueError:
 
1244
                mi = 0
 
1245
 
 
1246
            m = abs(mi)
 
1247
            y = m / 12      # how many years are in month increment
 
1248
            m = m % 12      # get remaining months
 
1249
 
 
1250
            if mi < 0:
 
1251
                mth = mth - m           # sub months from start month
 
1252
                if mth < 1:             # cross start-of-year?
 
1253
                    y   -= 1            #   yes - decrement year
 
1254
                    mth += 12           #         and fix month
 
1255
            else:
 
1256
                mth = mth + m           # add months to start month
 
1257
                if mth > 12:            # cross end-of-year?
 
1258
                    y   += 1            #   yes - increment year
 
1259
                    mth -= 12           #         and fix month
 
1260
 
 
1261
            yr += y
 
1262
 
 
1263
        d = source.replace(year=yr, month=mth)
 
1264
 
 
1265
        return source + (d - source)
 
1266