michael@0: /*
michael@0: * ====================================================================
michael@0: * Licensed to the Apache Software Foundation (ASF) under one
michael@0: * or more contributor license agreements. See the NOTICE file
michael@0: * distributed with this work for additional information
michael@0: * regarding copyright ownership. The ASF licenses this file
michael@0: * to you under the Apache License, Version 2.0 (the
michael@0: * "License"); you may not use this file except in compliance
michael@0: * with the License. You may obtain a copy of the License at
michael@0: *
michael@0: * http://www.apache.org/licenses/LICENSE-2.0
michael@0: *
michael@0: * Unless required by applicable law or agreed to in writing,
michael@0: * software distributed under the License is distributed on an
michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
michael@0: * KIND, either express or implied. See the License for the
michael@0: * specific language governing permissions and limitations
michael@0: * under the License.
michael@0: * ====================================================================
michael@0: *
michael@0: * This software consists of voluntary contributions made by many
michael@0: * individuals on behalf of the Apache Software Foundation. For more
michael@0: * information on the Apache Software Foundation, please see
michael@0: * .
michael@0: *
michael@0: */
michael@0:
michael@0: package ch.boye.httpclientandroidlib.impl.cookie;
michael@0:
michael@0: import java.lang.ref.SoftReference;
michael@0: import java.text.ParseException;
michael@0: import java.text.SimpleDateFormat;
michael@0: import java.util.Calendar;
michael@0: import java.util.Date;
michael@0: import java.util.HashMap;
michael@0: import java.util.Locale;
michael@0: import java.util.Map;
michael@0: import java.util.TimeZone;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.annotation.Immutable;
michael@0:
michael@0: /**
michael@0: * A utility class for parsing and formatting HTTP dates as used in cookies and
michael@0: * other headers. This class handles dates as defined by RFC 2616 section
michael@0: * 3.3.1 as well as some other common non-standard formats.
michael@0: *
michael@0: *
michael@0: * @since 4.0
michael@0: */
michael@0: @Immutable
michael@0: public final class DateUtils {
michael@0:
michael@0: /**
michael@0: * Date format pattern used to parse HTTP date headers in RFC 1123 format.
michael@0: */
michael@0: public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
michael@0:
michael@0: /**
michael@0: * Date format pattern used to parse HTTP date headers in RFC 1036 format.
michael@0: */
michael@0: public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
michael@0:
michael@0: /**
michael@0: * Date format pattern used to parse HTTP date headers in ANSI C
michael@0: * asctime()
format.
michael@0: */
michael@0: public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
michael@0:
michael@0: private static final String[] DEFAULT_PATTERNS = new String[] {
michael@0: PATTERN_RFC1036,
michael@0: PATTERN_RFC1123,
michael@0: PATTERN_ASCTIME
michael@0: };
michael@0:
michael@0: private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
michael@0:
michael@0: public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
michael@0:
michael@0: static {
michael@0: Calendar calendar = Calendar.getInstance();
michael@0: calendar.setTimeZone(GMT);
michael@0: calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
michael@0: calendar.set(Calendar.MILLISECOND, 0);
michael@0: DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
michael@0: }
michael@0:
michael@0: /**
michael@0: * Parses a date value. The formats used for parsing the date value are retrieved from
michael@0: * the default http params.
michael@0: *
michael@0: * @param dateValue the date value to parse
michael@0: *
michael@0: * @return the parsed date
michael@0: *
michael@0: * @throws DateParseException if the value could not be parsed using any of the
michael@0: * supported date formats
michael@0: */
michael@0: public static Date parseDate(String dateValue) throws DateParseException {
michael@0: return parseDate(dateValue, null, null);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Parses the date value using the given date formats.
michael@0: *
michael@0: * @param dateValue the date value to parse
michael@0: * @param dateFormats the date formats to use
michael@0: *
michael@0: * @return the parsed date
michael@0: *
michael@0: * @throws DateParseException if none of the dataFormats could parse the dateValue
michael@0: */
michael@0: public static Date parseDate(final String dateValue, String[] dateFormats)
michael@0: throws DateParseException {
michael@0: return parseDate(dateValue, dateFormats, null);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Parses the date value using the given date formats.
michael@0: *
michael@0: * @param dateValue the date value to parse
michael@0: * @param dateFormats the date formats to use
michael@0: * @param startDate During parsing, two digit years will be placed in the range
michael@0: * startDate
to startDate + 100 years
. This value may
michael@0: * be null
. When null
is given as a parameter, year
michael@0: * 2000
will be used.
michael@0: *
michael@0: * @return the parsed date
michael@0: *
michael@0: * @throws DateParseException if none of the dataFormats could parse the dateValue
michael@0: */
michael@0: public static Date parseDate(
michael@0: String dateValue,
michael@0: String[] dateFormats,
michael@0: Date startDate
michael@0: ) throws DateParseException {
michael@0:
michael@0: if (dateValue == null) {
michael@0: throw new IllegalArgumentException("dateValue is null");
michael@0: }
michael@0: if (dateFormats == null) {
michael@0: dateFormats = DEFAULT_PATTERNS;
michael@0: }
michael@0: if (startDate == null) {
michael@0: startDate = DEFAULT_TWO_DIGIT_YEAR_START;
michael@0: }
michael@0: // trim single quotes around date if present
michael@0: // see issue #5279
michael@0: if (dateValue.length() > 1
michael@0: && dateValue.startsWith("'")
michael@0: && dateValue.endsWith("'")
michael@0: ) {
michael@0: dateValue = dateValue.substring (1, dateValue.length() - 1);
michael@0: }
michael@0:
michael@0: for (String dateFormat : dateFormats) {
michael@0: SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
michael@0: dateParser.set2DigitYearStart(startDate);
michael@0:
michael@0: try {
michael@0: return dateParser.parse(dateValue);
michael@0: } catch (ParseException pe) {
michael@0: // ignore this exception, we will try the next format
michael@0: }
michael@0: }
michael@0:
michael@0: // we were unable to parse the date
michael@0: throw new DateParseException("Unable to parse the date " + dateValue);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Formats the given date according to the RFC 1123 pattern.
michael@0: *
michael@0: * @param date The date to format.
michael@0: * @return An RFC 1123 formatted date string.
michael@0: *
michael@0: * @see #PATTERN_RFC1123
michael@0: */
michael@0: public static String formatDate(Date date) {
michael@0: return formatDate(date, PATTERN_RFC1123);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Formats the given date according to the specified pattern. The pattern
michael@0: * must conform to that used by the {@link SimpleDateFormat simple date
michael@0: * format} class.
michael@0: *
michael@0: * @param date The date to format.
michael@0: * @param pattern The pattern to use for formatting the date.
michael@0: * @return A formatted date string.
michael@0: *
michael@0: * @throws IllegalArgumentException If the given date pattern is invalid.
michael@0: *
michael@0: * @see SimpleDateFormat
michael@0: */
michael@0: public static String formatDate(Date date, String pattern) {
michael@0: if (date == null) throw new IllegalArgumentException("date is null");
michael@0: if (pattern == null) throw new IllegalArgumentException("pattern is null");
michael@0:
michael@0: SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
michael@0: return formatter.format(date);
michael@0: }
michael@0:
michael@0: /** This class should not be instantiated. */
michael@0: private DateUtils() {
michael@0: }
michael@0:
michael@0: /**
michael@0: * A factory for {@link SimpleDateFormat}s. The instances are stored in a
michael@0: * threadlocal way because SimpleDateFormat is not threadsafe as noted in
michael@0: * {@link SimpleDateFormat its javadoc}.
michael@0: *
michael@0: */
michael@0: final static class DateFormatHolder {
michael@0:
michael@0: private static final ThreadLocal>>
michael@0: THREADLOCAL_FORMATS = new ThreadLocal>>() {
michael@0:
michael@0: @Override
michael@0: protected SoftReference