~ubuntu-branches/ubuntu/oneiric/moin/oneiric-security

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Jamie Strandboge
  • Date: 2010-03-30 12:55:34 UTC
  • mfrom: (0.1.17 sid)
  • Revision ID: james.westby@ubuntu.com-20100330125534-4c2ufc1rok24447l
Tags: 1.9.2-2ubuntu1
* Merge from Debian testing (LP: #521834). Based on work by Stefan Ebner.
  Remaining changes:
 - Remove python-xml from Suggests field, the package isn't anymore in
   sys.path.
 - Demote fckeditor from Recommends to Suggests; the code was previously
   embedded in moin, but it was also disabled, so there's no reason for us
   to pull this in by default currently. Note: This isn't necessary anymore
   but needs a MIR for fckeditor, so postpone dropping this change until
   lucid+1
* debian/rules:
  - Replace hardcoded python2.5 with python* and hardcore python2.6 for ln
* debian/control.in: drop versioned depends on cdbs

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
Parse human-readable date/time text.
5
5
"""
6
6
 
7
 
__license__ = """Copyright (c) 2004-2006 Mike Taylor, All rights reserved.
 
7
__license__ = """
 
8
Copyright (c) 2004-2008 Mike Taylor
 
9
Copyright (c) 2006-2008 Darshana Chhajed
 
10
All rights reserved.
8
11
 
9
12
Licensed under the Apache License, Version 2.0 (the "License");
10
13
you may not use this file except in compliance with the License.
18
21
See the License for the specific language governing permissions and
19
22
limitations under the License.
20
23
"""
21
 
__author__       = 'Mike Taylor <http://code-bear.com>'
22
 
__contributors__ = ['Darshana Chhajed <mailto://darshana@osafoundation.org>',
23
 
                   ]
24
24
 
25
25
_debug = False
26
26
 
27
27
 
28
 
import string, re, time
29
 
import datetime, calendar, rfc822
 
28
import re
 
29
import time
 
30
import datetime
 
31
import rfc822
30
32
import parsedatetime_consts
31
33
 
32
34
 
33
35
# Copied from feedparser.py
34
 
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
36
# Universal Feedparser
 
37
# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
35
38
# Originally a def inside of _parse_date_w3dtf()
36
39
def _extract_date(m):
37
40
    year = int(m.group('year'))
57
60
                    day = 31
58
61
            elif jday < julian:
59
62
                if day + diff < 28:
60
 
                   day = day + diff
 
63
                    day = day + diff
61
64
                else:
62
65
                    month = month + 1
63
66
        return year, month, day
74
77
            day = 1
75
78
    return year, month, day
76
79
 
77
 
# Copied from feedparser.py 
78
 
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
80
# Copied from feedparser.py
 
81
# Universal Feedparser
 
82
# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
79
83
# Originally a def inside of _parse_date_w3dtf()
80
84
def _extract_time(m):
81
85
    if not m:
94
98
 
95
99
 
96
100
# Copied from feedparser.py
97
 
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
101
# Universal Feedparser
 
102
# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
98
103
# Modified to return a tuple instead of mktime
99
104
#
100
105
# 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
 
106
#   W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
 
107
#   Drake and licensed under the Python license.  Removed all range checking
 
108
#   for month, day, hour, minute, and second, since mktime will normalize
 
109
#   these later
105
110
def _parse_date_w3dtf(dateString):
106
111
    # the __extract_date and __extract_time methods were
107
112
    # copied-out so they could be used by my code --bear
142
147
 
143
148
 
144
149
# Copied from feedparser.py
145
 
# Universal Feedparser, Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
 
150
# Universal Feedparser
 
151
# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.
146
152
# Modified to return a tuple instead of mktime
147
153
#
148
154
def _parse_date_rfc822(dateString):
164
170
 
165
171
# rfc822.py defines several time zones, but we define some extra ones.
166
172
# 'ET' is equivalent to 'EST', etc.
167
 
_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
 
173
_additional_timezones = {'AT': -400, 'ET': -500,
 
174
                         'CT': -600, 'MT': -700,
 
175
                         'PT': -800}
168
176
rfc822._timezones.update(_additional_timezones)
169
177
 
170
178
 
176
184
 
177
185
    def __init__(self, constants=None):
178
186
        """
179
 
        Default constructor for the Calendar class.
 
187
        Default constructor for the L{Calendar} class.
180
188
 
181
189
        @type  constants: object
182
 
        @param constants: Instance of the class L{CalendarConstants}
 
190
        @param constants: Instance of the class L{parsedatetime_consts.Constants}
183
191
 
184
192
        @rtype:  object
185
 
        @return: Calendar instance
 
193
        @return: L{Calendar} instance
186
194
        """
187
195
          # if a constants reference is not included, use default
188
196
        if constants is None:
190
198
        else:
191
199
            self.ptc = constants
192
200
 
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
201
        self.weekdyFlag    = False  # monday/tuesday/...
224
202
        self.dateStdFlag   = False  # 07/21/06
225
203
        self.dateStrFlag   = False  # July 21st, 2006
226
 
        self.timeFlag      = False  # 5:50 
 
204
        self.timeStdFlag   = False  # 5:50 
227
205
        self.meridianFlag  = False  # am/pm
228
206
        self.dayStrFlag    = False  # tomorrow/yesterday/today/..
229
207
        self.timeStrFlag   = False  # lunch/noon/breakfast/...
232
210
        self.unitsFlag     = False  # hrs/weeks/yrs/min/..
233
211
        self.qunitsFlag    = False  # h/m/t/d..
234
212
 
 
213
        self.timeFlag      = 0
 
214
        self.dateFlag      = 0
 
215
 
235
216
 
236
217
    def _convertUnitAsWords(self, unitText):
237
218
        """
244
225
        Two thousand twenty five = 2025
245
226
 
246
227
        @type  unitText: string
247
 
        @param unitText: number string
 
228
        @param unitText: number text to convert
248
229
 
249
230
        @rtype:  integer
250
231
        @return: numerical value of unitText
255
236
 
256
237
    def _buildTime(self, source, quantity, modifier, units):
257
238
        """
258
 
        Take quantity, modifier and unit strings and convert them into values.
259
 
        Then calcuate the time and return the adjusted sourceTime
 
239
        Take C{quantity}, C{modifier} and C{unit} strings and convert them into values.
 
240
        After converting, calcuate the time and return the adjusted sourceTime.
260
241
 
261
242
        @type  source:   time
262
243
        @param source:   time to use as the base (or source)
267
248
        @type  units:    string
268
249
        @param units:    unit of the quantity (i.e. hours, days, months, etc)
269
250
 
270
 
        @rtype:  timetuple
271
 
        @return: timetuple of the calculated time
 
251
        @rtype:  struct_time
 
252
        @return: C{struct_time} of the calculated time
272
253
        """
273
254
        if _debug:
274
255
            print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units)
279
260
        if quantity is None:
280
261
            quantity = ''
281
262
        else:
282
 
            quantity = string.strip(quantity)
 
263
            quantity = quantity.strip()
283
264
 
284
265
        if len(quantity) == 0:
285
266
            qty = 1
297
278
 
298
279
        # plurals are handled by regex's (could be a bug tho)
299
280
 
300
 
        (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = source
 
281
        (yr, mth, dy, hr, mn, sec, _, _, _) = source
301
282
 
302
283
        start  = datetime.datetime(yr, mth, dy, hr, mn, sec)
303
284
        target = start
304
285
 
305
286
        if units.startswith('y'):
306
 
            target = self.inc(start, year=qty)
 
287
            target        = self.inc(start, year=qty)
 
288
            self.dateFlag = 1
307
289
        elif units.endswith('th') or units.endswith('ths'):
308
 
            target = self.inc(start, month=qty)
 
290
            target        = self.inc(start, month=qty)
 
291
            self.dateFlag = 1
309
292
        else:
310
293
            if units.startswith('d'):
311
 
                target = start + datetime.timedelta(days=qty)
 
294
                target        = start + datetime.timedelta(days=qty)
 
295
                self.dateFlag = 1
312
296
            elif units.startswith('h'):
313
 
                target = start + datetime.timedelta(hours=qty)
 
297
                target        = start + datetime.timedelta(hours=qty)
 
298
                self.timeFlag = 2
314
299
            elif units.startswith('m'):
315
 
                target = start + datetime.timedelta(minutes=qty)
 
300
                target        = start + datetime.timedelta(minutes=qty)
 
301
                self.timeFlag = 2
316
302
            elif units.startswith('s'):
317
 
                target = start + datetime.timedelta(seconds=qty)
 
303
                target        = start + datetime.timedelta(seconds=qty)
 
304
                self.timeFlag = 2
318
305
            elif units.startswith('w'):
319
 
                target = start + datetime.timedelta(weeks=qty)
320
 
 
321
 
        if target != start:
322
 
            self.invalidFlag = False
 
306
                target        = start + datetime.timedelta(weeks=qty)
 
307
                self.dateFlag = 1
323
308
 
324
309
        return target.timetuple()
325
310
 
326
311
 
327
312
    def parseDate(self, dateString):
328
313
        """
329
 
        Parses strings like 05/28/200 or 04.21
 
314
        Parse short-form date strings::
 
315
 
 
316
            '05/28/2006' or '04.21'
330
317
 
331
318
        @type  dateString: string
332
 
        @param dateString: text to convert to a datetime
 
319
        @param dateString: text to convert to a C{datetime}
333
320
 
334
 
        @rtype:  datetime
335
 
        @return: calculated datetime value of dateString
 
321
        @rtype:  struct_time
 
322
        @return: calculated C{struct_time} value of dateString
336
323
        """
337
324
        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
338
325
 
 
326
        # values pulled from regex's will be stored here and later
 
327
        # assigned to mth, dy, yr based on information from the locale
 
328
        # -1 is used as the marker value because we want zero values
 
329
        # to be passed thru so they can be flagged as errors later
 
330
        v1 = -1
 
331
        v2 = -1
 
332
        v3 = -1
 
333
 
339
334
        s = dateString
340
 
        m = self.CRE_DATE2.search(s)
 
335
        m = self.ptc.CRE_DATE2.search(s)
341
336
        if m is not None:
342
337
            index = m.start()
343
 
            mth   = int(s[:index])
 
338
            v1    = int(s[:index])
344
339
            s     = s[index + 1:]
345
340
 
346
 
        m = self.CRE_DATE2.search(s)
 
341
        m = self.ptc.CRE_DATE2.search(s)
347
342
        if m is not None:
348
343
            index = m.start()
349
 
            dy    = int(s[:index])
350
 
            yr    = int(s[index + 1:])
351
 
            # TODO should this have a birthday epoch constraint?
352
 
            if yr < 99:
353
 
                yr += 2000
354
 
        else:
355
 
            dy = int(string.strip(s))
356
 
 
357
 
        if (mth > 0 and mth <= 12) and (dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]):
 
344
            v2    = int(s[:index])
 
345
            v3    = int(s[index + 1:])
 
346
        else:
 
347
            v2 = int(s.strip())
 
348
 
 
349
        v = [ v1, v2, v3 ]
 
350
        d = { 'm': mth, 'd': dy, 'y': yr }
 
351
 
 
352
        for i in range(0, 3):
 
353
            n = v[i]
 
354
            c = self.ptc.dp_order[i]
 
355
            if n >= 0:
 
356
                d[c] = n
 
357
 
 
358
        # if the year is not specified and the date has already
 
359
        # passed, increment the year
 
360
        if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])):
 
361
            yr = d['y'] + 1
 
362
        else:
 
363
            yr  = d['y']
 
364
 
 
365
        mth = d['m']
 
366
        dy  = d['d']
 
367
 
 
368
        # birthday epoch constraint
 
369
        if yr < self.ptc.BirthdayEpoch:
 
370
            yr += 2000
 
371
        elif yr < 100:
 
372
            yr += 1900
 
373
 
 
374
        if _debug:
 
375
            print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr)
 
376
 
 
377
        if (mth > 0 and mth <= 12) and \
 
378
           (dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)):
358
379
            sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
359
380
        else:
360
 
            self.invalidFlag = True
361
 
            sourceTime       = time.localtime() #return current time if date string is invalid
 
381
            self.dateFlag = 0
 
382
            self.timeFlag = 0
 
383
            sourceTime    = time.localtime() # return current time if date
 
384
                                             # string is invalid
362
385
 
363
386
        return sourceTime
364
387
 
365
388
 
366
389
    def parseDateText(self, dateString):
367
390
        """
368
 
        Parses strings like "May 31st, 2006" or "Jan 1st" or "July 2006"
 
391
        Parse long-form date strings::
 
392
 
 
393
            'May 31st, 2006'
 
394
            'Jan 1st'
 
395
            'July 2006'
369
396
 
370
397
        @type  dateString: string
371
398
        @param dateString: text to convert to a datetime
372
399
 
373
 
        @rtype:  datetime
374
 
        @return: calculated datetime value of dateString
 
400
        @rtype:  struct_time
 
401
        @return: calculated C{struct_time} value of dateString
375
402
        """
376
403
        yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime()
377
404
 
379
406
        currentDy  = dy
380
407
 
381
408
        s   = dateString.lower()
382
 
        m   = self.CRE_DATE3.search(s)
 
409
        m   = self.ptc.CRE_DATE3.search(s)
383
410
        mth = m.group('mthname')
384
411
        mth = self.ptc.MonthOffsets[mth]
385
412
 
390
417
 
391
418
        if m.group('year') !=  None:
392
419
            yr = int(m.group('year'))
 
420
 
 
421
            # birthday epoch constraint
 
422
            if yr < self.ptc.BirthdayEpoch:
 
423
                yr += 2000
 
424
            elif yr < 100:
 
425
                yr += 1900
 
426
 
393
427
        elif (mth < currentMth) or (mth == currentMth and dy < currentDy):
394
428
            # if that day and month have already passed in this year,
395
429
            # then increment the year by 1
396
430
            yr += 1
397
431
 
398
 
        if dy > 0 and dy <= self.ptc.DaysInMonthList[mth - 1]:
399
 
            sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
 
432
        if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr):
 
433
            sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
400
434
        else:
401
 
              # Return current time if date string is invalid
402
 
            self.invalidFlag = True
403
 
            sourceTime       = time.localtime()
 
435
            # Return current time if date string is invalid
 
436
            self.dateFlag = 0
 
437
            self.timeFlag = 0
 
438
            sourceTime    = time.localtime()
404
439
 
405
440
        return sourceTime
406
441
 
407
442
 
408
443
    def evalRanges(self, datetimeString, sourceTime=None):
409
444
        """
410
 
        Evaluates the strings with time or date ranges
 
445
        Evaluate the C{datetimeString} text and determine if
 
446
        it represents a date or time range.
411
447
 
412
448
        @type  datetimeString: string
413
449
        @param datetimeString: datetime text to evaluate
414
 
        @type  sourceTime:     datetime
415
 
        @param sourceTime:     datetime value to use as the base
 
450
        @type  sourceTime:     struct_time
 
451
        @param sourceTime:     C{struct_time} value to use as the base
416
452
 
417
453
        @rtype:  tuple
418
 
        @return: tuple of the start datetime, end datetime and the invalid flag
 
454
        @return: tuple of: start datetime, end datetime and the invalid flag
419
455
        """
420
456
        startTime = ''
421
457
        endTime   = ''
423
459
        endDate   = ''
424
460
        rangeFlag = 0
425
461
 
426
 
        s = string.strip(datetimeString.lower())
427
 
 
428
 
        m = self.CRE_TIMERNG1.search(s)
 
462
        s = datetimeString.strip().lower()
 
463
 
 
464
        if self.ptc.rangeSep in s:
 
465
            s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep)
 
466
            s = s.replace('  ', ' ')
 
467
 
 
468
        m = self.ptc.CRE_TIMERNG1.search(s)
429
469
        if m is not None:
430
470
            rangeFlag = 1
431
471
        else:
432
 
            m = self.CRE_TIMERNG2.search(s)
 
472
            m = self.ptc.CRE_TIMERNG2.search(s)
433
473
            if m is not None:
434
474
                rangeFlag = 2
435
475
            else:
436
 
                m = self.CRE_TIMERNG3.search(s)
 
476
                m = self.ptc.CRE_TIMERNG4.search(s)
437
477
                if m is not None:
438
 
                    rangeFlag = 3
 
478
                    rangeFlag = 7
439
479
                else:
440
 
                    m = self.CRE_DATERNG1.search(s)
 
480
                    m = self.ptc.CRE_TIMERNG3.search(s)
441
481
                    if m is not None:
442
 
                        rangeFlag = 4
 
482
                        rangeFlag = 3
443
483
                    else:
444
 
                        m = self.CRE_DATERNG2.search(s)
 
484
                        m = self.ptc.CRE_DATERNG1.search(s)
445
485
                        if m is not None:
446
 
                            rangeFlag = 5
 
486
                            rangeFlag = 4
447
487
                        else:
448
 
                            m = self.CRE_DATERNG3.search(s)
 
488
                            m = self.ptc.CRE_DATERNG2.search(s)
449
489
                            if m is not None:
450
 
                                rangeFlag = 6
 
490
                                rangeFlag = 5
 
491
                            else:
 
492
                                m = self.ptc.CRE_DATERNG3.search(s)
 
493
                                if m is not None:
 
494
                                    rangeFlag = 6
451
495
 
452
496
        if _debug:
453
497
            print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s
463
507
 
464
508
                sourceTime, flag = self.parse(s, sourceTime)
465
509
 
466
 
                if flag == True:
 
510
                if flag == 0:
467
511
                    sourceTime = None
468
512
            else:
469
513
                parseStr = s
470
514
 
471
515
        if rangeFlag == 1:
472
 
            # FIXME hardcoded seperator
473
 
            m                = re.search('-', parseStr)
 
516
            m                = re.search(self.ptc.rangeSep, parseStr)
474
517
            startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
475
518
            endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
476
519
 
477
 
            if eflag is False and sflag is False:
478
 
                return (startTime, endTime, False)
 
520
            if (eflag != 0)  and (sflag != 0):
 
521
                return (startTime, endTime, 2)
479
522
 
480
523
        elif rangeFlag == 2:
481
 
            # FIXME hardcoded seperator
482
 
            m                = re.search('-', parseStr)
483
 
            startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime)
 
524
            m                = re.search(self.ptc.rangeSep, parseStr)
 
525
            startTime, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
484
526
            endTime, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
485
527
 
486
 
            if eflag is False and sflag is False:
487
 
                return (startTime, endTime, False)
488
 
 
489
 
        elif rangeFlag == 3:
490
 
            # FIXME hardcoded seperator
491
 
            m = re.search('-', parseStr)
492
 
 
 
528
            if (eflag != 0)  and (sflag != 0):
 
529
                return (startTime, endTime, 2)
 
530
 
 
531
        elif rangeFlag == 3 or rangeFlag == 7:
 
532
            m = re.search(self.ptc.rangeSep, parseStr)
493
533
            # capturing the meridian from the end time
494
 
            # FIXME hardcoded meridian
495
534
            if self.ptc.usesMeridian:
496
 
                ampm = re.search('a', parseStr)
 
535
                ampm = re.search(self.ptc.am[0], parseStr)
497
536
 
498
537
                # appending the meridian to the start time
499
538
                if ampm is not None:
505
544
 
506
545
            endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime)
507
546
 
508
 
            if eflag is False and sflag is False:
509
 
                return (startTime, endTime, False)
 
547
            if (eflag != 0)  and (sflag != 0):
 
548
                return (startTime, endTime, 2)
510
549
 
511
550
        elif rangeFlag == 4:
512
 
            # FIXME hardcoded seperator
513
 
            m                = re.search('-', parseStr)
 
551
            m                = re.search(self.ptc.rangeSep, parseStr)
514
552
            startDate, sflag = self.parse((parseStr[:m.start()]),       sourceTime)
515
553
            endDate, eflag   = self.parse((parseStr[(m.start() + 1):]), sourceTime)
516
554
 
517
 
            if eflag is False and sflag is False:
518
 
                return (startDate, endDate, False)
 
555
            if (eflag != 0)  and (sflag != 0):
 
556
                return (startDate, endDate, 1)
519
557
 
520
558
        elif rangeFlag == 5:
521
 
            # FIXME hardcoded seperator
522
 
            m       = re.search('-', parseStr)
 
559
            m       = re.search(self.ptc.rangeSep, parseStr)
523
560
            endDate = parseStr[(m.start() + 1):]
524
561
 
525
562
            # capturing the year from the end date
526
 
            date    = self.CRE_DATE3.search(endDate)
 
563
            date    = self.ptc.CRE_DATE3.search(endDate)
527
564
            endYear = date.group('year')
528
565
 
529
566
            # appending the year to the start date if the start date
530
567
            # does not have year information and the end date does.
531
568
            # eg : "Aug 21 - Sep 4, 2007"
532
569
            if endYear is not None:
533
 
                startDate = parseStr[:m.start()]
534
 
                date      = self.CRE_DATE3.search(startDate)
 
570
                startDate = (parseStr[:m.start()]).strip()
 
571
                date      = self.ptc.CRE_DATE3.search(startDate)
535
572
                startYear = date.group('year')
536
573
 
537
574
                if startYear is None:
538
 
                    startDate += endYear
 
575
                    startDate = startDate + ', ' + endYear
539
576
            else:
540
577
                startDate = parseStr[:m.start()]
541
578
 
542
579
            startDate, sflag = self.parse(startDate, sourceTime)
543
580
            endDate, eflag   = self.parse(endDate, sourceTime)
544
581
 
545
 
            if eflag is False and sflag is False:
546
 
                return (startDate, endDate, False)
 
582
            if (eflag != 0)  and (sflag != 0):
 
583
                return (startDate, endDate, 1)
547
584
 
548
585
        elif rangeFlag == 6:
549
 
            # FIXME hardcoded seperator
550
 
            m = re.search('-', parseStr)
 
586
            m = re.search(self.ptc.rangeSep, parseStr)
551
587
 
552
588
            startDate = parseStr[:m.start()]
553
589
 
554
590
            # capturing the month from the start date
555
 
            mth = self.CRE_DATE3.search(startDate)
 
591
            mth = self.ptc.CRE_DATE3.search(startDate)
556
592
            mth = mth.group('mthname')
557
593
 
558
594
            # appending the month name to the end date
561
597
            startDate, sflag = self.parse(startDate, sourceTime)
562
598
            endDate, eflag   = self.parse(endDate, sourceTime)
563
599
 
564
 
            if eflag is False and sflag is False:
565
 
                return (startDate, endDate, False)
 
600
            if (eflag != 0)  and (sflag != 0):
 
601
                return (startDate, endDate, 1)
566
602
        else:
567
603
            # if range is not found
568
604
            sourceTime = time.localtime()
569
605
 
570
 
            return (sourceTime, sourceTime, True)
 
606
            return (sourceTime, sourceTime, 0)
 
607
 
 
608
 
 
609
    def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle):
 
610
        """
 
611
        Based on the C{style} and C{currentDayStyle} determine what
 
612
        day-of-week value is to be returned.
 
613
 
 
614
        @type  wd:              integer
 
615
        @param wd:              day-of-week value for the current day
 
616
        @type  wkdy:            integer
 
617
        @param wkdy:            day-of-week value for the parsed day
 
618
        @type  offset:          integer
 
619
        @param offset:          offset direction for any modifiers (-1, 0, 1)
 
620
        @type  style:           integer
 
621
        @param style:           normally the value set in C{Constants.DOWParseStyle}
 
622
        @type  currentDayStyle: integer
 
623
        @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle}
 
