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.annotation.Immutable;
41  import org.apache.http.util.Args;
42  
43  /**
44   * A utility class for parsing and formatting HTTP dates as used in cookies and
45   * other headers.  This class handles dates as defined by RFC 2616 section
46   * 3.3.1 as well as some other common non-standard formats.
47   *
48   * @since 4.3
49   */
50  @Immutable
51  public final class DateUtils {
52  
53      /**
54       * Date format pattern used to parse HTTP date headers in RFC 1123 format.
55       */
56      public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
57  
58      /**
59       * Date format pattern used to parse HTTP date headers in RFC 1036 format.
60       */
61      public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
62  
63      /**
64       * Date format pattern used to parse HTTP date headers in ANSI C
65       * {@code asctime()} format.
66       */
67      public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
68  
69      private static final String[] DEFAULT_PATTERNS = new String[] {
70          PATTERN_RFC1123,
71          PATTERN_RFC1036,
72          PATTERN_ASCTIME
73      };
74  
75      private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
76  
77      public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
78  
79      static {
80          final Calendar calendar = Calendar.getInstance();
81          calendar.setTimeZone(GMT);
82          calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
83          calendar.set(Calendar.MILLISECOND, 0);
84          DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
85      }
86  
87      /**
88       * Parses a date value.  The formats used for parsing the date value are retrieved from
89       * the default http params.
90       *
91       * @param dateValue the date value to parse
92       *
93       * @return the parsed date or null if input could not be parsed
94       */
95      public static Date parseDate(final String dateValue) {
96          return parseDate(dateValue, null, null);
97      }
98  
99      /**
100      * Parses the date value using the given date formats.
101      *
102      * @param dateValue the date value to parse
103      * @param dateFormats the date formats to use
104      *
105      * @return the parsed date or null if input could not be parsed
106      */
107     public static Date parseDate(final String dateValue, final String[] dateFormats) {
108         return parseDate(dateValue, dateFormats, null);
109     }
110 
111     /**
112      * Parses the date value using the given date formats.
113      *
114      * @param dateValue the date value to parse
115      * @param dateFormats the date formats to use
116      * @param startDate During parsing, two digit years will be placed in the range
117      * {@code startDate} to {@code startDate + 100 years}. This value may
118      * be {@code null}. When {@code null} is given as a parameter, year
119      * {@code 2000} will be used.
120      *
121      * @return the parsed date or null if input could not be parsed
122      */
123     public static Date parseDate(
124             final String dateValue,
125             final String[] dateFormats,
126             final Date startDate) {
127         Args.notNull(dateValue, "Date value");
128         final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
129         final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
130         String v = dateValue;
131         // trim single quotes around date if present
132         // see issue #5279
133         if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
134             v = v.substring (1, v.length() - 1);
135         }
136 
137         for (final String dateFormat : localDateFormats) {
138             final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
139             dateParser.set2DigitYearStart(localStartDate);
140             final ParsePosition pos = new ParsePosition(0);
141             final Date result = dateParser.parse(v, pos);
142             if (pos.getIndex() != 0) {
143                 return result;
144             }
145         }
146         return null;
147     }
148 
149     /**
150      * Formats the given date according to the RFC 1123 pattern.
151      *
152      * @param date The date to format.
153      * @return An RFC 1123 formatted date string.
154      *
155      * @see #PATTERN_RFC1123
156      */
157     public static String formatDate(final Date date) {
158         return formatDate(date, PATTERN_RFC1123);
159     }
160 
161     /**
162      * Formats the given date according to the specified pattern.  The pattern
163      * must conform to that used by the {@link SimpleDateFormat simple date
164      * format} class.
165      *
166      * @param date The date to format.
167      * @param pattern The pattern to use for formatting the date.
168      * @return A formatted date string.
169      *
170      * @throws IllegalArgumentException If the given date pattern is invalid.
171      *
172      * @see SimpleDateFormat
173      */
174     public static String formatDate(final Date date, final String pattern) {
175         Args.notNull(date, "Date");
176         Args.notNull(pattern, "Pattern");
177         final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
178         return formatter.format(date);
179     }
180 
181     /**
182      * Clears thread-local variable containing {@link java.text.DateFormat} cache.
183      *
184      * @since 4.3
185      */
186     public static void clearThreadLocal() {
187         DateFormatHolder.clearThreadLocal();
188     }
189 
190     /** This class should not be instantiated. */
191     private DateUtils() {
192     }
193 
194     /**
195      * A factory for {@link SimpleDateFormat}s. The instances are stored in a
196      * threadlocal way because SimpleDateFormat is not threadsafe as noted in
197      * {@link SimpleDateFormat its javadoc}.
198      *
199      */
200     final static class DateFormatHolder {
201 
202         private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
203             THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
204 
205             @Override
206             protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
207                 return new SoftReference<Map<String, SimpleDateFormat>>(
208                         new HashMap<String, SimpleDateFormat>());
209             }
210 
211         };
212 
213         /**
214          * creates a {@link SimpleDateFormat} for the requested format string.
215          *
216          * @param pattern
217          *            a non-{@code null} format String according to
218          *            {@link SimpleDateFormat}. The format is not checked against
219          *            {@code null} since all paths go through
220          *            {@link DateUtils}.
221          * @return the requested format. This simple dateformat should not be used
222          *         to {@link SimpleDateFormat#applyPattern(String) apply} to a
223          *         different pattern.
224          */
225         public static SimpleDateFormat formatFor(final String pattern) {
226             final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
227             Map<String, SimpleDateFormat> formats = ref.get();
228             if (formats == null) {
229                 formats = new HashMap<String, SimpleDateFormat>();
230                 THREADLOCAL_FORMATS.set(
231                         new SoftReference<Map<String, SimpleDateFormat>>(formats));
232             }
233 
234             SimpleDateFormat format = formats.get(pattern);
235             if (format == null) {
236                 format = new SimpleDateFormat(pattern, Locale.US);
237                 format.setTimeZone(TimeZone.getTimeZone("GMT"));
238                 formats.put(pattern, format);
239             }
240 
241             return format;
242         }
243 
244         public static void clearThreadLocal() {
245             THREADLOCAL_FORMATS.remove();
246         }
247 
248     }
249 
250 }