001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023/* 024 * ==================================================================== 025 * Licensed to the Apache Software Foundation (ASF) under one 026 * or more contributor license agreements. See the NOTICE file 027 * distributed with this work for additional information 028 * regarding copyright ownership. The ASF licenses this file 029 * to you under the Apache License, Version 2.0 (the 030 * "License"); you may not use this file except in compliance 031 * with the License. You may obtain a copy of the License at 032 * 033 * http://www.apache.org/licenses/LICENSE-2.0 034 * 035 * Unless required by applicable law or agreed to in writing, 036 * software distributed under the License is distributed on an 037 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 038 * KIND, either express or implied. See the License for the 039 * specific language governing permissions and limitations 040 * under the License. 041 * ==================================================================== 042 * 043 * This software consists of voluntary contributions made by many 044 * individuals on behalf of the Apache Software Foundation. For more 045 * information on the Apache Software Foundation, please see 046 * <http://www.apache.org/>. 047 * 048 */ 049 050import java.lang.ref.SoftReference; 051import java.text.ParsePosition; 052import java.text.SimpleDateFormat; 053import java.util.Calendar; 054import java.util.Date; 055import java.util.HashMap; 056import java.util.Locale; 057import java.util.Map; 058import java.util.TimeZone; 059 060/** 061 * A utility class for parsing and formatting HTTP dates as used in cookies and 062 * other headers. This class handles dates as defined by RFC 2616 section 063 * 3.3.1 as well as some other common non-standard formats. 064 */ 065public final class DateUtils { 066 067 /** 068 * Date format pattern used to parse HTTP date headers in RFC 1123 format. 069 */ 070 public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; 071 072 /** 073 * Date format pattern used to parse HTTP date headers in RFC 1036 format. 074 */ 075 public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; 076 077 /** 078 * Date format pattern used to parse HTTP date headers in ANSI C 079 * {@code asctime()} format. 080 */ 081 public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; 082 083 private static final String[] DEFAULT_PATTERNS = new String[] { 084 PATTERN_RFC1123, 085 PATTERN_RFC1036, 086 PATTERN_ASCTIME 087 }; 088 089 private static final Date DEFAULT_TWO_DIGIT_YEAR_START; 090 091 public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 092 093 static { 094 final Calendar calendar = Calendar.getInstance(); 095 calendar.setTimeZone(GMT); 096 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); 097 calendar.set(Calendar.MILLISECOND, 0); 098 DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); 099 } 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}