624
 
 
625
        @rtype:  integer
 
626
        @return: calculated day-of-week
 
627
        """
 
628
        if offset == 1:
 
629
            # modifier is indicating future week eg: "next".
 
630
            # DOW is calculated as DOW of next week
 
631
            diff = 7 - wd + wkdy
 
632
 
 
633
        elif offset == -1:
 
634
            # modifier is indicating past week eg: "last","previous"
 
635
            # DOW is calculated as DOW of previous week
 
636
            diff = wkdy - wd - 7
 
637
 
 
638
        elif offset == 0:
 
639
            # modifier is indiacting current week eg: "this"
 
640
            # DOW is calculated as DOW of this week
 
641
            diff = wkdy - wd
 
642
 
 
643
        elif offset == 2:
 
644
            # no modifier is present.
 
645
            # i.e. string to be parsed is just DOW
 
646
            if style == 1:
 
647
                # next occurance of the DOW is calculated
 
648
                if currentDayStyle == True:
 
649
                    if wkdy >= wd:
 
650
                        diff = wkdy - wd
 
651
                    else:
 
652
                        diff = 7 - wd + wkdy
 
653
                else:
 
654
                    if wkdy > wd:
 
655
                        diff = wkdy - wd
 
656
                    else:
 
657
                        diff = 7 - wd + wkdy
 
658
 
 
659
            elif style == -1:
 
660
                # last occurance of the DOW is calculated
 
661
                if currentDayStyle == True:
 
662
                    if wkdy <= wd:
 
663
                        diff = wkdy - wd
 
664
                    else:
 
665
                        diff = wkdy - wd - 7
 
666
                else:
 
667
                    if wkdy < wd:
 
668
                        diff = wkdy - wd
 
669
                    else:
 
670
                        diff = wkdy - wd - 7
 
671
            else:
 
672
                # occurance of the DOW in the current week is calculated
 
673
                diff = wkdy - wd
 
674
 
 
675
        if _debug:
 
676
            print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style)
 
677
 
 
678
        return diff
571
679
 
572
680
 
573
681
    def _evalModifier(self, modifier, chunk1, chunk2, sourceTime):
574
682
        """
