1b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# (c) 2005 Clark C. Evans and contributors
2b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# This module is part of the Python Paste Project and is released under
3b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# the MIT License: http://www.opensource.org/licenses/mit-license.php
4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# Some of this code was funded by: http://prometheusresearch.com
5b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
6b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikDate, Time, and Timespan Parsing Utilities
7b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
8b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikThis module contains parsing support to create "human friendly"
9b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik``datetime`` object parsing.  The explicit goal of these routines is
10b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikto provide a multi-format date/time support not unlike that found in
11b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikMicrosoft Excel.  In most approaches, the input is very "strict" to
12b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikprevent errors -- however, this approach is much more liberal since we
13b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikare assuming the user-interface is parroting back the normalized value
14b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikand thus the user has immediate feedback if the data is not typed in
15b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikcorrectly.
16b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
17b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  ``parse_date`` and ``normalize_date``
18b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
19b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     These functions take a value like '9 jan 2007' and returns either an
20b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     ``date`` object, or an ISO 8601 formatted date value such
21b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     as '2007-01-09'.  There is an option to provide an Oracle database
22b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     style output as well, ``09 JAN 2007``, but this is not the default.
23b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
24b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     This module always treats '/' delimiters as using US date order
25b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     (since the author's clients are US based), hence '1/9/2007' is
26b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     January 9th.  Since this module treats the '-' as following
27b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     European order this supports both modes of data-entry; together
28b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     with immediate parroting back the result to the screen, the author
29b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     has found this approach to work well in pratice.
30b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
31b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  ``parse_time`` and ``normalize_time``
32b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
33b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     These functions take a value like '1 pm' and returns either an
34b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     ``time`` object, or an ISO 8601 formatted 24h clock time
35b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     such as '13:00'.  There is an option to provide for US style time
36b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     values, '1:00 PM', however this is not the default.
37b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
38b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  ``parse_datetime`` and ``normalize_datetime``
39b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
40b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     These functions take a value like '9 jan 2007 at 1 pm' and returns
41b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     either an ``datetime`` object, or an ISO 8601 formatted
42b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     return (without the T) such as '2007-01-09 13:00'. There is an
43b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     option to provide for Oracle / US style, '09 JAN 2007 @ 1:00 PM',
44b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     however this is not the default.
45b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
46b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  ``parse_delta`` and ``normalize_delta``
47b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
48b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     These functions take a value like '1h 15m' and returns either an
49b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     ``timedelta`` object, or an 2-decimal fixed-point
50b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     numerical value in hours, such as '1.25'.  The rationale is to
51b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     support meeting or time-billing lengths, not to be an accurate
52b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     representation in mili-seconds.  As such not all valid
53b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik     ``timedelta`` values will have a normalized representation.
54b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
55b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
56b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom datetime import timedelta, time, date
57b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom time import localtime
58b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
59b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik__all__ = ['parse_timedelta', 'normalize_timedelta',
60b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik           'parse_time', 'normalize_time',
61b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik           'parse_date', 'normalize_date']
62b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
63b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef _number(val):
64b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    try:
65b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return int(val)
66b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    except:
67b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return None
68b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
69b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
70b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# timedelta
71b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
72b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef parse_timedelta(val):
73b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
74b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    returns a ``timedelta`` object, or None
75b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
76b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not val:
77b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return None
78b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.lower()
79b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if "." in val:
80b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = float(val)
81b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return timedelta(hours=int(val), minutes=60*(val % 1.0))
82b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fHour = ("h" in val or ":" in val)
83b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fMin  = ("m" in val or ":" in val)
84b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for noise in "minu:teshour()":
85b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val.replace(noise, ' ')
86b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.strip()
87b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.split()
88b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    hr = 0.0
89b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    mi = 0
90b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val.reverse()
91b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if fHour:
92b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = int(val.pop())
93b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if fMin:
94b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        mi = int(val.pop())
95b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if len(val) > 0 and not hr:
96b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = int(val.pop())
97b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return timedelta(hours=hr, minutes=mi)
98b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
99b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef normalize_timedelta(val):
100b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
101b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    produces a normalized string value of the timedelta
102b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
103b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    This module returns a normalized time span value consisting of the
104b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    number of hours in fractional form. For example '1h 15min' is
105b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    formatted as 01.25.
106b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
107b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if type(val) == str:
108b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = parse_timedelta(val)
109b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not val:
110b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ''
111b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    hr = val.seconds/3600
112b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    mn = (val.seconds % 3600)/60
113b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return "%d.%02d" % (hr, mn * 100/60)
114b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
115b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
116b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# time
117b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
118b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef parse_time(val):
119b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not val:
120b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return None
121b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    hr = mi = 0
122b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.lower()
123b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    amflag = (-1 != val.find('a'))  # set if AM is found
124b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    pmflag = (-1 != val.find('p'))  # set if PM is found
125b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for noise in ":amp.":
126b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val.replace(noise, ' ')
127b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.split()
128b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if len(val) > 1:
129b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = int(val[0])
130b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        mi = int(val[1])
131b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
132b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val[0]
133b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if len(val) < 1:
134b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            pass
135b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif 'now' == val:
136b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            tm = localtime()
137b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = tm[3]
138b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            mi = tm[4]
139b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif 'noon' == val:
140b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = 12
141b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif len(val) < 3:
142b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = int(val)
143b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if not amflag and not pmflag and hr < 7:
144b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                hr += 12
145b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif len(val) < 5:
146b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = int(val[:-2])
147b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            mi = int(val[-2:])
148b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
149b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = int(val[:1])
150b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if amflag and hr >= 12:
151b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = hr - 12
152b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if pmflag and hr < 12:
153b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = hr + 12
154b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return time(hr, mi)
155b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
156b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef normalize_time(value, ampm):
157b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not value:
158b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ''
159b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if type(value) == str:
160b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        value = parse_time(value)
161b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not ampm:
162b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return "%02d:%02d" % (value.hour, value.minute)
163b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    hr = value.hour
164b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    am = "AM"
165b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if hr < 1 or hr > 23:
166b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        hr = 12
167b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    elif hr >= 12:
168b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        am = "PM"
169b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if hr > 12:
170b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            hr = hr - 12
171b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return "%02d:%02d %s" % (hr, value.minute, am)
172b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
173b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
174b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# Date Processing
175b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#
176b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
177b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik_one_day = timedelta(days=1)
178b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
179b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik_str2num = {'jan':1, 'feb':2, 'mar':3, 'apr':4,  'may':5, 'jun':6,
180b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 }
181b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
182b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef _month(val):
183b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for (key, mon) in _str2num.items():
184b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if key in val:
185b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            return mon
186b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    raise TypeError("unknown month '%s'" % val)
187b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
188b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik_days_in_month = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30,
189b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                  7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31,
190b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                  }
191b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik_num2str = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
192b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec',
193b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            }
194b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik_wkdy = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
195b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
196b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef parse_date(val):
197b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not(val):
198b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return None
199b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = val.lower()
200b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    now = None
201b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
202b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # optimized check for YYYY-MM-DD
203b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    strict = val.split("-")
204b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if len(strict) == 3:
205b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        (y, m, d) = strict
206b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if "+" in d:
207b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            d = d.split("+")[0]
208b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if " " in d:
209b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            d = d.split(" ")[0]
210b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        try:
211b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            now = date(int(y), int(m), int(d))
212b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            val = "xxx" + val[10:]
213b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        except ValueError:
214b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            pass
215b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
216b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # allow for 'now', 'mon', 'tue', etc.
217b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not now:
218b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        chk = val[:3]
219b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if chk in ('now','tod'):
220b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            now = date.today()
221b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif chk in _wkdy:
222b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            now = date.today()
223b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            idx = list(_wkdy).index(chk) + 1
224b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            while now.isoweekday() != idx:
225b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                now += _one_day
226b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
227b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # allow dates to be modified via + or - /w number of days, so
228b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # that now+3 is three days from now
229b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if now:
230b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        tail = val[3:].strip()
231b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        tail = tail.replace("+"," +").replace("-"," -")
232b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        for item in tail.split():
233b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            try:
234b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                days = int(item)
235b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            except ValueError:
236b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                pass
237b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
238b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                now += timedelta(days=days)
239b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return now
240b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
241b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # ok, standard parsing
242b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    yr = mo = dy = None
243b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for noise in ('/', '-', ',', '*'):
244b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val.replace(noise, ' ')
245b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for noise in _wkdy:
246b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val.replace(noise, ' ')
247b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    out = []
248b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    last = False
249b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    ldig = False
250b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for ch in val:
251b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if ch.isdigit():
252b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if last and not ldig:
253b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik               out.append(' ')
254b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            last = ldig = True
255b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
256b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if ldig:
257b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                out.append(' ')
258b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                ldig = False
259b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            last = True
260b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        out.append(ch)
261b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    val = "".join(out).split()
262b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if 3 == len(val):
263b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        a = _number(val[0])
264b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        b = _number(val[1])
265b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        c = _number(val[2])
266b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if len(val[0]) == 4:
267b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            yr = a
268b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if b:  # 1999 6 23
269b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = b
270b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = c
271b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:  # 1999 Jun 23
272b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = _month(val[1])
273b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = c
274b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif a is not None and a > 0:
275b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            yr = c
276b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if len(val[2]) < 4:
277b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise TypeError("four digit year required")
278b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if b: # 6 23 1999
279b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = b
280b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = a
281b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else: # 23 Jun 1999
282b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = a
283b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = _month(val[1])
284b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else: # Jun 23, 2000
285b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            dy = b
286b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            yr = c
287b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if len(val[2]) < 4:
288b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise TypeError("four digit year required")
289b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            mo = _month(val[0])
290b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    elif 2 == len(val):
291b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        a = _number(val[0])
292b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        b = _number(val[1])
293b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if a is not None and a > 999:
294b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            yr = a
295b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            dy = 1
296b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if b is not None and b > 0: # 1999 6
297b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = b
298b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else: # 1999 Jun
299b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = _month(val[1])
300b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif a is not None and a > 0:
301b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if b is not None and b > 999: # 6 1999
302b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = a
303b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                yr = b
304b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = 1
305b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            elif b is not None and b > 0: # 6 23
306b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = a
307b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = b
308b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else: # 23 Jun
309b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = a
310b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = _month(val[1])
311b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
312b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if b > 999: # Jun 2001
313b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                yr = b
314b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = 1
315b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:  # Jun 23
316b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = b
317b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            mo = _month(val[0])
318b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    elif 1 == len(val):
319b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = val[0]
320b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if not val.isdigit():
321b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            mo = _month(val)
322b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if mo is not None:
323b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = 1
324b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
325b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            v = _number(val)
326b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            val = str(v)
327b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if 8 == len(val): # 20010623
328b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                yr = _number(val[:4])
329b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                mo = _number(val[4:6])
330b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = _number(val[6:])
331b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            elif len(val) in (3,4):
332b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                if v is not None and v > 1300: # 2004
333b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    yr = v
334b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    mo = 1
335b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    dy = 1
336b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                else:        # 1202
337b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    mo = _number(val[:-2])
338b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    dy = _number(val[-2:])
339b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            elif v < 32:
340b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                dy = v
341b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
342b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                raise TypeError("four digit year required")
343b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    tm = localtime()
344b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if mo is None:
345b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        mo = tm[1]
346b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if dy is None:
347b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        dy = tm[2]
348b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if yr is None:
349b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        yr = tm[0]
350b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return date(yr, mo, dy)
351b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
352b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef normalize_date(val, iso8601=True):
353b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not val:
354b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ''
355b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if type(val) == str:
356b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        val = parse_date(val)
357b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if iso8601:
358b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return "%4d-%02d-%02d" % (val.year, val.month, val.day)
359b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return "%02d %s %4d" % (val.day, _num2str[val.month], val.year)
360