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