575
 
        Evaluate the modifier string and following text (passed in
576
 
        as chunk1 and chunk2) and if they match any known modifiers
577
 
        calculate the delta and apply it to sourceTime
 
683
        Evaluate the C{modifier} string and following text (passed in
 
684
        as C{chunk1} and C{chunk2}) and if they match any known modifiers
 
685
        calculate the delta and apply it to C{sourceTime}.
578
686
 
579
687
        @type  modifier:   string
580
688
        @param modifier:   modifier text to apply to sourceTime
582
690
        @param chunk1:     first text chunk that followed modifier (if any)
583
691
        @type  chunk2:     string
584
692
        @param chunk2:     second text chunk that followed modifier (if any)
585
 
        @type  sourceTime: datetime
586
 
        @param sourceTime: datetime value to use as the base
 
693
        @type  sourceTime: struct_time
 
694
        @param sourceTime: C{struct_time} value to use as the base
587
695
 
588
696
        @rtype:  tuple
589
 
        @return: tuple of any remaining text and the modified sourceTime
 
697
        @return: tuple of: remaining text and the modified sourceTime
590
698
        """
591
699
        offset = self.ptc.Modifiers[modifier]
592
700
 
595
703
        else:
596
704
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
597
705
 
598
 
        # capture the units after the modifier and the remaining string after the unit
599
 
        m = self.CRE_REMAINING.search(chunk2)
 
706
        # capture the units after the modifier and the remaining
 
707
        # string after the unit
 
708
        m = self.ptc.CRE_REMAINING.search(chunk2)
600
709
        if m is not None:
601
710
            index  = m.start() + 1
602
711
            unit   = chunk2[:m.start()]
608
717
        flag = False
609
718
 
610
719
        if unit == 'month' or \
611
 
           unit == 'mth':
 
720
           unit == 'mth' or \
 
721
           unit == 'm':
612
722
            if offset == 0:
613
 
                dy         = self.ptc.DaysInMonthList[mth - 1]
 
723
                dy         = self.ptc.daysInMonth(mth, yr)
614
724
                sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst)
615
725
            elif offset == 2:
616
 
                # if day is the last day of the month, calculate the last day of the next month
617
 
                if dy == self.ptc.DaysInMonthList[mth - 1]:
618
 
                    dy = self.ptc.DaysInMonthList[mth]
 
726
                # if day is the last day of the month, calculate the last day
 
727
                # of the next month
 
728
                if dy == self.ptc.daysInMonth(mth, yr):
 
729
                    dy = self.ptc.daysInMonth(mth + 1, yr)
619
730
 
620
731
                start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
621
732
                target     = self.inc(start, month=1)
626
737
                sourceTime = target.timetuple()
627
738
 
628
739
            flag = True
 
740
            self.dateFlag = 1
629
741
 
630
742
        if unit == 'week' or \
631
743
             unit == 'wk' or \
641
753
            else:
642
754
                return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime)
643
755
 
644
 
            flag = True
 
756
            flag          = True
 
757
            self.dateFlag = 1
645
758
 
646
759
        if unit == 'day' or \
647
760
            unit == 'dy' or \
648
761
            unit == 'd':
649
762
            if offset == 0:
650
 
                sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
 
763
                sourceTime    = (yr, mth, dy, 17, 0, 0, wd, yd, isdst)
 
764
                self.timeFlag = 2
651
765
            elif offset == 2:
652
766
                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
653
767
                target     = start + datetime.timedelta(days=1)
657
771
                target     = start + datetime.timedelta(days=offset)
658
772
                sourceTime = target.timetuple()
659
773
 
660
 
            flag = True
 
774
            flag          = True
 
775
            self.dateFlag = 1
661
776
 
662
777
        if unit == 'hour' or \
663
778
           unit == 'hr':
668
783
                target     = start + datetime.timedelta(hours=offset)
669
784
                sourceTime = target.timetuple()
670
785
 
671
 
            flag = True
 
786
            flag          = True
 
787
            self.timeFlag = 2
672
788
 
673
789
        if unit == 'year' or \
674
790
             unit == 'yr' or \
680
796
            else:
681
797
                sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst)
682
798
 
683
 
            flag = True
 
799
            flag          = True
 
800
            self.dateFlag = 1
684
801
 
685
802
        if flag == False:
686
 
            m = self.CRE_WEEKDAY.match(unit)
 
803
            m = self.ptc.CRE_WEEKDAY.match(unit)
687
804
            if m is not None:
688
 
                wkdy = m.group()
689
 
                wkdy = self.ptc.WeekdayOffsets[wkdy]
690
 
 
691
 
                if offset == 0:
692
 
                    diff       = wkdy - wd
 
805
                wkdy          = m.group()
 
806
                self.dateFlag = 1
 
807
 
 
808
                if modifier == 'eod':
 
809
                    # Calculate the  upcoming weekday
 
810
                    self.modifierFlag = False
 
811
                    (sourceTime, _)   = self.parse(wkdy, sourceTime)
 
812
                    sources           = self.ptc.buildSources(sourceTime)
 
813
                    self.timeFlag     = 2
 
814
 
 
815
                    if modifier in sources:
 
816
                        sourceTime = sources[modifier]
 
817
 
 
818
                else:
 
819
                    wkdy       = self.ptc.WeekdayOffsets[wkdy]
 
820
                    diff       = self._CalculateDOWDelta(wd, wkdy, offset,
 
821
                                                         self.ptc.DOWParseStyle,
 
822
                                                         self.ptc.CurrentDOWParseStyle)
693
823
                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
694
824
                    target     = start + datetime.timedelta(days=diff)
695
825
                    sourceTime = target.timetuple()
696
 
                else:
697
 
                    diff       = wkdy - wd
698
 
                    start      = datetime.datetime(yr, mth, dy, 9, 0, 0)
699
 
                    target     = start + datetime.timedelta(days=diff + 7 * offset)
700
 
                    sourceTime = target.timetuple()
701
826
 
702
 
                flag = True
 
827
                flag          = True
 
828
                self.dateFlag = 1
703
829
 
704
830
        if not flag:
705
 
            m = self.CRE_TIME.match(unit)
 
831
            m = self.ptc.CRE_TIME.match(unit)
706
832
            if m is not None:
707
 
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), self.invalidFlag = self.parse(unit)
 
833
                self.modifierFlag = False
 
834
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit)
 
835
 
708
836
                start      = datetime.datetime(yr, mth, dy, hr, mn, sec)
709
837
                target     = start + datetime.timedelta(days=offset)
710
838
                sourceTime = target.timetuple()
711
 
 
712
 
                flag              = True
 
839
                flag       = True
 
840
            else:
713
841
                self.modifierFlag = False
714
842
 
715
 
        # if the word after next is a number, the string is likely
716
 
        # to be something like "next 4 hrs" for which we have to
717
 
        # combine the units with the rest of the string
 
843
                # check if the remaining text is parsable and if so,
 
844
                # use it as the base time for the modifier source time
 
845
                t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime)
 
846
 
 
847
                if flag2 != 0:
 
848
                    sourceTime = t
 
849
 
 
850
                sources = self.ptc.buildSources(sourceTime)
 
851
 
 
852
                if modifier in sources:
 
853
                    sourceTime    = sources[modifier]
 
854
                    flag          = True
 
855
                    self.timeFlag = 2
 
856
 
 
857
        # if the word after next is a number, the string is more than likely
 
858
        # to be "next 4 hrs" which we will have to combine the units with the
 
859
        # rest of the string
718
860
        if not flag:
719
861
            if offset < 0:
720
862
                # if offset is negative, the unit has to be made negative
724
866
 
725
867
        self.modifierFlag = False
726
868
 
727
 
        return '%s %s' % (chunk1, chunk2), sourceTime
728
 
 
 
869
        #return '%s %s' % (chunk1, chunk2), sourceTime
 
870
        return '%s' % chunk2, sourceTime
729
871
 
730
872
    def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime):
731
873
        """
