001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2020 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 023import java.lang.ref.SoftReference; 024import java.text.ParsePosition; 025import java.text.SimpleDateFormat; 026import java.util.*; 027 028/** 029 * A utility class for parsing and formatting HTTP dates as used in cookies and 030 * other headers. This class handles dates as defined by RFC 2616 section 031 * 3.3.1 as well as some other common non-standard formats. 032 * <p> 033 * This class is basically intended to be a high-performance workaround 034 * for the fact that Java SimpleDateFormat is kind of expensive to 035 * create and yet isn't thread safe. 036 * </p> 037 * <p> 038 * This class was adapted from the class with the same name from the Jetty 039 * project, licensed under the terms of the Apache Software License 2.0. 040 * </p> 041 */ 042public final class DateUtils { 043 044 /** 045 * GMT TimeZone 046 */ 047 public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 048 049 /** 050 * Date format pattern used to parse HTTP date headers in RFC 1123 format. 051 */ 052 @SuppressWarnings("WeakerAccess") 053 public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; 054 055 /** 056 * Date format pattern used to parse HTTP date headers in RFC 1036 format. 057 */ 058 @SuppressWarnings("WeakerAccess") 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 @SuppressWarnings("WeakerAccess") 066 public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; 067 068 private static final String[] DEFAULT_PATTERNS = new String[]{ 069 PATTERN_RFC1123, 070 PATTERN_RFC1036, 071 PATTERN_ASCTIME 072 }; 073 private static final Date DEFAULT_TWO_DIGIT_YEAR_START; 074 075 static { 076 final Calendar calendar = Calendar.getInstance(); 077 calendar.setTimeZone(GMT); 078 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); 079 calendar.set(Calendar.MILLISECOND, 0); 080 DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); 081 } 082 083 /** 084 * This class should not be instantiated. 085 */ 086 private DateUtils() { 087 } 088 089 /** 090 * A factory for {@link SimpleDateFormat}s. The instances are stored in a 091 * threadlocal way because SimpleDateFormat is not thread safe as noted in 092 * {@link SimpleDateFormat its javadoc}. 093 */ 094 final static class DateFormatHolder { 095 096 private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = ThreadLocal.withInitial(() -> new SoftReference<>(new HashMap<>())); 097 098 /** 099 * creates a {@link SimpleDateFormat} for the requested format string. 100 * 101 * @param pattern a non-{@code null} format String according to 102 * {@link SimpleDateFormat}. The format is not checked against 103 * {@code null} since all paths go through 104 * {@link DateUtils}. 105 * @return the requested format. This simple DateFormat should not be used 106 * to {@link SimpleDateFormat#applyPattern(String) apply} to a 107 * different pattern. 108 */ 109 static SimpleDateFormat formatFor(final String pattern) { 110 final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); 111 Map<String, SimpleDateFormat> formats = ref.get(); 112 if (formats == null) { 113 formats = new HashMap<>(); 114 THREADLOCAL_FORMATS.set( 115 new SoftReference<>(formats)); 116 } 117 118 SimpleDateFormat format = formats.get(pattern); 119 if (format == null) { 120 format = new SimpleDateFormat(pattern, Locale.US); 121 format.setTimeZone(TimeZone.getTimeZone("GMT")); 122 formats.put(pattern, format); 123 } 124 125 return format; 126 } 127 128 } 129 130 /** 131 * Parses a date value. The formats used for parsing the date value are retrieved from 132 * the default http params. 133 * 134 * @param theDateValue the date value to parse 135 * @return the parsed date or null if input could not be parsed 136 */ 137 public static Date parseDate(final String theDateValue) { 138 notNull(theDateValue, "Date value"); 139 String v = theDateValue; 140 if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) { 141 v = v.substring(1, v.length() - 1); 142 } 143 144 for (final String dateFormat : DEFAULT_PATTERNS) { 145 final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); 146 dateParser.set2DigitYearStart(DEFAULT_TWO_DIGIT_YEAR_START); 147 final ParsePosition pos = new ParsePosition(0); 148 final Date result = dateParser.parse(v, pos); 149 if (pos.getIndex() != 0) { 150 return result; 151 } 152 } 153 return null; 154 } 155 156 /** 157 * Formats the given date according to the RFC 1123 pattern. 158 * 159 * @param date The date to format. 160 * @return An RFC 1123 formatted date string. 161 * @see #PATTERN_RFC1123 162 */ 163 public static String formatDate(final Date date) { 164 notNull(date, "Date"); 165 notNull(PATTERN_RFC1123, "Pattern"); 166 final SimpleDateFormat formatter = DateFormatHolder.formatFor(PATTERN_RFC1123); 167 return formatter.format(date); 168 } 169 170 public static <T> T notNull(final T argument, final String name) { 171 if (argument == null) { 172 throw new IllegalArgumentException(name + " may not be null"); 173 } 174 return argument; 175 } 176 177}