|
1 /* |
|
2 * ==================================================================== |
|
3 * Licensed to the Apache Software Foundation (ASF) under one |
|
4 * or more contributor license agreements. See the NOTICE file |
|
5 * distributed with this work for additional information |
|
6 * regarding copyright ownership. The ASF licenses this file |
|
7 * to you under the Apache License, Version 2.0 (the |
|
8 * "License"); you may not use this file except in compliance |
|
9 * with the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, |
|
14 * software distributed under the License is distributed on an |
|
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
16 * KIND, either express or implied. See the License for the |
|
17 * specific language governing permissions and limitations |
|
18 * under the License. |
|
19 * ==================================================================== |
|
20 * |
|
21 * This software consists of voluntary contributions made by many |
|
22 * individuals on behalf of the Apache Software Foundation. For more |
|
23 * information on the Apache Software Foundation, please see |
|
24 * <http://www.apache.org/>. |
|
25 * |
|
26 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.impl.cookie; |
|
29 |
|
30 import java.lang.ref.SoftReference; |
|
31 import java.text.ParseException; |
|
32 import java.text.SimpleDateFormat; |
|
33 import java.util.Calendar; |
|
34 import java.util.Date; |
|
35 import java.util.HashMap; |
|
36 import java.util.Locale; |
|
37 import java.util.Map; |
|
38 import java.util.TimeZone; |
|
39 |
|
40 import ch.boye.httpclientandroidlib.annotation.Immutable; |
|
41 |
|
42 /** |
|
43 * A utility class for parsing and formatting HTTP dates as used in cookies and |
|
44 * other headers. This class handles dates as defined by RFC 2616 section |
|
45 * 3.3.1 as well as some other common non-standard formats. |
|
46 * |
|
47 * |
|
48 * @since 4.0 |
|
49 */ |
|
50 @Immutable |
|
51 public final class DateUtils { |
|
52 |
|
53 /** |
|
54 * Date format pattern used to parse HTTP date headers in RFC 1123 format. |
|
55 */ |
|
56 public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; |
|
57 |
|
58 /** |
|
59 * Date format pattern used to parse HTTP date headers in RFC 1036 format. |
|
60 */ |
|
61 public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz"; |
|
62 |
|
63 /** |
|
64 * Date format pattern used to parse HTTP date headers in ANSI C |
|
65 * <code>asctime()</code> format. |
|
66 */ |
|
67 public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; |
|
68 |
|
69 private static final String[] DEFAULT_PATTERNS = new String[] { |
|
70 PATTERN_RFC1036, |
|
71 PATTERN_RFC1123, |
|
72 PATTERN_ASCTIME |
|
73 }; |
|
74 |
|
75 private static final Date DEFAULT_TWO_DIGIT_YEAR_START; |
|
76 |
|
77 public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); |
|
78 |
|
79 static { |
|
80 Calendar calendar = Calendar.getInstance(); |
|
81 calendar.setTimeZone(GMT); |
|
82 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); |
|
83 calendar.set(Calendar.MILLISECOND, 0); |
|
84 DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); |
|
85 } |
|
86 |
|
87 /** |
|
88 * Parses a date value. The formats used for parsing the date value are retrieved from |
|
89 * the default http params. |
|
90 * |
|
91 * @param dateValue the date value to parse |
|
92 * |
|
93 * @return the parsed date |
|
94 * |
|
95 * @throws DateParseException if the value could not be parsed using any of the |
|
96 * supported date formats |
|
97 */ |
|
98 public static Date parseDate(String dateValue) throws DateParseException { |
|
99 return parseDate(dateValue, null, null); |
|
100 } |
|
101 |
|
102 /** |
|
103 * Parses the date value using the given date formats. |
|
104 * |
|
105 * @param dateValue the date value to parse |
|
106 * @param dateFormats the date formats to use |
|
107 * |
|
108 * @return the parsed date |
|
109 * |
|
110 * @throws DateParseException if none of the dataFormats could parse the dateValue |
|
111 */ |
|
112 public static Date parseDate(final String dateValue, String[] dateFormats) |
|
113 throws DateParseException { |
|
114 return parseDate(dateValue, dateFormats, null); |
|
115 } |
|
116 |
|
117 /** |
|
118 * Parses the date value using the given date formats. |
|
119 * |
|
120 * @param dateValue the date value to parse |
|
121 * @param dateFormats the date formats to use |
|
122 * @param startDate During parsing, two digit years will be placed in the range |
|
123 * <code>startDate</code> to <code>startDate + 100 years</code>. This value may |
|
124 * be <code>null</code>. When <code>null</code> is given as a parameter, year |
|
125 * <code>2000</code> will be used. |
|
126 * |
|
127 * @return the parsed date |
|
128 * |
|
129 * @throws DateParseException if none of the dataFormats could parse the dateValue |
|
130 */ |
|
131 public static Date parseDate( |
|
132 String dateValue, |
|
133 String[] dateFormats, |
|
134 Date startDate |
|
135 ) throws DateParseException { |
|
136 |
|
137 if (dateValue == null) { |
|
138 throw new IllegalArgumentException("dateValue is null"); |
|
139 } |
|
140 if (dateFormats == null) { |
|
141 dateFormats = DEFAULT_PATTERNS; |
|
142 } |
|
143 if (startDate == null) { |
|
144 startDate = DEFAULT_TWO_DIGIT_YEAR_START; |
|
145 } |
|
146 // trim single quotes around date if present |
|
147 // see issue #5279 |
|
148 if (dateValue.length() > 1 |
|
149 && dateValue.startsWith("'") |
|
150 && dateValue.endsWith("'") |
|
151 ) { |
|
152 dateValue = dateValue.substring (1, dateValue.length() - 1); |
|
153 } |
|
154 |
|
155 for (String dateFormat : dateFormats) { |
|
156 SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); |
|
157 dateParser.set2DigitYearStart(startDate); |
|
158 |
|
159 try { |
|
160 return dateParser.parse(dateValue); |
|
161 } catch (ParseException pe) { |
|
162 // ignore this exception, we will try the next format |
|
163 } |
|
164 } |
|
165 |
|
166 // we were unable to parse the date |
|
167 throw new DateParseException("Unable to parse the date " + dateValue); |
|
168 } |
|
169 |
|
170 /** |
|
171 * Formats the given date according to the RFC 1123 pattern. |
|
172 * |
|
173 * @param date The date to format. |
|
174 * @return An RFC 1123 formatted date string. |
|
175 * |
|
176 * @see #PATTERN_RFC1123 |
|
177 */ |
|
178 public static String formatDate(Date date) { |
|
179 return formatDate(date, PATTERN_RFC1123); |
|
180 } |
|
181 |
|
182 /** |
|
183 * Formats the given date according to the specified pattern. The pattern |
|
184 * must conform to that used by the {@link SimpleDateFormat simple date |
|
185 * format} class. |
|
186 * |
|
187 * @param date The date to format. |
|
188 * @param pattern The pattern to use for formatting the date. |
|
189 * @return A formatted date string. |
|
190 * |
|
191 * @throws IllegalArgumentException If the given date pattern is invalid. |
|
192 * |
|
193 * @see SimpleDateFormat |
|
194 */ |
|
195 public static String formatDate(Date date, String pattern) { |
|
196 if (date == null) throw new IllegalArgumentException("date is null"); |
|
197 if (pattern == null) throw new IllegalArgumentException("pattern is null"); |
|
198 |
|
199 SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern); |
|
200 return formatter.format(date); |
|
201 } |
|
202 |
|
203 /** This class should not be instantiated. */ |
|
204 private DateUtils() { |
|
205 } |
|
206 |
|
207 /** |
|
208 * A factory for {@link SimpleDateFormat}s. The instances are stored in a |
|
209 * threadlocal way because SimpleDateFormat is not threadsafe as noted in |
|
210 * {@link SimpleDateFormat its javadoc}. |
|
211 * |
|
212 */ |
|
213 final static class DateFormatHolder { |
|
214 |
|
215 private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> |
|
216 THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() { |
|
217 |
|
218 @Override |
|
219 protected SoftReference<Map<String, SimpleDateFormat>> initialValue() { |
|
220 return new SoftReference<Map<String, SimpleDateFormat>>( |
|
221 new HashMap<String, SimpleDateFormat>()); |
|
222 } |
|
223 |
|
224 }; |
|
225 |
|
226 /** |
|
227 * creates a {@link SimpleDateFormat} for the requested format string. |
|
228 * |
|
229 * @param pattern |
|
230 * a non-<code>null</code> format String according to |
|
231 * {@link SimpleDateFormat}. The format is not checked against |
|
232 * <code>null</code> since all paths go through |
|
233 * {@link DateUtils}. |
|
234 * @return the requested format. This simple dateformat should not be used |
|
235 * to {@link SimpleDateFormat#applyPattern(String) apply} to a |
|
236 * different pattern. |
|
237 */ |
|
238 public static SimpleDateFormat formatFor(String pattern) { |
|
239 SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); |
|
240 Map<String, SimpleDateFormat> formats = ref.get(); |
|
241 if (formats == null) { |
|
242 formats = new HashMap<String, SimpleDateFormat>(); |
|
243 THREADLOCAL_FORMATS.set( |
|
244 new SoftReference<Map<String, SimpleDateFormat>>(formats)); |
|
245 } |
|
246 |
|
247 SimpleDateFormat format = formats.get(pattern); |
|
248 if (format == null) { |
|
249 format = new SimpleDateFormat(pattern, Locale.US); |
|
250 format.setTimeZone(TimeZone.getTimeZone("GMT")); |
|
251 formats.put(pattern, format); |
|
252 } |
|
253 |
|
254 return format; |
|
255 } |
|
256 |
|
257 } |
|
258 |
|
259 } |