732
 
        Evaluate the modifier string and following text (passed in
733
 
        as chunk1 and chunk2) and if they match any known modifiers
734
 
        calculate the delta and apply it to sourceTime
 
874
        Evaluate the C{modifier} string and following text (passed in
 
875
        as C{chunk1} and C{chunk2}) and if they match any known modifiers
 
876
        calculate the delta and apply it to C{sourceTime}.
735
877
 
736
878
        @type  modifier:   string
737
 
        @param modifier:   modifier text to apply to sourceTime
 
879
        @param modifier:   modifier text to apply to C{sourceTime}
738
880
        @type  chunk1:     string
739
881
        @param chunk1:     first text chunk that followed modifier (if any)
740
882
        @type  chunk2:     string
741
883
        @param chunk2:     second text chunk that followed modifier (if any)
742
 
        @type  sourceTime: datetime
743
 
        @param sourceTime: datetime value to use as the base
 
884
        @type  sourceTime: struct_time
 
885
        @param sourceTime: C{struct_time} value to use as the base
744
886
 
745
887
        @rtype:  tuple
746
 
        @return: tuple of any remaining text and the modified sourceTime
 
888
        @return: tuple of: remaining text and the modified sourceTime
747
889
        """
748
890
        offset = self.ptc.Modifiers[modifier]
749
891
        digit  = r'\d+'
750
892
 
751
 
        if sourceTime is not None:
752
 
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
753
 
        else:
754
 
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime()
755
 
 
756
893
        self.modifier2Flag = False
757
894
 
758
 
        # If the string after the negative modifier starts with
759
 
        # digits, then it is likely that the string is similar to
760
 
        # " before 3 days" or 'evening prior to 3 days'.
761
 
        # In this case, the total time is calculated by subtracting
762
 
        # '3 days' from the current date.
763
 
        # So, we have to identify the quantity and negate it before
764
 
        # parsing the string.
765
 
        # This is not required for strings not starting with digits
766
 
        # since the string is enough to calculate the sourceTime
767
 
        if offset < 0:
768
 
            m = re.match(digit, string.strip(chunk2))
769
 
            if m is not None:
770
 
                qty    = int(m.group()) * -1
771
 
                chunk2 = chunk2[m.end():]
772
 
                chunk2 = '%d%s' % (qty, chunk2)
 
895
        # If the string after the negative modifier starts with digits,
 
896
        # then it is likely that the string is similar to ' before 3 days'
 
897
        # or 'evening prior to 3 days'.
 
898
        # In this case, the total time is calculated by subtracting '3 days'
 
899
        # from the current date.
 
900
        # So, we have to identify the quantity and negate it before parsing
 
901
        # the string.
 
902
        # This is not required for strings not starting with digits since the
 
903
        # string is enough to calculate the sourceTime
 
904
        if chunk2 != '':
 
905
            if offset < 0:
 
906
                m = re.match(digit, chunk2.strip())
 
907
                if m is not None:
 
908
                    qty    = int(m.group()) * -1
 
909
                    chunk2 = chunk2[m.end():]
 
910
                    chunk2 = '%d%s' % (qty, chunk2)
773
911
 
774
 
        sourceTime, flag = self.parse(chunk2, sourceTime)
 
912
            sourceTime, flag1 = self.parse(chunk2, sourceTime)
 
913
            if flag1 == 0:
 
914
                flag1 = True
 
915
            else:
 
916
                flag1 = False
 
917
            flag2 = False
 
918
        else:
 
919
            flag1 = False
775
920
 
776
921
        if chunk1 != '':
777
922
            if offset < 0:
778
 
                m = re.match(digit, string.strip(chunk1))
 
923
                m = re.search(digit, chunk1.strip())
779
924
                if m is not None:
780
925
                    qty    = int(m.group()) * -1
781
926
                    chunk1 = chunk1[m.end():]
782
927
                    chunk1 = '%d%s' % (qty, chunk1)
783
928
 
784
 
            sourceTime, flag = self.parse(chunk1, sourceTime)
785
 
 
786
 
        return '', sourceTime
 
929
            tempDateFlag       = self.dateFlag
 
930
            tempTimeFlag       = self.timeFlag
 
931
            sourceTime2, flag2 = self.parse(chunk1, sourceTime)
 
932
        else:
 
933
            return sourceTime, (flag1 and flag2)
 
934
 
 
935
        # if chunk1 is not a datetime and chunk2 is then do not use datetime
 
936
        # value returned by parsing chunk1
 
937
        if not (flag1 == False and flag2 == 0):
 
938
            sourceTime = sourceTime2
 
939
        else:
 
940
            self.timeFlag = tempTimeFlag
 
941
            self.dateFlag = tempDateFlag
 
942
 
 
943
        return sourceTime, (flag1 and flag2)
787
944
 
788
945
 
789
946
    def _evalString(self, datetimeString, sourceTime=None):
797
954
            DD MMMM YYYY
798
955
 
799
956
        @type  datetimeString: string
800
 
        @param datetimeString: text to try and parse as more "traditional" date/time text
801
 
        @type  sourceTime:     datetime
802
 
        @param sourceTime:     datetime value to use as the base
 
957
        @param datetimeString: text to try and parse as more "traditional"
 
958
                               date/time text
 
959
        @type  sourceTime:     struct_time
 
960
        @param sourceTime:     C{struct_time} value to use as the base
803
961
 
804
962
        @rtype:  datetime
805
 
        @return: calculated datetime value or current datetime if not parsed
 
963
        @return: calculated C{struct_time} value or current C{struct_time}
 
964
                 if not parsed
806
965
        """
