1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/DateUtils.java $
3 * $Revision: 677240 $
4 * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.impl.cookie;
33
34import java.lang.ref.SoftReference;
35import java.text.ParseException;
36import java.text.SimpleDateFormat;
37import java.util.Calendar;
38import java.util.Date;
39import java.util.HashMap;
40import java.util.Locale;
41import java.util.Map;
42import java.util.TimeZone;
43
44/**
45 * A utility class for parsing and formatting HTTP dates as used in cookies and
46 * other headers.  This class handles dates as defined by RFC 2616 section
47 * 3.3.1 as well as some other common non-standard formats.
48 *
49 * @author Christopher Brown
50 * @author Michael Becke
51 */
52public 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 = "EEEE, 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_RFC1036,
72    	PATTERN_RFC1123,
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
160            try {
161                return dateParser.parse(dateValue);
162            } catch (ParseException pe) {
163                // ignore this exception, we will try the next format
164            }
165        }
166
167        // we were unable to parse the date
168        throw new DateParseException("Unable to parse the date " + dateValue);
169    }
170
171    /**
172     * Formats the given date according to the RFC 1123 pattern.
173     *
174     * @param date The date to format.
175     * @return An RFC 1123 formatted date string.
176     *
177     * @see #PATTERN_RFC1123
178     */
179    public static String formatDate(Date date) {
180        return formatDate(date, PATTERN_RFC1123);
181    }
182
183    /**
184     * Formats the given date according to the specified pattern.  The pattern
185     * must conform to that used by the {@link SimpleDateFormat simple date
186     * format} class.
187     *
188     * @param date The date to format.
189     * @param pattern The pattern to use for formatting the date.
190     * @return A formatted date string.
191     *
192     * @throws IllegalArgumentException If the given date pattern is invalid.
193     *
194     * @see SimpleDateFormat
195     */
196    public static String formatDate(Date date, String pattern) {
197        if (date == null) throw new IllegalArgumentException("date is null");
198        if (pattern == null) throw new IllegalArgumentException("pattern is null");
199
200        SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
201        return formatter.format(date);
202    }
203
204    /** This class should not be instantiated. */
205    private DateUtils() {
206    }
207
208    /**
209     * A factory for {@link SimpleDateFormat}s. The instances are stored in a
210     * threadlocal way because SimpleDateFormat is not threadsafe as noted in
211     * {@link SimpleDateFormat its javadoc}.
212     *
213     * @author Daniel Mueller
214     */
215    final static class DateFormatHolder {
216
217        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
218            THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
219
220            @Override
221            protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
222                return new SoftReference<Map<String, SimpleDateFormat>>(
223                        new HashMap<String, SimpleDateFormat>());
224            }
225
226        };
227
228        /**
229         * creates a {@link SimpleDateFormat} for the requested format string.
230         *
231         * @param pattern
232         *            a non-<code>null</code> format String according to
233         *            {@link SimpleDateFormat}. The format is not checked against
234         *            <code>null</code> since all paths go through
235         *            {@link DateUtils}.
236         * @return the requested format. This simple dateformat should not be used
237         *         to {@link SimpleDateFormat#applyPattern(String) apply} to a
238         *         different pattern.
239         */
240        public static SimpleDateFormat formatFor(String pattern) {
241            SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
242            Map<String, SimpleDateFormat> formats = ref.get();
243            if (formats == null) {
244                formats = new HashMap<String, SimpleDateFormat>();
245                THREADLOCAL_FORMATS.set(
246                        new SoftReference<Map<String, SimpleDateFormat>>(formats));
247            }
248
249            SimpleDateFormat format = formats.get(pattern);
250            if (format == null) {
251                format = new SimpleDateFormat(pattern, Locale.US);
252                format.setTimeZone(TimeZone.getTimeZone("GMT"));
253                formats.put(pattern, format);
254            }
255
256            return format;
257        }
258
259    }
260
261}
262