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> initialValue() { michael@0: return new SoftReference>( michael@0: new HashMap()); michael@0: } michael@0: michael@0: }; michael@0: michael@0: /** michael@0: * creates a {@link SimpleDateFormat} for the requested format string. michael@0: * michael@0: * @param pattern michael@0: * a non-null format String according to michael@0: * {@link SimpleDateFormat}. The format is not checked against michael@0: * null since all paths go through michael@0: * {@link DateUtils}. michael@0: * @return the requested format. This simple dateformat should not be used michael@0: * to {@link SimpleDateFormat#applyPattern(String) apply} to a michael@0: * different pattern. michael@0: */ michael@0: public static SimpleDateFormat formatFor(String pattern) { michael@0: SoftReference> ref = THREADLOCAL_FORMATS.get(); michael@0: Map formats = ref.get(); michael@0: if (formats == null) { michael@0: formats = new HashMap(); michael@0: THREADLOCAL_FORMATS.set( michael@0: new SoftReference>(formats)); michael@0: } michael@0: michael@0: SimpleDateFormat format = formats.get(pattern); michael@0: if (format == null) { michael@0: format = new SimpleDateFormat(pattern, Locale.US); michael@0: format.setTimeZone(TimeZone.getTimeZone("GMT")); michael@0: formats.put(pattern, format); michael@0: } michael@0: michael@0: return format; michael@0: } michael@0: michael@0: } michael@0: michael@0: }