807
 
        s   = string.strip(datetimeString)
 
966
        s   = datetimeString.strip()
808
967
        now = time.localtime()
809
968
 
810
 
          # Given string date is a RFC822 date
 
969
        # Given string date is a RFC822 date
811
970
        if sourceTime is None:
812
971
            sourceTime = _parse_date_rfc822(s)
813
972
 
814
 
          # Given string date is a W3CDTF date
 
973
            if sourceTime is not None:
 
974
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime
 
975
                self.dateFlag = 1
 
976
 
 
977
                if (hr != 0) and (mn != 0) and (sec != 0):
 
978
                    self.timeFlag = 2
 
979
 
 
980
                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
 
981
 
 
982
        # Given string date is a W3CDTF date
815
983
        if sourceTime is None:
816
984
            sourceTime = _parse_date_w3dtf(s)
817
985
 
 
986
            if sourceTime is not None:
 
987
                self.dateFlag = 1
 
988
                self.timeFlag = 2
 
989
 
818
990
        if sourceTime is None:
819
991
            s = s.lower()
820
992
 
821
 
          # Given string is in the format HH:MM(:SS)(am/pm)
 
993
        # Given string is in the format HH:MM(:SS)(am/pm)
822
994
        if self.meridianFlag:
823
995
            if sourceTime is None:
