root/django/trunk/contrib/dateutil/parser.py

Revision 84, 31.7 kB (checked in by steadicat, 15 months ago)

Added loads of useful contrib.

  • Property svn:keywords set to Id
Line 
1# -*- coding:iso-8859-1 -*-
2"""
3Copyright (c) 2003-2005  Gustavo Niemeyer <gustavo@niemeyer.net>
4
5This module offers extensions to the standard python 2.3+
6datetime module.
7"""
8__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
9__license__ = "PSF License"
10
11import os.path
12import string
13import sys
14import time
15
16import datetime
17import relativedelta
18import tz
19
20__all__ = ["parse", "parserinfo"]
21
22# Some pointers:
23#
24# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
25# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
26# http://www.w3.org/TR/NOTE-datetime
27# http://ringmaster.arc.nasa.gov/tools/time_formats.html
28# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
29# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
30
31try:
32    from cStringIO import StringIO
33except ImportError:
34    from StringIO import StringIO
35
36class _timelex:
37    def __init__(self, instream):
38        if isinstance(instream, basestring):
39            instream = StringIO(instream)
40        self.instream = instream
41        self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
42                          'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
43                          '�����������'
44                          '���������������')
45        self.numchars = '0123456789'
46        self.whitespace = ' \t\r\n'
47        self.charstack = []
48        self.tokenstack = []
49        self.eof = False
50
51    def get_token(self):
52        if self.tokenstack:
53            return self.tokenstack.pop(0)
54        seenletters = False
55        token = None
56        state = None
57        wordchars = self.wordchars
58        numchars = self.numchars
59        whitespace = self.whitespace
60        while not self.eof:
61            if self.charstack:
62                nextchar = self.charstack.pop(0)
63            else:
64                nextchar = self.instream.read(1)
65                while nextchar == '\x00':
66                    nextchar = self.instream.read(1)
67            if not nextchar:
68                self.eof = True
69                break
70            elif not state:
71                token = nextchar
72                if nextchar in wordchars:
73                    state = 'a'
74                elif nextchar in numchars:
75                    state = '0'
76                elif nextchar in whitespace:
77                    token = ' '
78                    break # emit token
79                else:
80                    break # emit token
81            elif state == 'a':
82                seenletters = True
83                if nextchar in wordchars:
84                    token += nextchar
85                elif nextchar == '.':
86                    token += nextchar
87                    state = 'a.'
88                else:
89                    self.charstack.append(nextchar)
90                    break # emit token
91            elif state == '0':
92                if nextchar in numchars:
93                    token += nextchar
94                elif nextchar == '.':
95                    token += nextchar
96                    state = '0.'
97                else:
98                    self.charstack.append(nextchar)
99                    break # emit token
100            elif state == 'a.':
101                seenletters = True
102                if nextchar == '.' or nextchar in wordchars:
103                    token += nextchar
104                elif nextchar in numchars and token[-1] == '.':
105                    token += nextchar
106                    state = '0.'
107                else:
108                    self.charstack.append(nextchar)
109                    break # emit token
110            elif state == '0.':
111                if nextchar == '.' or nextchar in numchars:
112                    token += nextchar
113                elif nextchar in wordchars and token[-1] == '.':
114                    token += nextchar
115                    state = 'a.'
116                else:
117                    self.charstack.append(nextchar)
118                    break # emit token
119        if (state in ('a.', '0.') and
120            (seenletters or token.count('.') > 1 or token[-1] == '.')):
121            l = token.split('.')
122            token = l[0]
123            for tok in l[1:]:
124                self.tokenstack.append('.')
125                if tok:
126                    self.tokenstack.append(tok)
127        return token
128
129    def __iter__(self):
130        return self
131
132    def next(self):
133        token = self.get_token()
134        if token is None:
135            raise StopIteration
136        return token
137
138    def split(cls, s):
139        return list(cls(s))
140    split = classmethod(split)
141
142class _resultbase(object):
143
144    def __init__(self):
145        for attr in self.__slots__:
146            setattr(self, attr, None)
147
148    def _repr(self, classname):
149        l = []
150        for attr in self.__slots__:
151            value = getattr(self, attr)
152            if value is not None:
153                l.append("%s=%s" % (attr, `value`))
154        return "%s(%s)" % (classname, ", ".join(l))
155
156    def __repr__(self):
157        return self._repr(self.__class__.__name__)
158
159class parserinfo:
160
161    # m from a.m/p.m, t from ISO T separator
162    JUMP = [" ", ".", ",", ";", "-", "/", "'",
163            "at", "on", "and", "ad", "m", "t", "of",
164            "st", "nd", "rd", "th"] 
165
166    WEEKDAYS = [("Mon", "Monday"),
167                ("Tue", "Tuesday"),
168                ("Wed", "Wednesday"),
169                ("Thu", "Thursday"),
170                ("Fri", "Friday"),
171                ("Sat", "Saturday"),
172                ("Sun", "Sunday")]
173    MONTHS   = [("Jan", "January"),
174                ("Feb", "February"),
175                ("Mar", "March"),
176                ("Apr", "April"),
177                ("May", "May"),
178                ("Jun", "June"),
179                ("Jul", "July"),
180                ("Aug", "August"),
181                ("Sep", "September"),
182                ("Oct", "October"),
183                ("Nov", "November"),
184                ("Dec", "December")]
185    HMS = [("h", "hour", "hours"),
186           ("m", "minute", "minutes"),
187           ("s", "second", "seconds")]
188    AMPM = [("am", "a"),
189            ("pm", "p")]
190    UTCZONE = ["UTC", "GMT", "Z"]
191    PERTAIN = ["of"]
192    TZOFFSET = {}
193
194    def __init__(self, dayfirst=False, yearfirst=False):
195        self._jump = self._convert(self.JUMP)
196        self._weekdays = self._convert(self.WEEKDAYS)
197        self._months = self._convert(self.MONTHS)
198        self._hms = self._convert(self.HMS)
199        self._ampm = self._convert(self.AMPM)
200        self._utczone = self._convert(self.UTCZONE)
201        self._pertain = self._convert(self.PERTAIN)
202
203        self.dayfirst = dayfirst
204        self.yearfirst = yearfirst
205
206        self._year = time.localtime().tm_year
207        self._century = self._year/100*100
208
209    def _convert(self, lst):
210        dct = {}
211        for i in range(len(lst)):
212            v = lst[i]
213            if isinstance(v, tuple):
214                for v in v:
215                    dct[v.lower()] = i
216            else:
217                dct[v.lower()] = i
218        return dct
219
220    def jump(self, name):
221        return name.lower() in self._jump
222
223    def weekday(self, name):
224        if len(name) >= 3:
225            try:
226                return self._weekdays[name.lower()]
227            except KeyError:
228                pass
229        return None
230
231    def month(self, name):
232        if len(name) >= 3:
233            try:
234                return self._months[name.lower()]+1
235            except KeyError:
236                pass
237        return None
238
239    def hms(self, name):
240        try:
241            return self._hms[name.lower()]
242        except KeyError:
243            return None
244
245    def ampm(self, name):
246        try:
247            return self._ampm[name.lower()]
248        except KeyError:
249            return None
250
251    def pertain(self, name):
252        return name.lower() in self._pertain
253
254    def utczone(self, name):
255        return name.lower() in self._utczone
256
257    def tzoffset(self, name):
258        if name in self._utczone:
259            return 0
260        return self.TZOFFSET.get(name)
261
262    def convertyear(self, year):
263        if year < 100:
264            year += self._century
265            if abs(year-self._year) >= 50:
266                if year < self._year:
267                    year += 100
268                else:
269                    year -= 100
270        return year
271
272    def validate(self, res):
273        # move to info
274        if res.year is not None:
275            res.year = self.convertyear(res.year)
276        if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
277            res.tzname = "UTC"
278            res.tzoffset = 0
279        elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
280            res.tzoffset = 0
281        return True
282
283
284class parser:
285
286    def __init__(self, info=parserinfo):
287        if issubclass(info, parserinfo):
288            self.info = parserinfo()
289        elif isinstance(info, parserinfo):
290            self.info = info
291        else:
292            raise TypeError, "Unsupported parserinfo type"
293
294    def parse(self, timestr, default=None,
295                    ignoretz=False, tzinfos=None,
296                    **kwargs):
297        if not default:
298            default = datetime.datetime.now().replace(hour=0, minute=0,
299                                                      second=0, microsecond=0)
300        res = self._parse(timestr, **kwargs)
301        if res is None:
302            raise ValueError, "unknown string format"
303        repl = {}
304        for attr in ["year", "month", "day", "hour",
305                     "minute", "second", "microsecond"]:
306            value = getattr(res, attr)
307            if value is not None:
308                repl[attr] = value
309        ret = default.replace(**repl)
310        if res.weekday is not None and not res.day:
311            ret = ret+relativedelta.relativedelta(weekday=res.weekday)
312        if not ignoretz:
313            if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
314                if callable(tzinfos):
315                    tzdata = tzinfos(res.tzname, res.tzoffset)
316                else:
317                    tzdata = tzinfos.get(res.tzname)
318                if isinstance(tzdata, datetime.tzinfo):
319                    tzinfo = tzdata
320                elif isinstance(tzdata, basestring):
321                    tzinfo = tz.tzstr(tzdata)
322                elif isinstance(tzdata, int):
323                    tzinfo = tz.tzoffset(res.tzname, tzdata)
324                else:
325                    raise ValueError, "offset must be tzinfo subclass, " \
326                                      "tz string, or int offset"
327                ret = ret.replace(tzinfo=tzinfo)
328            elif res.tzname and res.tzname in time.tzname:
329                ret = ret.replace(tzinfo=tz.tzlocal())
330            elif res.tzoffset == 0:
331                ret = ret.replace(tzinfo=tz.tzutc())
332            elif res.tzoffset:
333                ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
334        return ret
335
336    class _result(_resultbase):
337        __slots__ = ["year", "month", "day", "weekday",
338                     "hour", "minute", "second", "microsecond",
339                     "tzname", "tzoffset"]
340
341    def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
342        info = self.info
343        if dayfirst is None:
344            dayfirst = info.dayfirst
345        if yearfirst is None:
346            yearfirst = info.yearfirst
347        res = self._result()
348        l = _timelex.split(timestr)
349        try:
350
351            # year/month/day list
352            ymd = []
353
354            # Index of the month string in ymd
355            mstridx = -1
356
357            len_l = len(l)
358            i = 0
359            while i < len_l:
360
361                # Check if it's a number
362                try:
363                    value = float(l[i])
364                except ValueError:
365                    value = None
366                if value is not None:
367                    # Token is a number
368                    len_li = len(l[i])
369                    i += 1
370                    if (len(ymd) == 3 and len_li in (2, 4)
371                        and (i >= len_l or (l[i] != ':' and
372                                            info.hms(l[i]) is None))):
373                        # 19990101T23[59]
374                        s = l[i-1]
375                        res.hour = int(s[:2])
376                        if len_li == 4:
377                            res.minute = int(s[2:])
378                    elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
379                        # YYMMDD or HHMMSS[.ss]
380                        s = l[i-1] 
381                        if not ymd and l[i-1].find('.') == -1:
382                            ymd.append(info.convertyear(int(s[:2])))
383                            ymd.append(int(s[2:4]))
384                            ymd.append(int(s[4:]))
385                        else:
386                            # 19990101T235959[.59]
387                            res.hour = int(s[:2])
388                            res.minute = int(s[2:4])
389                            value = float(s[4:])
390                            res.second = int(value)
391                            if value%1:
392                                res.microsecond = int(1000000*(value%1))
393                    elif len_li == 8:
394                        # YYYYMMDD
395                        s = l[i-1]
396                        ymd.append(int(s[:4]))
397                        ymd.append(int(s[4:6]))
398                        ymd.append(int(s[6:]))
399                    elif len_li in (12, 14):
400                        # YYYYMMDDhhmm[ss]
401                        s = l[i-1]
402                        ymd.append(int(s[:4]))
403                        ymd.append(int(s[4:6]))
404                        ymd.append(int(s[6:8]))
405                        res.hour = int(s[8:10])
406                        res.minute = int(s[10:12])
407                        if len_li == 14:
408                            res.second = int(s[12:])
409                    elif ((i < len_l and info.hms(l[i]) is not None) or
410                          (i+1 < len_l and l[i] == ' ' and
411                           info.hms(l[i+1]) is not None)):
412                        # HH[ ]h or MM[ ]m or SS[.ss][ ]s
413                        if l[i] == ' ':
414                            i += 1
415                        idx = info.hms(l[i])
416                        while True:
417                            if idx == 0:
418                                res.hour = int(value)
419                                if value%1:
420                                    res.minute = int(60*(value%1))
421                            elif idx == 1:
422                                res.minute = int(value)
423                                if value%1:
424                                    res.second = int(60*(value%1))
425                            elif idx == 2:
426                                res.second = int(value)
427                                if value%1:
428                                    res.microsecond = int(1000000*(value%1))
429                            i += 1
430                            if i >= len_l or idx == 2:
431                                break
432                            # 12h00
433                            try:
434                                value = float(l[i])
435                            except ValueError:
436                                break
437                            else:
438                                i += 1
439                                idx += 1
440                                if i < len_l:
441                                    newidx = info.hms(l[i])
442                                    if newidx is not None:
443                                        idx = newidx
444                    elif i+1 < len_l and l[i] == ':':
445                        # HH:MM[:SS[.ss]]
446                        res.hour = int(value)
447                        i += 1
448                        value = float(l[i])
449                        res.minute = int(value)
450                        if value%1:
451                            res.second = int(60*(value%1))
452                        i += 1
453                        if i < len_l and l[i] == ':':
454                            value = float(l[i+1])
455                            res.second = int(value)
456                            if value%1:
457                                res.microsecond = int(1000000*(value%1))
458                            i += 2
459                    elif i < len_l and l[i] in ('-', '/', '.'):
460                        sep = l[i]
461                        ymd.append(int(value))
462                        i += 1
463                        if i < len_l and not info.jump(l[i]):
464                            try:
465                                # 01-01[-01]
466                                ymd.append(int(l[i]))
467                            except ValueError:
468                                # 01-Jan[-01]
469                                value = info.month(l[i])
470                                if value is not None:
471                                    ymd.append(value)
472                                    assert mstridx == -1
473                                    mstridx = len(ymd)-1
474                                else:
475                                    return None
476                            i += 1
477                            if i < len_l and l[i] == sep:
478                                # We have three members
479                                i += 1
480                                value = info.month(l[i])
481                                if value is not None:
482                                    ymd.append(value)
483                                    mstridx = len(ymd)-1
484                                    assert mstridx == -1
485                                else:
486                                    ymd.append(int(l[i]))
487                                i += 1
488                    elif i >= len_l or info.jump(l[i]):
489                        if i+1 < len_l and info.ampm(l[i+1]) is not None:
490                            # 12 am
491                            res.hour = int(value)
492                            if res.hour < 12 and info.ampm(l[i+1]) == 1:
493                                res.hour += 12
494                            elif res.hour == 12 and info.ampm(l[i+1]) == 0:
495                                res.hour = 0
496                            i += 1
497                        else:
498                            # Year, month or day
499                            ymd.append(int(value))
500                        i += 1
501                    elif info.ampm(l[i]) is not None:
502                        # 12am
503                        res.hour = int(value)
504                        if res.hour < 12 and info.ampm(l[i]) == 1:
505                            res.hour += 12
506                        elif res.hour == 12 and info.ampm(l[i]) == 0:
507                            res.hour = 0
508                        i += 1
509                    elif not fuzzy:
510                        return None
511                    else:
512                        i += 1
513                    continue
514
515                # Check weekday
516                value = info.weekday(l[i])
517                if value is not None:
518                    res.weekday = value
519                    i += 1
520                    continue
521
522                # Check month name
523                value = info.month(l[i])
524                if value is not None:
525                    ymd.append(value)
526                    assert mstridx == -1
527                    mstridx = len(ymd)-1
528                    i += 1
529                    if i < len_l:
530                        if l[i] in ('-', '/'):
531                            # Jan-01[-99]
532                            sep = l[i]
533                            i += 1
534                            ymd.append(int(l[i]))
535                            i += 1
536                            if i < len_l and l[i] == sep:
537                                # Jan-01-99
538                                i += 1
539                                ymd.append(int(l[i]))
540                                i += 1
541                        elif (i+3 < len_l and l[i] == l[i+2] == ' '
542                              and info.pertain(l[i+1])):
543                            # Jan of 01
544                            # In this case, 01 is clearly year
545                            try:
546                                value = int(l[i+3])
547                            except ValueError:
548                                # Wrong guess
549                                pass
550                            else:
551                                # Convert it here to become unambiguous
552                                ymd.append(info.convertyear(value))
553                            i += 4
554                    continue
555
556                # Check am/pm
557                value = info.ampm(l[i])
558                if value is not None:
559                    if value == 1 and res.hour < 12:
560                        res.hour += 12
561                    elif value == 0 and res.hour == 12:
562                        res.hour = 0
563                    i += 1
564                    continue
565
566                # Check for a timezone name
567                if (res.hour is not None and len(l[i]) <= 5 and
568                    res.tzname is None and res.tzoffset is None and
569                    not [x for x in l[i] if x not in string.ascii_uppercase]):
570                    res.tzname = l[i]
571                    res.tzoffset = info.tzoffset(res.tzname)
572                    i += 1
573
574                    # Check for something like GMT+3, or BRST+3. Notice
575                    # that it doesn't mean "I am 3 hours after GMT", but
576                    # "my time +3 is GMT". If found, we reverse the
577                    # logic so that timezone parsing code will get it
578                    # right.
579                    if i < len_l and l[i] in ('+', '-'):
580                        l[i] = ('+', '-')[l[i] == '+']
581                        res.tzoffset = None
582                        if info.utczone(res.tzname):
583                            # With something like GMT+3, the timezone
584                            # is *not* GMT.
585                            res.tzname = None
586
587                    continue
588
589                # Check for a numbered timezone
590                if res.hour is not None and l[i] in ('+', '-'):
591                    signal = (-1,1)[l[i] == '+']
592                    i += 1
593                    len_li = len(l[i])
594                    if len_li == 4:
595                        # -0300
596                        res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
597                    elif i+1 < len_l and l[i+1] == ':':
598                        # -03:00
599                        res.tzoffset = int(l[i])*3600+int(l[i+2])*60
600                        i += 2
601                    elif len_li <= 2:
602                        # -[0]3
603                        res.tzoffset = int(l[i][:2])*3600
604                    else:
605                        return None
606                    i += 1
607                    res.tzoffset *= signal
608
609                    # Look for a timezone name between parenthesis
610                    if (i+3 < len_l and
611                        info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
612                        3 <= len(l[i+2]) <= 5 and
613                        not [x for x in l[i+2]
614                                if x not in string.ascii_uppercase]):
615                        # -0300 (BRST)
616                        res.tzname <