001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2019 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 public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 045 /** 046 * Date format pattern used to parse HTTP date headers in RFC 1123 format. 047 */ 048 private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; 049 /** 050 * Date format pattern used to parse HTTP date headers in RFC 1036 format. 051 */ 052 private static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; 053 /** 054 * Date format pattern used to parse HTTP date headers in ANSI C 055 * {@code asctime()} format. 056 */ 057 private static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; 058 private static final String[] DEFAULT_PATTERNS = new String[]{ 059 PATTERN_RFC1123, 060 PATTERN_RFC1036, 061 PATTERN_ASCTIME 062 }; 063 private static final Date DEFAULT_TWO_DIGIT_YEAR_START; 064 065 static { 066 final Calendar calendar = Calendar.getInstance(); 067 calendar.setTimeZone(GMT); 068 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); 069 calendar.set(Calendar.MILLISECOND, 0); 070 DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); 071 } 072 073 /** 074 * This class should not be instantiated. 075 */ 076 private DateUtils() { 077 } 078 079 /** 080 * A factory for {@link SimpleDateFormat}s. The instances are stored in a 081 * threadlocal way because SimpleDateFormat is not thread safe as noted in 082 * {@link SimpleDateFormat its javadoc}. 083 */ 084 final static class DateFormatHolder { 085 086 private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = ThreadLocal.withInitial(() -> new SoftReference<>(new HashMap<>())); 087 088 /** 089 * creates a {@link SimpleDateFormat} for the requested format string. 090 * 091 * @param pattern a non-{@code null} format String according to 092 * {@link SimpleDateFormat}. The format is not checked against 093 * {@code null} since all paths go through 094 * {@link DateUtils}. 095 * @return the requested format. This simple DateFormat should not be used 096 * to {@link SimpleDateFormat#applyPattern(String) apply} to a 097 * different pattern. 098 */ 099 static SimpleDateFormat formatFor(final String pattern) { 100 final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); 101 Map<String, SimpleDateFormat> formats = ref.get(); 102 if (formats == null) { 103 formats = new HashMap<>(); 104 THREADLOCAL_FORMATS.set( 105 new SoftReference<>(formats)); 106 } 107 108 SimpleDateFormat format = formats.get(pattern); 109 if (format == null) { 110 format = new SimpleDateFormat(pattern, Locale.US); 111 format.setTimeZone(TimeZone.getTimeZone("GMT")); 112 formats.put(pattern, format); 113 } 114 115 return format; 116 } 117 118 } 119 120 /** 121 * Parses a date value. The formats used for parsing the date value are retrieved from 122 * the default http params. 123 * 124 * @param theDateValue the date value to parse 125 * @return the parsed date or null if input could not be parsed 126 */ 127 public static Date parseDate(final String theDateValue) { 128 notNull(theDateValue, "Date value"); 129 String v = theDateValue; 130 if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) { 131 v = v.substring(1, v.length() - 1); 132 } 133 134 for (final String dateFormat : DEFAULT_PATTERNS) { 135 final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); 136 dateParser.set2DigitYearStart(DEFAULT_TWO_DIGIT_YEAR_START); 137 final ParsePosition pos = new ParsePosition(0); 138 final Date result = dateParser.parse(v, pos); 139 if (pos.getIndex() != 0) { 140 return result; 141 } 142 } 143 return null; 144 } 145 146 /** 147 * Formats the given date according to the RFC 1123 pattern. 148 * 149 * @param date The date to format. 150 * @return An RFC 1123 formatted date string. 151 * @see #PATTERN_RFC1123 152 */ 153 public static String formatDate(final Date date) { 154 notNull(date, "Date"); 155 notNull(PATTERN_RFC1123, "Pattern"); 156 final SimpleDateFormat formatter = DateFormatHolder.formatFor(PATTERN_RFC1123); 157 return formatter.format(date); 158 } 159 160 public static <T> T notNull(final T argument, final String name) { 161 if (argument == null) { 162 throw new IllegalArgumentException(name + " may not be null"); 163 } 164 return argument; 165 } 166 167}