1"""Copyright 2008 Orbitz WorldWide
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7   http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License."""
14import pytz
15
16from datetime import datetime, timedelta
17from time import daylight
18
19months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
20          'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
21weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
22
23
24def parseATTime(s, tzinfo=None):
25    if tzinfo is None:
26        from ..app import app
27        tzinfo = pytz.timezone(app.config['TIME_ZONE'])
28    s = s.strip().lower().replace('_', '').replace(',', '').replace(' ', '')
29    if s.isdigit():
30        if (
31            len(s) == 8 and
32            int(s[:4]) > 1900 and
33            int(s[4:6]) < 13 and
34            int(s[6:]) < 32
35        ):
36            pass  # Fall back because its not a timestamp, its YYYYMMDD form
37        else:
38            return datetime.fromtimestamp(int(s), tzinfo)
39    elif ':' in s and len(s) == 13:
40        return tzinfo.localize(datetime.strptime(s, '%H:%M%Y%m%d'), daylight)
41    if '+' in s:
42        ref, offset = s.split('+', 1)
43        offset = '+' + offset
44    elif '-' in s:
45        ref, offset = s.split('-', 1)
46        offset = '-' + offset
47    else:
48        ref, offset = s, ''
49    return (parseTimeReference(ref) +
50            parseTimeOffset(offset)).astimezone(tzinfo)
51
52
53def parseTimeReference(ref):
54    if not ref or ref == 'now':
55        return datetime.utcnow().replace(tzinfo=pytz.utc)
56
57    # Time-of-day reference
58    i = ref.find(':')
59    hour, min = 0, 0
60    if i != -1:
61        hour = int(ref[:i])
62        min = int(ref[i+1:i+3])
63        ref = ref[i+3:]
64        if ref[:2] == 'am':
65            ref = ref[2:]
66        elif ref[:2] == 'pm':
67            hour = (hour + 12) % 24
68            ref = ref[2:]
69    if ref.startswith('noon'):
70        hour, min = 12, 0
71        ref = ref[4:]
72    elif ref.startswith('midnight'):
73        hour, min = 0, 0
74        ref = ref[8:]
75    elif ref.startswith('teatime'):
76        hour, min = 16, 0
77        ref = ref[7:]
78
79    refDate = datetime.utcnow().replace(hour=hour, minute=min, second=0,
80                                        tzinfo=pytz.utc)
81
82    # Day reference
83    if ref in ('yesterday', 'today', 'tomorrow'):  # yesterday, today, tomorrow
84        if ref == 'yesterday':
85            refDate = refDate - timedelta(days=1)
86        if ref == 'tomorrow':
87            refDate = refDate + timedelta(days=1)
88    elif ref.count('/') == 2:  # MM/DD/YY[YY]
89        m, d, y = map(int, ref.split('/'))
90        if y < 1900:
91            y += 1900
92        if y < 1970:
93            y += 100
94        refDate = replace_date(refDate, y, m, d)
95
96    elif len(ref) == 8 and ref.isdigit():  # YYYYMMDD
97        refDate = replace_date(refDate, int(ref[:4]), int(ref[4:6]),
98                               int(ref[6:8]))
99
100    elif ref[:3] in months:  # MonthName DayOfMonth
101        month = months.index(ref[:3]) + 1
102        if ref[-2:].isdigit():
103            day = int(ref[-2])
104        elif ref[-1:].isdigit():
105            day = int(ref[-1:])
106        else:
107            raise Exception("Day of month required after month name")
108        refDate = replace_date(refDate, None, month, day)
109    elif ref[:3] in weekdays:  # DayOfWeek (Monday, etc)
110        todayDayName = refDate.strftime("%a").lower()[:3]
111        today = weekdays.index(todayDayName)
112        twoWeeks = weekdays * 2
113        dayOffset = today - twoWeeks.index(ref[:3])
114        if dayOffset < 0:
115            dayOffset += 7
116        refDate -= timedelta(days=dayOffset)
117    elif ref:
118        raise Exception("Unknown day reference")
119    return refDate
120
121
122def replace_date(date, year, month, day):
123    if year is not None:
124        try:
125            date = date.replace(year=year)
126        except ValueError:  # Feb 29.
127            date = date.replace(year=year, day=28)
128    try:
129        date = date.replace(month=month)
130        date = date.replace(day=day)
131    except ValueError:  # day out of range for month, or vice versa
132        date = date.replace(day=day)
133        date = date.replace(month=month)
134    return date
135
136
137def parseTimeOffset(offset):
138    if not offset:
139        return timedelta()
140
141    t = timedelta()
142
143    if offset[0].isdigit():
144        sign = 1
145    else:
146        sign = {'+': 1, '-': -1}[offset[0]]
147        offset = offset[1:]
148
149    while offset:
150        i = 1
151        while offset[:i].isdigit() and i <= len(offset):
152            i += 1
153        num = int(offset[:i-1])
154        offset = offset[i-1:]
155        i = 1
156        while offset[:i].isalpha() and i <= len(offset):
157            i += 1
158        unit = offset[:i-1]
159        offset = offset[i-1:]
160        unitString = getUnitString(unit)
161        if unitString == 'months':
162            unitString = 'days'
163            num = num * 30
164        if unitString == 'years':
165            unitString = 'days'
166            num = num * 365
167        t += timedelta(**{unitString: sign * num})
168
169    return t
170
171
172def getUnitString(s):
173    if s.startswith('s'):
174        return 'seconds'
175    if s.startswith('min'):
176        return 'minutes'
177    if s.startswith('h'):
178        return 'hours'
179    if s.startswith('d'):
180        return 'days'
181    if s.startswith('w'):
182        return 'weeks'
183    if s.startswith('mon'):
184        return 'months'
185    if s.startswith('y'):
186        return 'years'
187    raise Exception("Invalid offset unit '%s'" % s)
188