824
996
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
825
997
            else:
826
998
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
827
999
 
828
 
            m = self.CRE_TIMEHMS2.search(s)
 
1000
            m = self.ptc.CRE_TIMEHMS2.search(s)
829
1001
            if m is not None:
830
1002
                dt = s[:m.start('meridian')].strip()
831
1003
                if len(dt) <= 2:
851
1023
 
852
1024
              # invalid time
853
1025
            if hr > 24 or mn > 59 or sec > 59:
854
 
                sourceTime       = now
855
 
                self.invalidFlag = True
 
1026
                sourceTime    = now
 
1027
                self.dateFlag = 0
 
1028
                self.timeFlag = 0
856
1029
 
857
1030
            self.meridianFlag = False
858
1031
 
859
1032
          # Given string is in the format HH:MM(:SS)
860
 
        if self.timeFlag:
 
1033
        if self.timeStdFlag:
861
1034
            if sourceTime is None:
862
1035
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
863
1036
            else:
864
1037
                (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime
865
1038
 
866
 
            m = self.CRE_TIMEHMS.search(s)
 
1039
            m = self.ptc.CRE_TIMEHMS.search(s)
867
1040
            if m is not None:
868
1041
                hr, mn, sec = _extract_time(m)
869
1042
            if hr == 24:
871
1044
 
872
1045
            if hr > 24 or mn > 59 or sec > 59:
873
1046
                # invalid time
874
 
                sourceTime       = now
875
 
                self.invalidFlag = True
 
1047
                sourceTime    = now
 
1048
                self.dateFlag = 0
 
1049
                self.timeFlag = 0
876
1050
            else:
877
1051
                sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst)
878
1052
 
879
 
            self.timeFlag = False
 
1053
            self.timeStdFlag = False
880
1054
 
881
 
          # Given string is in the format 07/21/2006
 
1055
        # Given string is in the format 07/21/2006
882
1056
        if self.dateStdFlag:
883
1057
            sourceTime       = self.parseDate(s)
884
1058
            self.dateStdFlag = False
885
1059
 
886
 
          # Given string is in the format  "May 23rd, 2005"
 
1060
        # Given string is in the format  "May 23rd, 2005"
887
1061
        if self.dateStrFlag:
888
1062
            sourceTime       = self.parseDateText(s)
889
1063
            self.dateStrFlag = False
890
1064
 
891
 
          # Given string is a weekday
 
1065
        # Given string is a weekday
892
1066
        if self.weekdyFlag:
