View Javadoc
1   package ca.uhn.fhir.util;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  /*
24   * ====================================================================
25   * Licensed to the Apache Software Foundation (ASF) under one
26   * or more contributor license agreements.  See the NOTICE file
27   * distributed with this work for additional information
28   * regarding copyright ownership.  The ASF licenses this file
29   * to you under the Apache License, Version 2.0 (the
30   * "License"); you may not use this file except in compliance
31   * with the License.  You may obtain a copy of the License at
32   *
33   *   http://www.apache.org/licenses/LICENSE-2.0
34   *
35   * Unless required by applicable law or agreed to in writing,
36   * software distributed under the License is distributed on an
37   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
38   * KIND, either express or implied.  See the License for the
39   * specific language governing permissions and limitations
40   * under the License.
41   * ====================================================================
42   *
43   * This software consists of voluntary contributions made by many
44   * individuals on behalf of the Apache Software Foundation.  For more
45   * information on the Apache Software Foundation, please see
46   * <http://www.apache.org/>.
47   *
48   */
49  
50  import java.lang.ref.SoftReference;
51  import java.text.ParsePosition;
52  import java.text.SimpleDateFormat;
53  import java.util.Calendar;
54  import java.util.Date;
55  import java.util.HashMap;
56  import java.util.Locale;
57  import java.util.Map;
58  import java.util.TimeZone;
59  
60  /**
61   * A utility class for parsing and formatting HTTP dates as used in cookies and
62   * other headers.  This class handles dates as defined by RFC 2616 section
63   * 3.3.1 as well as some other common non-standard formats.
64   */
65  public final class DateUtils {
66  
67      /**
68       * Date format pattern used to parse HTTP date headers in RFC 1123 format.
69       */
70      public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
71  
72      /**
73       * Date format pattern used to parse HTTP date headers in RFC 1036 format.
74       */
75      public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
76  
77      /**
78       * Date format pattern used to parse HTTP date headers in ANSI C
79       * {@code asctime()} format.
80       */
81      public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
82  
83      private static final String[] DEFAULT_PATTERNS = new String[] {
84          PATTERN_RFC1123,
85          PATTERN_RFC1036,
86          PATTERN_ASCTIME
87      };
88  
89      private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
90  
91      public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
92  
93      static {
94          final Calendar calendar = Calendar.getInstance();
95          calendar.setTimeZone(GMT);
96          calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
97          calendar.set(Calendar.MILLISECOND, 0);
98          DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
99      }
100 
101     /**
102      * Parses a date value.  The formats used for parsing the date value are retrieved from
103      * the default http params.
104      *
105      * @param dateValue the date value to parse
106      *
107      * @return the parsed date or null if input could not be parsed
108      */
109     public static Date parseDate(final String dateValue) {
110         return parseDate(dateValue, null, null);
111     }
112 
113     /**
114      * Parses the date value using the given date formats.
115      *
116      * @param dateValue the date value to parse
117      * @param dateFormats the date formats to use
118      *
119      * @return the parsed date or null if input could not be parsed
120      */
121     public static Date parseDate(final String dateValue, final String[] dateFormats) {
122         return parseDate(dateValue, dateFormats, null);
123     }
124 
125     /**
126      * Parses the date value using the given date formats.
127      *
128      * @param dateValue the date value to parse
129      * @param dateFormats the date formats to use
130      * @param startDate During parsing, two digit years will be placed in the range
131      * {@code startDate} to {@code startDate + 100 years}. This value may
132      * be {@code null}. When {@code null} is given as a parameter, year
133      * {@code 2000} will be used.
134      *
135      * @return the parsed date or null if input could not be parsed
136      */
137     public static Date parseDate(
138             final String dateValue,
139             final String[] dateFormats,
140             final Date startDate) {
141         notNull(dateValue, "Date value");
142         final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
143         final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
144         String v = dateValue;
145         // trim single quotes around date if present
146         // see issue #5279
147         if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
148             v = v.substring (1, v.length() - 1);
149         }
150 
151         for (final String dateFormat : localDateFormats) {
152             final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
153             dateParser.set2DigitYearStart(localStartDate);
154             final ParsePosition pos = new ParsePosition(0);
155             final Date result = dateParser.parse(v, pos);
156             if (pos.getIndex() != 0) {
157                 return result;
158             }
159         }
160         return null;
161     }
162 
163     /**
164      * Formats the given date according to the RFC 1123 pattern.
165      *
166      * @param date The date to format.
167      * @return An RFC 1123 formatted date string.
168      *
169      * @see #PATTERN_RFC1123
170      */
171     public static String formatDate(final Date date) {
172         return formatDate(date, PATTERN_RFC1123);
173     }
174 
175     /**
176      * Formats the given date according to the specified pattern.  The pattern
177      * must conform to that used by the {@link SimpleDateFormat simple date
178      * format} class.
179      *
180      * @param date The date to format.
181      * @param pattern The pattern to use for formatting the date.
182      * @return A formatted date string.
183      *
184      * @throws IllegalArgumentException If the given date pattern is invalid.
185      *
186      * @see SimpleDateFormat
187      */
188     public static String formatDate(final Date date, final String pattern) {
189         notNull(date, "Date");
190         notNull(pattern, "Pattern");
191         final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
192         return formatter.format(date);
193     }
194     
195 
196     public static <T> T notNull(final T argument, final String name) {
197         if (argument == null) {
198             throw new IllegalArgumentException(name + " may not be null");
199         }
200         return argument;
201     }    
202 
203     /**
204      * Clears thread-local variable containing {@link java.text.DateFormat} cache.
205      *
206      * @since 4.3
207      */
208     public static void clearThreadLocal() {
209         DateFormatHolder.clearThreadLocal();
210     }
211 
212     /** This class should not be instantiated. */
213     private DateUtils() {
214     }
215 
216     /**
217      * A factory for {@link SimpleDateFormat}s. The instances are stored in a
218      * threadlocal way because SimpleDateFormat is not threadsafe as noted in
219      * {@link SimpleDateFormat its javadoc}.
220      *
221      */
222     final static class DateFormatHolder {
223 
224         private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
225             THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
226 
227             @Override
228             protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
229                 return new SoftReference<Map<String, SimpleDateFormat>>(
230                         new HashMap<String, SimpleDateFormat>());
231             }
232 
233         };
234 
235         /**
236          * creates a {@link SimpleDateFormat} for the requested format string.
237          *
238          * @param pattern
239          *            a non-{@code null} format String according to
240          *            {@link SimpleDateFormat}. The format is not checked against
241          *            {@code null} since all paths go through
242          *            {@link DateUtils}.
243          * @return the requested format. This simple dateformat should not be used
244          *         to {@link SimpleDateFormat#applyPattern(String) apply} to a
245          *         different pattern.
246          */
247         public static SimpleDateFormat formatFor(final String pattern) {
248             final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
249             Map<String, SimpleDateFormat> formats = ref.get();
250             if (formats == null) {
251                 formats = new HashMap<String, SimpleDateFormat>();
252                 THREADLOCAL_FORMATS.set(
253                         new SoftReference<Map<String, SimpleDateFormat>>(formats));
254             }
255 
256             SimpleDateFormat format = formats.get(pattern);
257             if (format == null) {
258                 format = new SimpleDateFormat(pattern, Locale.US);
259                 format.setTimeZone(TimeZone.getTimeZone("GMT"));
260                 formats.put(pattern, format);
261             }
262 
263             return format;
264         }
265 
266         public static void clearThreadLocal() {
267             THREADLOCAL_FORMATS.remove();
268         }
269 
270     }
271 
272 }