View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.client.utils;
29  
30  import java.lang.ref.SoftReference;
31  import java.text.ParsePosition;
32  import java.text.SimpleDateFormat;
33  import java.util.Calendar;
34  import java.util.Date;
35  import java.util.HashMap;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.TimeZone;
39  
40  import org.apache.http.util.Args;
41  
42  /**
43   * A utility class for parsing and formatting HTTP dates as used in cookies and
44   * other headers.  This class handles dates as defined by RFC 2616 section
45   * 3.3.1 as well as some other common non-standard formats.
46   *
47   * @since 4.3
48   */
49  public final class DateUtils {
50  
51      /**
52       * Date format pattern used to parse HTTP date headers in RFC 1123 format.
53       */
54      public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
55  
56      /**
57       * Date format pattern used to parse HTTP date headers in RFC 1036 format.
58       */
59      public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
60  
61      /**
62       * Date format pattern used to parse HTTP date headers in ANSI C
63       * {@code asctime()} format.
64       */
65      public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
66  
67      private static final String[] DEFAULT_PATTERNS = new String[] {
68          PATTERN_RFC1123,
69          PATTERN_RFC1036,
70          PATTERN_ASCTIME
71      };
72  
73      private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
74  
75      public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
76  
77      static {
78          final Calendar calendar = Calendar.getInstance();
79          calendar.setTimeZone(GMT);
80          calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
81          calendar.set(Calendar.MILLISECOND, 0);
82          DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
83      }
84  
85      /**
86       * Parses a date value.  The formats used for parsing the date value are retrieved from
87       * the default http params.
88       *
89       * @param dateValue the date value to parse
90       *
91       * @return the parsed date or null if input could not be parsed
92       */
93      public static Date parseDate(final String dateValue) {
94          return parseDate(dateValue, null, null);
95      }
96  
97      /**
98       * Parses the date value using the given date formats.
99       *
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 }