893
1067
            (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now
894
1068
 
895
1069
            start = datetime.datetime(yr, mth, dy, hr, mn, sec)
896
 
            wkDy  = self.ptc.WeekdayOffsets[s]
 
1070
            wkdy  = self.ptc.WeekdayOffsets[s]
897
1071
 
898
 
            if wkDy > wd:
899
 
                qty    = wkDy - wd
900
 
                target = start + datetime.timedelta(days=qty)
901
 
                wd     = wkDy
 
1072
            if wkdy > wd:
 
1073
                qty = self._CalculateDOWDelta(wd, wkdy, 2,
 
1074
                                              self.ptc.DOWParseStyle,
 
1075
                                              self.ptc.CurrentDOWParseStyle)
902
1076
            else:
903
 
                qty    = 6 - wd + wkDy + 1
904
 
                target = start + datetime.timedelta(days=qty)
905
 
                wd     = wkDy
 
1077
                qty = self._CalculateDOWDelta(wd, wkdy, 2,
 
1078
                                              self.ptc.DOWParseStyle,
 
1079
                                              self.ptc.CurrentDOWParseStyle)
 
1080
 
 
1081
            target = start + datetime.timedelta(days=qty)
 
1082
            wd     = wkdy
906
1083
 
907
1084
            sourceTime      = target.timetuple()
908
1085
            self.weekdyFlag = False
909
1086
 
910
 
          # Given string is a natural language time string like lunch, midnight, etc
 
1087
        # Given string is a natural language time string like
 
1088
        # lunch, midnight, etc
911
1089
        if self.timeStrFlag:
912
1090
            if s in self.ptc.re_values['now']:
913
1091
                sourceTime = now
914
1092
            else:
915
 
                sources = self.ptc.buildSources(now)
 
1093
                sources = self.ptc.buildSources(sourceTime)
916
1094
 
917
1095
                if s in sources:
918
1096
                    sourceTime = sources[s]
919
1097
                else:
920
 
                    sourceTime       = now
921
 
                    self.invalidFlag = True
 
1098
                    sourceTime    = now
 
1099
                    self.dateFlag = 0
 
1100
                    self.timeFlag = 0
922
1101
 
923
1102
            self.timeStrFlag = False
924
1103
 
925
 
           # Given string is a natural language date string like today, tomorrow..
 
1104
        # Given string is a natural language date string like today, tomorrow..
926
1105
        if self.dayStrFlag:
927
1106
            if sourceTime is None:
928
1107
                sourceTime = now
940
1119
 
941
1120
            self.dayStrFlag = False
942
1121
 
943
 
          # Given string is a time string with units like "5 hrs 30 min"
 
1122
        # Given string is a time string with units like "5 hrs 30 min"
944
1123
        if self.unitsFlag:
945
1124
            modifier = ''  # TODO
946
1125
 
947
1126
            if sourceTime is None:
948
1127
                sourceTime = now
949
1128
 
950
 
            m = self.CRE_UNITS.search(s)
 
1129
            m = self.ptc.CRE_UNITS.search(s)
951
1130
            if m is not None:
952
1131
                units    = m.group('units')
953
1132
                quantity = s[:m.start('units')]
955
1134
            sourceTime     = self._buildTime(sourceTime, quantity, modifier, units)
956
1135
            self.unitsFlag = False
957
1136
 
958
 
          # Given string is a time string with single char units like "5 h 30 m"
 
1137
        # Given string is a time string with single char units like "5 h 30 m"
959
1138
        if self.qunitsFlag:
960
1139
            modifier = ''  # TODO
961
1140
 
962
1141
            if sourceTime is None:
963
1142
                sourceTime = now
964
1143
 
965
 
            m = self.CRE_QUNITS.search(s)
 
1144
            m = self.ptc.CRE_QUNITS.search(s)
966
1145
            if m is not None:
967
1146
                units    = m.group('qunits')
968
1147
                quantity = s[:m.start('qunits')]
972
1151
 
973
1152
          # Given string does not match anything
974
1153
        if sourceTime is None:
975
 
            sourceTime       = now
976
 
            self.invalidFlag = True
 
1154
            sourceTime    = now
 
1155
            self.dateFlag = 0
 
1156
            self.timeFlag = 0
977
1157
 
978
1158
        return sourceTime
979
1159
 
980
1160
 
981
1161
    def parse(self, datetimeString, sourceTime=None):
982
1162
        """
983
 
        Splits the L{datetimeString} into tokens, finds the regex patters
984
 
        that match and then calculates a datetime value from the chunks
985
 
 
986
 
        if L{sourceTime} is given then the datetime value will be calcualted
987
 
        from that datetime, otherwise from the current datetime.
 
1163
        Splits the given C{datetimeString} into tokens, finds the regex
 
1164
        patterns that match and then calculates a C{struct_time} value from
 
1165
        the chunks.
 
1166
 
 
1167
        If C{sourceTime} is given then the C{struct_time} value will be
 
1168
        calculated from that value, otherwise from the current date/time.
 
1169
 
 
1170
        If the C{datetimeString} is parsed and date/time value found then
 
1171
        the second item of the returned tuple will be a flag to let you know
 
1172
        what kind of C{struct_time} value is being returned::
 
1173
 
 
1174
            0 = not parsed at all
 
1175
            1 = parsed as a C{date}
 
1176
            2 = parsed as a C{time}
 
1177
            3 = parsed as a C{datetime}
988
1178
 
989
1179
        @type  datetimeString: string
990
 
        @param datetimeString: datetime text to evaluate
991
 
        @type  sourceTime:     datetime
992
 
        @param sourceTime:     datetime value to use as the base
 
1180
        @param datetimeString: date/time text to evaluate
 
1181
        @type  sourceTime:     struct_time
 
1182
        @param sourceTime:     C{struct_time} value to use as the base
993
1183
 
994
1184
        @rtype:  tuple
995
 
        @return: tuple of any remaining text and the modified sourceTime
 
1185
        @return: tuple of: modified C{sourceTime} and the result flag
996
1186
        """
997
 
        s         = string.strip(datetimeString.lower())
998
 
        dateStr   = ''
 
1187
 
 
1188
        if sourceTime:
 
1189
            if isinstance(sourceTime, datetime.datetime):
 
1190
                if _debug:
 
1191
                    print 'coercing datetime to timetuple'
 
1192
                sourceTime = sourceTime.timetuple()
 
1193
            else:
 
1194
                if not isinstance(sourceTime, time.struct_time) and \
 
1195
                   not isinstance(sourceTime, tuple):
 
1196
                    raise Exception('sourceTime is not a struct_time')
 
1197
 
 
1198
        s         = datetimeString.strip().lower()
999
1199
        parseStr  = ''
1000
1200
        totalTime = sourceTime
1001
1201
 
1002
 
        self.invalidFlag = False
1003
 
 
1004
1202
        if s == '' :
1005
1203
            if sourceTime is not None:
1006
 
                return (sourceTime, False)
 
1204
                return (sourceTime, self.dateFlag + self.timeFlag)
1007
1205
            else:
1008
 
                return (time.localtime(), True)
 
1206
                return (time.localtime(), 0)
 
1207
 
 
1208
        self.timeFlag = 0
 
1209
        self.dateFlag = 0
1009
1210
 
1010
1211
        while len(s) > 0:
1011
1212
            flag   = False
1017
1218
 
1018
1219
            if parseStr == '':
1019
1220
                # Modifier like next\prev..
1020
 
                m = self.CRE_MODIFIER.search(s)
 
1221
                m = self.ptc.CRE_MODIFIER.search(s)
1021
1222
                if m is not None:
1022
1223
                    self.modifierFlag = True
1023
1224
                    if (m.group('modifier') != s):
1024
1225
                        # capture remaining string
1025
1226
                        parseStr = m.group('modifier')
1026
 
                        chunk1   = string.strip(s[:m.start('modifier')])
1027
 
                        chunk2   = string.strip(s[m.end('modifier'):])
 
1227
                        chunk1   = s[:m.start('modifier')].strip()
 
1228
                        chunk2   = s[m.end('modifier'):].strip()
1028
1229
                        flag     = True
1029
1230
                    else:
1030
1231
                        parseStr = s
1031
1232
 
1032
1233
            if parseStr == '':
1033
1234
                # Modifier like from\after\prior..
1034
 
                m = self.CRE_MODIFIER2.search(s)
 
1235
                m = self.ptc.CRE_MODIFIER2.search(s)
1035
1236
                if m is not None:
1036
1237
                    self.modifier2Flag = True
1037
1238
                    if (m.group('modifier') != s):
1038
1239
                        # capture remaining string
1039
1240
                        parseStr = m.group('modifier')
1040
 
                        chunk1   = string.strip(s[:m.start('modifier')])
1041
 
                        chunk2   = string.strip(s[m.end('modifier'):])
 
1241
                        chunk1   = s[:m.start('modifier')].strip()
 
1242
                        chunk2   = s[m.end('modifier'):].strip()
1042
1243
                        flag     = True
1043
1244
                    else:
1044
1245
                        parseStr = s
1045
1246
 
1046
1247
            if parseStr == '':
 
1248
                valid_date = False
 
1249
                for match in self.ptc.CRE_DATE3.finditer(s):
 
1250
                    # to prevent "HH:MM(:SS) time strings" expressions from triggering
 
1251
                    # this regex, we checks if the month field exists in the searched 
 
1252
                    # expression, if it doesn't exist, the date field is not valid
 
1253
                    if match.group('mthname'):
 
1254
                        m = self.ptc.CRE_DATE3.search(s, match.start())
 
1255
                        valid_date = True
 
1256
                        break
 
1257
 
1047
1258
                # String date format
1048
 
                m = self.CRE_DATE3.search(s)
1049
 
                if m is not None:
 
1259
                if valid_date:
1050
1260
                    self.dateStrFlag = True
 
1261
                    self.dateFlag    = 1
1051
1262
                    if (m.group('date') != s):
1052
1263
                        # capture remaining string
1053
1264
                        parseStr = m.group('date')
1060
1271
 
1061
1272
            if parseStr == '':
1062
1273
                # Standard date format
1063
 
                m = self.CRE_DATE.search(s)
 
1274
                m = self.ptc.CRE_DATE.search(s)
1064
1275
                if m is not None:
1065
1276
                    self.dateStdFlag = True
 
1277
                    self.dateFlag    = 1
1066
1278
                    if (m.group('date') != s):
1067
1279
                        # capture remaining string
1068
1280
                        parseStr = m.group('date')
1075
1287
 
1076
1288
            if parseStr == '':
1077
1289
                # Natural language day strings
1078
 
                m = self.CRE_DAY.search(s)
 
1290
                m = self.ptc.CRE_DAY.search(s)
1079
1291
                if m is not None:
1080
1292
                    self.dayStrFlag = True
 
1293
                    self.dateFlag   = 1
1081
1294
                    if (m.group('day') != s):
1082
1295
                        # capture remaining string
1083
1296
                        parseStr = m.group('day')
1090
1303
 
1091
1304
            if parseStr == '':
1092
1305
                # Quantity + Units
1093
 
                m = self.CRE_UNITS.search(s)
 
1306
                m = self.ptc.CRE_UNITS.search(s)
1094
1307
                if m is not None:
1095
1308
                    self.unitsFlag = True
1096
1309
                    if (m.group('qty') != s):
1110
1323
 
1111
1324
            if parseStr == '':
1112
1325
                # Quantity + Units
1113
 
                m = self.CRE_QUNITS.search(s)
 
1326
                m = self.ptc.CRE_QUNITS.search(s)
1114
1327
                if m is not None:
1115
1328
                    self.qunitsFlag = True
 
1329
 
1116
1330
                    if (m.group('qty') != s):
1117
1331
                        # capture remaining string
1118
1332
                        parseStr = m.group('qty')
1130
1344
 
1131
1345
            if parseStr == '':
1132
1346
                # Weekday
1133
 
                m = self.CRE_WEEKDAY.search(s)
 
1347
                m = self.ptc.CRE_WEEKDAY.search(s)
1134
1348
                if m is not None:
1135
 
                    self.weekdyFlag = True
1136
 
                    if (m.group('weekday') != s):
1137
 
                        # capture remaining string
1138
 
                        parseStr = m.group()
1139
 
                        chunk1   = s[:m.start('weekday')]
1140
 
                        chunk2   = s[m.end('weekday'):]
1141
 
                        s        = '%s %s' % (chunk1, chunk2)
1142
 
                        flag     = True
1143
 
                    else:
1144
 
                        parseStr = s
 
1349
                    gv = m.group('weekday')
 
1350
                    if s not in self.ptc.dayOffsets:
 
1351
                        self.weekdyFlag = True
 
1352
                        self.dateFlag   = 1
 
1353
                        if (gv != s):
 
1354
                            # capture remaining string
 
1355
                            parseStr = gv
 
1356
                            chunk1   = s[:m.start('weekday')]
 
1357
                            chunk2   = s[m.end('weekday'):]
 
1358
                            s        = '%s %s' % (chunk1, chunk2)
 
1359
                            flag     = True
 
1360
                        else:
 
1361
                            parseStr = s
1145
1362
 
1146
1363
            if parseStr == '':
1147
1364
                # Natural language time strings
1148
 
                m = self.CRE_TIME.search(s)
 
1365
                m = self.ptc.CRE_TIME.search(s)
1149
1366
                if m is not None:
1150
1367
                    self.timeStrFlag = True
 
1368
                    self.timeFlag    = 2
1151
1369
                    if (m.group('time') != s):
1152
1370
                        # capture remaining string
1153
1371
                        parseStr = m.group('time')
1160
1378
 
1161
1379
            if parseStr == '':
1162
1380
                # HH:MM(:SS) am/pm time strings
1163
 
                m = self.CRE_TIMEHMS2.search(s)
 
1381
                m = self.ptc.CRE_TIMEHMS2.search(s)
1164
1382
                if m is not None:
1165
1383
                    self.meridianFlag = True
 
1384
                    self.timeFlag     = 2
1166
1385
                    if m.group('minutes') is not None:
1167
1386
                        if m.group('seconds') is not None:
1168
 
                            parseStr = '%s:%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('seconds'), m.group('meridian'))
 
1387
                            parseStr = '%s:%s:%s %s' % (m.group('hours'),
 
1388
                                                        m.group('minutes'),
 
1389
                                                        m.group('seconds'),
 
1390
                                                        m.group('meridian'))
1169
1391
                        else:
1170
 
                            parseStr = '%s:%s %s' % (m.group('hours'), m.group('minutes'), m.group('meridian'))
 
1392
                            parseStr = '%s:%s %s' % (m.group('hours'),
 
1393
                                                     m.group('minutes'),
 
1394
                                                     m.group('meridian'))
1171
1395
                    else:
1172
 
                        parseStr = '%s %s' % (m.group('hours'), m.group('meridian'))
 
1396
                        parseStr = '%s %s' % (m.group('hours'),
 
1397
                                              m.group('meridian'))
1173
1398
 
1174
1399
                    chunk1 = s[:m.start('hours')]
1175
1400
                    chunk2 = s[m.end('meridian'):]
1179
1404
 
1180
1405
            if parseStr == '':
1181
1406
                # HH:MM(:SS) time strings
1182
 
                m = self.CRE_TIMEHMS.search(s)
 
1407
                m = self.ptc.CRE_TIMEHMS.search(s)
1183
1408
                if m is not None:
1184
 
                    self.timeFlag = True
 
1409
                    self.timeStdFlag = True
 
1410
                    self.timeFlag    = 2
1185
1411
                    if m.group('seconds') is not None:
1186
 
                        parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds'))
 
1412
                        parseStr = '%s:%s:%s' % (m.group('hours'),
 
1413
                                                 m.group('minutes'),
 
1414
                                                 m.group('seconds'))
1187
1415
                        chunk1   = s[:m.start('hours')]
1188
1416
                        chunk2   = s[m.end('seconds'):]
1189
1417
                    else:
1190
 
                        parseStr = '%s:%s' % (m.group('hours'), m.group('minutes'))
 
1418
                        parseStr = '%s:%s' % (m.group('hours'),
 
1419
                                              m.group('minutes'))
1191
1420
                        chunk1   = s[:m.start('hours')]
1192
1421
                        chunk2   = s[m.end('minutes'):]
1193
1422
 
1194
1423
                    s    = '%s %s' % (chunk1, chunk2)
1195
1424
                    flag = True
1196
1425
 
1197
 
            # if string does not match any regex, empty string to come out of the while loop
 
1426
            # if string does not match any regex, empty string to
 
1427
            # come out of the while loop
1198
1428
            if not flag:
1199
1429
                s = ''
1200
1430
 
1201
1431
            if _debug:
1202
1432
                print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2)
