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}