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 org.apache.http.impl.cookie;
29
30 import java.lang.ref.SoftReference;
31 import java.text.DateFormat;
32 import java.text.ParsePosition;
33 import java.text.SimpleDateFormat;
34 import java.util.Calendar;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.TimeZone;
40
41 import org.apache.http.annotation.Immutable;
42
43 /**
44 * A utility class for parsing and formatting HTTP dates as used in cookies and
45 * other headers. This class handles dates as defined by RFC 2616 section
46 * 3.3.1 as well as some other common non-standard formats.
47 *
48 *
49 * @since 4.0
50 */
51 @Immutable
52 public final class DateUtils {
53
54 /**
55 * Date format pattern used to parse HTTP date headers in RFC 1123 format.
56 */
57 public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
58
59 /**
60 * Date format pattern used to parse HTTP date headers in RFC 1036 format.
61 */
62 public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
63
64 /**
65 * Date format pattern used to parse HTTP date headers in ANSI C
66 * <code>asctime()</code> format.
67 */
68 public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
69
70 private static final String[] DEFAULT_PATTERNS = new String[] {
71 PATTERN_RFC1123,
72 PATTERN_RFC1036,
73 PATTERN_ASCTIME
74 };
75
76 private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
77
78 public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
79
80 static {
81 Calendar calendar = Calendar.getInstance();
82 calendar.setTimeZone(GMT);
83 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
84 calendar.set(Calendar.MILLISECOND, 0);
85 DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
86 }
87
88 /**
89 * Parses a date value. The formats used for parsing the date value are retrieved from
90 * the default http params.
91 *
92 * @param dateValue the date value to parse
93 *
94 * @return the parsed date
95 *
96 * @throws DateParseException if the value could not be parsed using any of the
97 * supported date formats
98 */
99 public static Date parseDate(String dateValue) throws DateParseException {
100 return parseDate(dateValue, null, null);
101 }
102
103 /**
104 * Parses the date value using the given date formats.
105 *
106 * @param dateValue the date value to parse
107 * @param dateFormats the date formats to use
108 *
109 * @return the parsed date
110 *
111 * @throws DateParseException if none of the dataFormats could parse the dateValue
112 */
113 public static Date parseDate(final String dateValue, String[] dateFormats)
114 throws DateParseException {
115 return parseDate(dateValue, dateFormats, null);
116 }
117
118 /**
119 * Parses the date value using the given date formats.
120 *
121 * @param dateValue the date value to parse
122 * @param dateFormats the date formats to use
123 * @param startDate During parsing, two digit years will be placed in the range
124 * <code>startDate</code> to <code>startDate + 100 years</code>. This value may
125 * be <code>null</code>. When <code>null</code> is given as a parameter, year
126 * <code>2000</code> will be used.
127 *
128 * @return the parsed date
129 *
130 * @throws DateParseException if none of the dataFormats could parse the dateValue
131 */
132 public static Date parseDate(
133 String dateValue,
134 String[] dateFormats,
135 Date startDate
136 ) throws DateParseException {
137
138 if (dateValue == null) {
139 throw new IllegalArgumentException("dateValue is null");
140 }
141 if (dateFormats == null) {
142 dateFormats = DEFAULT_PATTERNS;
143 }
144 if (startDate == null) {
145 startDate = DEFAULT_TWO_DIGIT_YEAR_START;
146 }
147 // trim single quotes around date if present
148 // see issue #5279
149 if (dateValue.length() > 1
150 && dateValue.startsWith("'")
151 && dateValue.endsWith("'")
152 ) {
153 dateValue = dateValue.substring (1, dateValue.length() - 1);
154 }
155
156 for (String dateFormat : dateFormats) {
157 SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
158 dateParser.set2DigitYearStart(startDate);
159 ParsePosition pos = new ParsePosition(0);
160 Date result = dateParser.parse(dateValue, pos);
161 if (pos.getIndex() != 0)
162 return result;
163 }
164
165 // we were unable to parse the date
166 throw new DateParseException("Unable to parse the date " + dateValue);
167 }
168
169 /**
170 * Formats the given date according to the RFC 1123 pattern.
171 *
172 * @param date The date to format.
173 * @return An RFC 1123 formatted date string.
174 *
175 * @see #PATTERN_RFC1123
176 */
177 public static String formatDate(Date date) {
178 return formatDate(date, PATTERN_RFC1123);
179 }
180
181 /**
182 * Formats the given date according to the specified pattern. The pattern
183 * must conform to that used by the {@link SimpleDateFormat simple date
184 * format} class.
185 *
186 * @param date The date to format.
187 * @param pattern The pattern to use for formatting the date.
188 * @return A formatted date string.
189 *
190 * @throws IllegalArgumentException If the given date pattern is invalid.
191 *
192 * @see SimpleDateFormat
193 */
194 public static String formatDate(Date date, String pattern) {
195 if (date == null) throw new IllegalArgumentException("date is null");
196 if (pattern == null) throw new IllegalArgumentException("pattern is null");
197
198 SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
199 return formatter.format(date);
200 }
201
202 /**
203 * Clears thread-local variable containing {@link DateFormat} cache.
204 */
205 public static void clearThreadLocal() {
206 DateFormatHolder.clearThreadLocal();
207 }
208
209 /** This class should not be instantiated. */
210 private DateUtils() {
211 }
212
213 /**
214 * A factory for {@link SimpleDateFormat}s. The instances are stored in a
215 * threadlocal way because SimpleDateFormat is not threadsafe as noted in
216 * {@link SimpleDateFormat its javadoc}.
217 *
218 */
219 final static class DateFormatHolder {
220
221 private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
222 THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
223
224 @Override
225 protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
226 return new SoftReference<Map<String, SimpleDateFormat>>(
227 new HashMap<String, SimpleDateFormat>());
228 }
229
230 };
231
232 /**
233 * creates a {@link SimpleDateFormat} for the requested format string.
234 *
235 * @param pattern
236 * a non-<code>null</code> format String according to
237 * {@link SimpleDateFormat}. The format is not checked against
238 * <code>null</code> since all paths go through
239 * {@link DateUtils}.
240 * @return the requested format. This simple dateformat should not be used
241 * to {@link SimpleDateFormat#applyPattern(String) apply} to a
242 * different pattern.
243 */
244 public static SimpleDateFormat formatFor(String pattern) {
245 SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
246 Map<String, SimpleDateFormat> formats = ref.get();
247 if (formats == null) {
248 formats = new HashMap<String, SimpleDateFormat>();
249 THREADLOCAL_FORMATS.set(
250 new SoftReference<Map<String, SimpleDateFormat>>(formats));
251 }
252
253 SimpleDateFormat format = formats.get(pattern);
254 if (format == null) {
255 format = new SimpleDateFormat(pattern, Locale.US);
256 format.setTimeZone(TimeZone.getTimeZone("GMT"));
257 formats.put(pattern, format);
258 }
259
260 return format;
261 }
262
263 public static void clearThreadLocal() {
264 THREADLOCAL_FORMATS.remove();
265 }
266
267 }
268
269 }