1203
 
                print 'invalid %s, weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
1204
 
                       (self.invalidFlag, self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeFlag, self.timeStrFlag, self.meridianFlag)
 
1433
                print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \
 
1434
                       (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag)
1205
1435
                print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \
1206
1436
                       (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag)
1207
1437
 
1209
1439
            if parseStr != '':
1210
1440
                if self.modifierFlag == True:
1211
1441
                    t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime)
1212
 
 
1213
 
                    return self.parse(t, totalTime)
 
1442
                    # t is the unparsed part of the chunks.
 
1443
                    # If it is not date/time, return current
 
1444
                    # totalTime as it is; else return the output
 
1445
                    # after parsing t.
 
1446
                    if (t != '') and (t != None):
 
1447
                        tempDateFlag       = self.dateFlag
 
1448
                        tempTimeFlag       = self.timeFlag
 
1449
                        (totalTime2, flag) = self.parse(t, totalTime)
 
1450
 
 
1451
                        if flag == 0 and totalTime is not None:
 
1452
                            self.timeFlag = tempTimeFlag
 
1453
                            self.dateFlag = tempDateFlag
 
1454
 
 
1455
                            return (totalTime, self.dateFlag + self.timeFlag)
 
1456
                        else:
 
1457
                            return (totalTime2, self.dateFlag + self.timeFlag)
1214
1458
 
1215
1459
                elif self.modifier2Flag == True:
1216
 
                    s, totalTime = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
 
1460
                    totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime)
 
1461
 
 
1462
                    if invalidFlag == True:
 
1463
                        self.dateFlag = 0
 
1464
                        self.timeFlag = 0
 
1465
 
1217
1466
                else:
1218
1467
                    totalTime = self._evalString(parseStr, totalTime)
1219
1468
                    parseStr  = ''
1220
1469
 
1221
1470
        # String is not parsed at all
1222
1471
        if totalTime is None or totalTime == sourceTime:
1223
 
            totalTime        = time.localtime()
1224
 
            self.invalidFlag = True
 
1472
            totalTime     = time.localtime()
 
1473
            self.dateFlag = 0
 
1474
            self.timeFlag = 0
1225
1475
 
1226
 
        return (totalTime, self.invalidFlag)
 
1476
        return (totalTime, self.dateFlag + self.timeFlag)
1227
1477
 
1228
1478
 
1229
1479
    def inc(self, source, month=None, year=None):
1230
1480
        """
1231
 
        Takes the given date, or current date if none is passed, and
1232
 
        increments it according to the values passed in by month
1233
 
        and/or year.
1234
 
 
1235
 
        This routine is needed because the timedelta() routine does
1236
 
        not allow for month or year increments.
1237
 
 
1238
 
        @type  source: datetime
1239
 
        @param source: datetime value to increment
 
1481
        Takes the given C{source} date, or current date if none is
 
1482
        passed, and increments it according to the values passed in
 
1483
        by month and/or year.
 
1484
 
 
1485
        This routine is needed because Python's C{timedelta()} function
 
1486
        does not allow for month or year increments.
 
1487
 
 
1488
        @type  source: struct_time
 
1489
        @param source: C{struct_time} value to increment
1240
1490
        @type  month:  integer
1241
1491
        @param month:  optional number of months to increment
1242
1492
        @type  year:   integer
1243
1493
        @param year:   optional number of years to increment
1244
1494
 
1245
1495
        @rtype:  datetime
1246
 
        @return: L{source} incremented by the number of months and/or years
 
1496
        @return: C{source} incremented by the number of months and/or years
1247
1497
        """
1248
1498
        yr  = source.year
1249
1499
        mth = source.month
 
1500
        dy  = source.day
1250
1501
 
1251
1502
        if year:
1252
1503
            try:
1279
1530
 
1280
1531
            yr += y
1281
1532
 
1282
 
        d = source.replace(year=yr, month=mth)
 
1533
            # if the day ends up past the last day of
 
1534
            # the new month, set it to the last day
 
1535
            if dy > self.ptc.daysInMonth(mth, yr):
 
1536
                dy = self.ptc.daysInMonth(mth, yr)
 
1537
 
 
1538
        d = source.replace(year=yr, month=mth, day=dy)
1283
1539
 
1284
1540
        return source + (d - source)
1285
1541