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.""" 14 15import pytz 16from datetime import datetime, timedelta, datetime as datetimetype 17from django.conf import settings 18 19months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'] 20weekdays = ['sun','mon','tue','wed','thu','fri','sat'] 21SECONDS_STRING = 'seconds' 22MINUTES_STRING = 'minutes' 23HOURS_STRING = 'hours' 24DAYS_STRING = 'days' 25WEEKS_STRING = 'weeks' 26MONTHS_STRING = 'months' 27YEARS_STRING = 'years' 28 29 30def parseATTime(s, tzinfo=None, now=None): 31 if tzinfo is None: 32 tzinfo = pytz.timezone(settings.TIME_ZONE) 33 if isinstance(s, datetimetype): 34 if s.tzinfo: 35 return s.astimezone(tzinfo) 36 return tzinfo.localize(s) 37 38 s = s.strip().lower().replace('_','').replace(',','').replace(' ','') 39 if s.isdigit(): 40 if len(s) == 8 and int(s[:4]) > 1900 and int(s[4:6]) < 13 and int(s[6:]) < 32: 41 pass # Fall back because its not a timestamp, its YYYYMMDD form 42 else: 43 return datetime.fromtimestamp(int(s),tzinfo) 44 if '+' in s: 45 ref,offset = s.split('+',1) 46 offset = '+' + offset 47 elif '-' in s: 48 ref,offset = s.split('-',1) 49 offset = '-' + offset 50 else: 51 ref,offset = s,'' 52 53 return tzinfo.normalize(parseTimeReference(ref, tzinfo, now) + parseTimeOffset(offset)) 54 55 56def parseTimeReference(ref, tzinfo=None, now=None): 57 if tzinfo is None: 58 tzinfo = pytz.timezone(settings.TIME_ZONE) 59 if isinstance(ref, datetimetype): 60 if ref.tzinfo: 61 return ref.astimezone(tzinfo) 62 return tzinfo.localize(ref) 63 64 if now is None: 65 now = datetime.now(tzinfo) 66 else: 67 now = parseATTime(now, tzinfo) 68 69 if not ref or ref == 'now': 70 return now 71 72 rawRef = ref 73 74 # Time-of-day reference 75 i = ref.find(':') 76 hour,minute = 0,0 77 if 0 < i < 3: 78 hour = int( ref[:i] ) 79 minute = int( ref[i+1:i+3] ) 80 ref = ref[i+3:] 81 if ref[:2] == 'am': 82 ref = ref[2:] 83 elif ref[:2] == 'pm': 84 hour = (hour + 12) % 24 85 ref = ref[2:] 86 87 # Xam or XXam 88 i = ref.find('am') 89 if 0 < i < 3: 90 hour = int( ref[:i] ) 91 ref = ref[i+2:] 92 93 # Xpm or XXpm 94 i = ref.find('pm') 95 if 0 < i < 3: 96 hour = (int( ref[:i] ) + 12) % 24 97 ref = ref[i+2:] 98 99 if ref.startswith('noon'): 100 hour,minute = 12,0 101 ref = ref[4:] 102 elif ref.startswith('midnight'): 103 hour,minute = 0,0 104 ref = ref[8:] 105 elif ref.startswith('teatime'): 106 hour,minute = 16,0 107 ref = ref[7:] 108 109 refDate = now.replace(hour=hour,minute=minute,second=0,microsecond=0,tzinfo=None) 110 111 # Day reference 112 if ref in ('yesterday','today','tomorrow'): # yesterday, today, tomorrow 113 if ref == 'yesterday': 114 refDate -= timedelta(days=1) 115 elif ref == 'tomorrow': 116 refDate += timedelta(days=1) 117 118 elif ref.count('/') == 2: # MM/DD/YY[YY] 119 m,d,y = map(int,ref.split('/')) 120 if y < 1900: 121 y += 1900 122 if y < 1970: 123 y += 100 124 refDate = datetime(year=y,month=m,day=d,hour=hour,minute=minute) 125 126 elif len(ref) == 8 and ref.isdigit(): # YYYYMMDD 127 refDate = datetime(year=int(ref[:4]), month=int(ref[4:6]), day=int(ref[6:8]), hour=hour, minute=minute) 128 129 elif ref[:3] in months: # MonthName DayOfMonth 130 d = None 131 if ref[-2:].isdigit(): 132 d = int(ref[-2:]) 133 elif ref[-1:].isdigit(): 134 d = int(ref[-1:]) 135 else: 136 raise Exception("Day of month required after month name") 137 refDate = datetime(year=refDate.year, month=months.index(ref[:3]) + 1, day=d, hour=hour, minute=minute) 138 139 elif ref[:3] in weekdays: # DayOfWeek (Monday, etc) 140 todayDayName = refDate.strftime("%a").lower()[:3] 141 today = weekdays.index( todayDayName ) 142 twoWeeks = weekdays * 2 143 dayOffset = today - twoWeeks.index(ref[:3]) 144 if dayOffset < 0: 145 dayOffset += 7 146 refDate -= timedelta(days=dayOffset) 147 148 elif ref: 149 raise ValueError("Unknown day reference: %s" % rawRef) 150 151 return tzinfo.localize(refDate) 152 153 154def parseTimeOffset(offset): 155 if not offset: 156 return timedelta() 157 158 t = timedelta() 159 160 if offset[0].isdigit(): 161 sign = 1 162 else: 163 try: 164 sign = { '+' : 1, '-' : -1 }[offset[0]] 165 except KeyError: 166 raise KeyError('Invalid offset: %s' % offset) 167 offset = offset[1:] 168 169 while offset: 170 i = 1 171 while offset[:i].isdigit() and i <= len(offset): 172 i += 1 173 num = int(offset[:i-1]) 174 offset = offset[i-1:] 175 i = 1 176 while offset[:i].isalpha() and i <= len(offset): 177 i += 1 178 unit = offset[:i-1] 179 offset = offset[i-1:] 180 unitString = getUnitString(unit) 181 if unitString == MONTHS_STRING: 182 unitString = DAYS_STRING 183 num = num * 30 184 if unitString == YEARS_STRING: 185 unitString = DAYS_STRING 186 num = num * 365 187 t += timedelta(**{ unitString : sign * num}) 188 189 return t 190 191 192def getUnitString(s): 193 if s.startswith('s'): return SECONDS_STRING 194 if s.startswith('min'): return MINUTES_STRING 195 if s.startswith('h'): return HOURS_STRING 196 if s.startswith('d'): return DAYS_STRING 197 if s.startswith('w'): return WEEKS_STRING 198 if s.startswith('mon'): return MONTHS_STRING 199 if s.startswith('y'): return YEARS_STRING 200 raise ValueError("Invalid offset unit '%s'" % s) 201