001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.client.utils;
029
030import java.lang.ref.SoftReference;
031import java.text.ParsePosition;
032import java.text.SimpleDateFormat;
033import java.util.Calendar;
034import java.util.Date;
035import java.util.HashMap;
036import java.util.Locale;
037import java.util.Map;
038import java.util.TimeZone;
039
040import org.apache.http.util.Args;
041
042/**
043 * A utility class for parsing and formatting HTTP dates as used in cookies and
044 * other headers.  This class handles dates as defined by RFC 2616 section
045 * 3.3.1 as well as some other common non-standard formats.
046 *
047 * @since 4.3
048 */
049public final class DateUtils {
050
051    /**
052     * Date format pattern used to parse HTTP date headers in RFC 1123 format.
053     */
054    public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
055
056    /**
057     * Date format pattern used to parse HTTP date headers in RFC 1036 format.
058     */
059    public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
060
061    /**
062     * Date format pattern used to parse HTTP date headers in ANSI C
063     * {@code asctime()} format.
064     */
065    public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
066
067    private static final String[] DEFAULT_PATTERNS = new String[] {
068        PATTERN_RFC1123,
069        PATTERN_RFC1036,
070        PATTERN_ASCTIME
071    };
072
073    private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
074
075    public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
076
077    static {
078        final Calendar calendar = Calendar.getInstance();
079        calendar.setTimeZone(GMT);
080        calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
081        calendar.set(Calendar.MILLISECOND, 0);
082        DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
083    }
084
085    /**
086     * Parses a date value.  The formats used for parsing the date value are retrieved from
087     * the default http params.
088     *
089     * @param dateValue the date value to parse
090     *
091     * @return the parsed date or null if input could not be parsed
092     */
093    public static Date parseDate(final String dateValue) {
094        return parseDate(dateValue, null, null);
095    }
096
097    /**
098     * Parses the date value using the given date formats.
099     *
100     * @param dateValue the date value to parse
101     * @param dateFormats the date formats to use
102     *
103     * @return the parsed date or null if input could not be parsed
104     */
105    public static Date parseDate(final String dateValue, final String[] dateFormats) {
106        return parseDate(dateValue, dateFormats, null);
107    }
108
109    /**
110     * Parses the date value using the given date formats.
111     *
112     * @param dateValue the date value to parse
113     * @param dateFormats the date formats to use
114     * @param startDate During parsing, two digit years will be placed in the range
115     * {@code startDate} to {@code startDate + 100 years}. This value may
116     * be {@code null}. When {@code null} is given as a parameter, year
117     * {@code 2000} will be used.
118     *
119     * @return the parsed date or null if input could not be parsed
120     */
121    public static Date parseDate(
122            final String dateValue,
123            final String[] dateFormats,
124            final Date startDate) {
125        Args.notNull(dateValue, "Date value");
126        final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
127        final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
128        String v = dateValue;
129        // trim single quotes around date if present
130        // see issue #5279
131        if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
132            v = v.substring (1, v.length() - 1);
133        }
134
135        for (final String dateFormat : localDateFormats) {
136            final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
137            dateParser.set2DigitYearStart(localStartDate);
138            final ParsePosition pos = new ParsePosition(0);
139            final Date result = dateParser.parse(v, pos);
140            if (pos.getIndex() != 0) {
141                return result;
142            }
143        }
144        return null;
145    }
146
147    /**
148     * Formats the given date according to the RFC 1123 pattern.
149     *
150     * @param date The date to format.
151     * @return An RFC 1123 formatted date string.
152     *
153     * @see #PATTERN_RFC1123
154     */
155    public static String formatDate(final Date date) {
156        return formatDate(date, PATTERN_RFC1123);
157    }
158
159    /**
160     * Formats the given date according to the specified pattern.  The pattern
161     * must conform to that used by the {@link SimpleDateFormat simple date
162     * format} class.
163     *
164     * @param date The date to format.
165     * @param pattern The pattern to use for formatting the date.
166     * @return A formatted date string.
167     *
168     * @throws IllegalArgumentException If the given date pattern is invalid.
169     *
170     * @see SimpleDateFormat
171     */
172    public static String formatDate(final Date date, final String pattern) {
173        Args.notNull(date, "Date");
174        Args.notNull(pattern, "Pattern");
175        final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
176        return formatter.format(date);
177    }
178
179    /**
180     * Clears thread-local variable containing {@link java.text.DateFormat} cache.
181     *
182     * @since 4.3
183     */
184    public static void clearThreadLocal() {
185        DateFormatHolder.clearThreadLocal();
186    }
187
188    /** This class should not be instantiated. */
189    private DateUtils() {
190    }
191
192    /**
193     * A factory for {@link SimpleDateFormat}s. The instances are stored in a
194     * threadlocal way because SimpleDateFormat is not threadsafe as noted in
195     * {@link SimpleDateFormat its javadoc}.
196     *
197     */
198    final static class DateFormatHolder {
199
200        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
201            THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>();
202
203        /**
204         * creates a {@link SimpleDateFormat} for the requested format string.
205         *
206         * @param pattern
207         *            a non-{@code null} format String according to
208         *            {@link SimpleDateFormat}. The format is not checked against
209         *            {@code null} since all paths go through
210         *            {@link DateUtils}.
211         * @return the requested format. This simple dateformat should not be used
212         *         to {@link SimpleDateFormat#applyPattern(String) apply} to a
213         *         different pattern.
214         */
215        public static SimpleDateFormat formatFor(final String pattern) {
216            final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
217            Map<String, SimpleDateFormat> formats = ref == null ? null : ref.get();
218            if (formats == null) {
219                formats = new HashMap<String, SimpleDateFormat>();
220                THREADLOCAL_FORMATS.set(
221                        new SoftReference<Map<String, SimpleDateFormat>>(formats));
222            }
223
224            SimpleDateFormat format = formats.get(pattern);
225            if (format == null) {
226                format = new SimpleDateFormat(pattern, Locale.US);
227                format.setTimeZone(TimeZone.getTimeZone("GMT"));
228                formats.put(pattern, format);
229            }
230
231            return format;
232        }
233
234        public static void clearThreadLocal() {
235            THREADLOCAL_FORMATS.remove();
236        }
237
238    }
239
240}