001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.internal; 014 015import static org.apache.juneau.common.internal.StringUtils.*; 016import static org.apache.juneau.common.internal.ThrowableUtils.*; 017 018import java.lang.ref.*; 019import java.text.*; 020import java.time.format.*; 021import java.util.*; 022 023import javax.xml.bind.*; 024 025import org.apache.juneau.reflect.*; 026 027/** 028 * A utility class for parsing and formatting HTTP dates as used in cookies and other headers. 029 * 030 * <p> 031 * This class handles dates as defined by RFC 2616 section 3.3.1 as well as some other common non-standard formats. 032 * 033 * <p> 034 * This class was copied from HttpClient 4.3. 035 * 036 * <h5 class='section'>See Also:</h5><ul> 037 * </ul> 038 */ 039public final class DateUtils { 040 041 /** 042 * Date format pattern used to parse HTTP date headers in RFC 1123 format. 043 */ 044 public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; 045 046 /** 047 * Date format pattern used to parse HTTP date headers in RFC 1036 format. 048 */ 049 public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; 050 051 /** 052 * Date format pattern used to parse HTTP date headers in ANSI C <c>asctime()</c> format. 053 */ 054 public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; 055 private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 056 static { 057 final Calendar calendar = Calendar.getInstance(); 058 calendar.setTimeZone(GMT); 059 calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); 060 calendar.set(Calendar.MILLISECOND, 0); 061 } 062 063 /** 064 * Parses an ISO8601 string and converts it to a {@link Calendar}. 065 * 066 * @param s The string to parse. 067 * @return The parsed value, or <jk>null</jk> if the string was <jk>null</jk> or empty. 068 */ 069 public static Calendar parseISO8601Calendar(String s) { 070 if (isEmpty(s)) 071 return null; 072 return DatatypeConverter.parseDateTime(toValidISO8601DT(s)); 073 } 074 075 /** 076 * Formats the given date according to the specified pattern. 077 * 078 * <p> 079 * The pattern must conform to that used by the {@link SimpleDateFormat simple date format} class. 080 * 081 * @param date The date to format. 082 * @param pattern The pattern to use for formatting the date. 083 * @return A formatted date string. 084 * @throws IllegalArgumentException If the given date pattern is invalid. 085 * @see SimpleDateFormat 086 */ 087 public static String formatDate(final Date date, final String pattern) { 088 final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern); 089 return formatter.format(date); 090 } 091 092 /** 093 * Clears thread-local variable containing {@link java.text.DateFormat} cache. 094 */ 095 public static void clearThreadLocal() { 096 DateFormatHolder.clearThreadLocal(); 097 } 098 099 /** 100 * A factory for {@link SimpleDateFormat}s. 101 * 102 * <p> 103 * The instances are stored in a thread-local way because SimpleDateFormat is not thread-safe as noted in 104 * {@link SimpleDateFormat its javadoc}. 105 */ 106 static final class DateFormatHolder { 107 private static final ThreadLocal<SoftReference<Map<String,SimpleDateFormat>>> THREADLOCAL_FORMATS = 108 new ThreadLocal<>() { 109 @Override 110 protected SoftReference<Map<String,SimpleDateFormat>> initialValue() { 111 Map<String,SimpleDateFormat> m = new HashMap<>(); 112 return new SoftReference<>(m); 113 } 114 }; 115 116 /** 117 * Creates a {@link SimpleDateFormat} for the requested format string. 118 * 119 * @param pattern 120 * A non-<c>null</c> format String according to {@link SimpleDateFormat}. 121 * The format is not checked against <c>null</c> since all paths go through {@link DateUtils}. 122 * @return 123 * The requested format. 124 * This simple date-format should not be used to {@link SimpleDateFormat#applyPattern(String) apply} to a 125 * different pattern. 126 */ 127 public static SimpleDateFormat formatFor(final String pattern) { 128 final SoftReference<Map<String,SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); 129 Map<String,SimpleDateFormat> formats = ref.get(); 130 if (formats == null) { 131 formats = new HashMap<>(); 132 THREADLOCAL_FORMATS.set(new SoftReference<>(formats)); 133 } 134 SimpleDateFormat format = formats.get(pattern); 135 if (format == null) { 136 format = new SimpleDateFormat(pattern, Locale.US); 137 format.setTimeZone(TimeZone.getTimeZone("GMT")); 138 formats.put(pattern, format); 139 } 140 return format; 141 } 142 143 public static void clearThreadLocal() { 144 THREADLOCAL_FORMATS.remove(); 145 } 146 } 147 148 /** 149 * Pads out an ISO8601 string so that it can be parsed using {@link DatatypeConverter#parseDateTime(String)}. 150 * 151 * <ul> 152 * <li><js>"2001-07-04T15:30:45-05:00"</js> -> <js>"2001-07-04T15:30:45-05:00"</js> 153 * <li><js>"2001-07-04T15:30:45Z"</js> -> <js>"2001-07-04T15:30:45Z"</js> 154 * <li><js>"2001-07-04T15:30:45.1Z"</js> -> <js>"2001-07-04T15:30:45.1Z"</js> 155 * <li><js>"2001-07-04T15:30Z"</js> -> <js>"2001-07-04T15:30:00Z"</js> 156 * <li><js>"2001-07-04T15:30"</js> -> <js>"2001-07-04T15:30:00"</js> 157 * <li><js>"2001-07-04"</js> -> <li><js>"2001-07-04T00:00:00"</js> 158 * <li><js>"2001-07"</js> -> <js>"2001-07-01T00:00:00"</js> 159 * <li><js>"2001"</js> -> <js>"2001-01-01T00:00:00"</js> 160 * </ul> 161 * 162 * @param in The string to pad. 163 * @return The padded string. 164 */ 165 public static String toValidISO8601DT(String in) { 166 167 // "2001-07-04T15:30:45Z" 168 final int 169 S1 = 1, // Looking for - 170 S2 = 2, // Found -, looking for - 171 S3 = 3, // Found -, looking for T 172 S4 = 4, // Found T, looking for : 173 S5 = 5, // Found :, looking for : 174 S6 = 6; // Found : 175 176 int state = 1; 177 boolean needsT = false; 178 for (int i = 0; i < in.length(); i++) { 179 char c = in.charAt(i); 180 if (state == S1) { 181 if (c == '-') 182 state = S2; 183 } else if (state == S2) { 184 if (c == '-') 185 state = S3; 186 } else if (state == S3) { 187 if (c == 'T') 188 state = S4; 189 if (c == ' ') { 190 state = S4; 191 needsT = true; 192 } 193 } else if (state == S4) { 194 if (c == ':') 195 state = S5; 196 } else if (state == S5) { 197 if (c == ':') 198 state = S6; 199 } 200 } 201 202 if (needsT) 203 in = in.replace(' ', 'T'); 204 switch(state) { 205 case S1: return in + "-01-01T00:00:00"; 206 case S2: return in + "-01T00:00:00"; 207 case S3: return in + "T00:00:00"; 208 case S4: return in + ":00:00"; 209 case S5: return in + ":00"; 210 default: return in; 211 } 212 } 213 214 /** 215 * Returns a {@link DateTimeFormatter} using either a pattern or predefined pattern name. 216 * 217 * @param pattern The pattern (e.g. <js>"yyyy-MM-dd"</js>) or pattern name (e.g. <js>"ISO_INSTANT"</js>). 218 * @return The formatter. 219 */ 220 public static DateTimeFormatter getFormatter(String pattern) { 221 if (isEmpty(pattern)) 222 return DateTimeFormatter.ISO_INSTANT; 223 try { 224 FieldInfo fi = ClassInfo.of(DateTimeFormatter.class).getPublicField(x -> x.isStatic() && x.hasName(pattern)); 225 if (fi != null) 226 return (DateTimeFormatter)fi.inner().get(null); 227 return DateTimeFormatter.ofPattern(pattern); 228 } catch (IllegalArgumentException | IllegalAccessException e) { 229 throw asRuntimeException(e); 230 } 231 } 232}