1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.http; 18 19 import android.text.format.Time; 20 21 import java.util.Calendar; 22 import java.util.regex.Matcher; 23 import java.util.regex.Pattern; 24 25 /** 26 * Helper for parsing an HTTP date. 27 */ 28 public final class HttpDateTime { 29 30 /* 31 * Regular expression for parsing HTTP-date. 32 * 33 * Wdy, DD Mon YYYY HH:MM:SS GMT 34 * RFC 822, updated by RFC 1123 35 * 36 * Weekday, DD-Mon-YY HH:MM:SS GMT 37 * RFC 850, obsoleted by RFC 1036 38 * 39 * Wdy Mon DD HH:MM:SS YYYY 40 * ANSI C's asctime() format 41 * 42 * with following variations 43 * 44 * Wdy, DD-Mon-YYYY HH:MM:SS GMT 45 * Wdy, (SP)D Mon YYYY HH:MM:SS GMT 46 * Wdy,DD Mon YYYY HH:MM:SS GMT 47 * Wdy, DD-Mon-YY HH:MM:SS GMT 48 * Wdy, DD Mon YYYY HH:MM:SS -HHMM 49 * Wdy, DD Mon YYYY HH:MM:SS 50 * Wdy Mon (SP)D HH:MM:SS YYYY 51 * Wdy Mon DD HH:MM:SS YYYY GMT 52 * 53 * HH can be H if the first digit is zero. 54 * 55 * Mon can be the full name of the month. 56 */ 57 private static final String HTTP_DATE_RFC_REGEXP = 58 "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" 59 + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; 60 61 private static final String HTTP_DATE_ANSIC_REGEXP = 62 "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" 63 + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; 64 65 /** 66 * The compiled version of the HTTP-date regular expressions. 67 */ 68 private static final Pattern HTTP_DATE_RFC_PATTERN = 69 Pattern.compile(HTTP_DATE_RFC_REGEXP); 70 private static final Pattern HTTP_DATE_ANSIC_PATTERN = 71 Pattern.compile(HTTP_DATE_ANSIC_REGEXP); 72 73 private static class TimeOfDay { 74 TimeOfDay(int h, int m, int s) { 75 this.hour = h; 76 this.minute = m; 77 this.second = s; 78 } 79 80 int hour; 81 int minute; 82 int second; 83 } 84 85 public static long parse(String timeString) 86 throws IllegalArgumentException { 87 88 int date = 1; 89 int month = Calendar.JANUARY; 90 int year = 1970; 91 TimeOfDay timeOfDay; 92 93 Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); 94 if (rfcMatcher.find()) { 95 date = getDate(rfcMatcher.group(1)); 96 month = getMonth(rfcMatcher.group(2)); 97 year = getYear(rfcMatcher.group(3)); 98 timeOfDay = getTime(rfcMatcher.group(4)); 99 } else { 100 Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); 101 if (ansicMatcher.find()) { 102 month = getMonth(ansicMatcher.group(1)); 103 date = getDate(ansicMatcher.group(2)); 104 timeOfDay = getTime(ansicMatcher.group(3)); 105 year = getYear(ansicMatcher.group(4)); 106 } else { 107 throw new IllegalArgumentException(); 108 } 109 } 110 111 // FIXME: Y2038 BUG! 112 if (year >= 2038) { 113 year = 2038; 114 month = Calendar.JANUARY; 115 date = 1; 116 } 117 118 Time time = new Time(Time.TIMEZONE_UTC); 119 time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, 120 month, year); 121 return time.toMillis(false /* use isDst */); 122 } 123 124 private static int getDate(String dateString) { 125 if (dateString.length() == 2) { 126 return (dateString.charAt(0) - '0') * 10 127 + (dateString.charAt(1) - '0'); 128 } else { 129 return (dateString.charAt(0) - '0'); 130 } 131 } 132 133 /* 134 * jan = 9 + 0 + 13 = 22 135 * feb = 5 + 4 + 1 = 10 136 * mar = 12 + 0 + 17 = 29 137 * apr = 0 + 15 + 17 = 32 138 * may = 12 + 0 + 24 = 36 139 * jun = 9 + 20 + 13 = 42 140 * jul = 9 + 20 + 11 = 40 141 * aug = 0 + 20 + 6 = 26 142 * sep = 18 + 4 + 15 = 37 143 * oct = 14 + 2 + 19 = 35 144 * nov = 13 + 14 + 21 = 48 145 * dec = 3 + 4 + 2 = 9 146 */ 147 private static int getMonth(String monthString) { 148 int hash = Character.toLowerCase(monthString.charAt(0)) + 149 Character.toLowerCase(monthString.charAt(1)) + 150 Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; 151 switch (hash) { 152 case 22: 153 return Calendar.JANUARY; 154 case 10: 155 return Calendar.FEBRUARY; 156 case 29: 157 return Calendar.MARCH; 158 case 32: 159 return Calendar.APRIL; 160 case 36: 161 return Calendar.MAY; 162 case 42: 163 return Calendar.JUNE; 164 case 40: 165 return Calendar.JULY; 166 case 26: 167 return Calendar.AUGUST; 168 case 37: 169 return Calendar.SEPTEMBER; 170 case 35: 171 return Calendar.OCTOBER; 172 case 48: 173 return Calendar.NOVEMBER; 174 case 9: 175 return Calendar.DECEMBER; 176 default: 177 throw new IllegalArgumentException(); 178 } 179 } 180 181 private static int getYear(String yearString) { 182 if (yearString.length() == 2) { 183 int year = (yearString.charAt(0) - '0') * 10 184 + (yearString.charAt(1) - '0'); 185 if (year >= 70) { 186 return year + 1900; 187 } else { 188 return year + 2000; 189 } 190 } else if (yearString.length() == 3) { 191 // According to RFC 2822, three digit years should be added to 1900. 192 int year = (yearString.charAt(0) - '0') * 100 193 + (yearString.charAt(1) - '0') * 10 194 + (yearString.charAt(2) - '0'); 195 return year + 1900; 196 } else if (yearString.length() == 4) { 197 return (yearString.charAt(0) - '0') * 1000 198 + (yearString.charAt(1) - '0') * 100 199 + (yearString.charAt(2) - '0') * 10 200 + (yearString.charAt(3) - '0'); 201 } else { 202 return 1970; 203 } 204 } 205 206 private static TimeOfDay getTime(String timeString) { 207 // HH might be H 208 int i = 0; 209 int hour = timeString.charAt(i++) - '0'; 210 if (timeString.charAt(i) != ':') 211 hour = hour * 10 + (timeString.charAt(i++) - '0'); 212 // Skip ':' 213 i++; 214 215 int minute = (timeString.charAt(i++) - '0') * 10 216 + (timeString.charAt(i++) - '0'); 217 // Skip ':' 218 i++; 219 220 int second = (timeString.charAt(i++) - '0') * 10 221 + (timeString.charAt(i++) - '0'); 222 223 return new TimeOfDay(hour, minute, second); 224 } 225 } 226