createSimpleDateFormats() {
+ final HttpDateFormatterFromSimpleDateTimeFormat[] formats = new HttpDateFormatterFromSimpleDateTimeFormat[]{
+ new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(RFC1123_DATE_FORMAT_PATTERN, Locale.US)),
+ new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(RFC1036_DATE_FORMAT_PATTERN, Locale.US)),
+ new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US))
+ };
+ formats[0].simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
+ formats[1].simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
+ formats[2].simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
return Collections.unmodifiableList(Arrays.asList(formats));
}
/**
- * Return an unmodifiable list of HTTP specified date formats to use for
- * parsing or formatting {@link Date}.
+ * Get the preferred HTTP specified date format (RFC 1123).
*
- * The list of date formats are scoped to the current thread and may be
- * used without requiring to synchronize access to the instances when
+ * The date format is scoped to the current thread and may be
+ * used without requiring to synchronize access to the instance when
* parsing or formatting.
*
- * @return the list of data formats.
+ * @return the preferred of data format.
*/
- private static List getDateFormats() {
- return dateFormats.get();
+ public static HttpDateFormatter getPreferredDateFormatter() {
+ if (USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER) {
+ List list = simpleDateFormats.poll();
+ if (list == null) {
+ list = createSimpleDateFormats();
+ }
+ // returns clone because calling SDF.parse(...) can change time zone
+ final SimpleDateFormat sdf = (SimpleDateFormat)
+ ((HttpDateFormatterFromSimpleDateTimeFormat) list.get(0)).simpleDateFormat.clone();
+ simpleDateFormats.add(list);
+ return new HttpDateFormatterFromSimpleDateTimeFormat(sdf);
+ } else {
+ return dateFormats.get(0);
+ }
}
/**
@@ -87,10 +165,20 @@ private static List getDateFormats() {
* parsing or formatting.
*
* @return the preferred of data format.
+ * @deprecated Use getPreferredDateFormatter instead
*/
+ // Unused in Jersey
+ @Deprecated(forRemoval = true)
public static SimpleDateFormat getPreferredDateFormat() {
+ List list = simpleDateFormats.poll();
+ if (list == null) {
+ list = createSimpleDateFormats();
+ }
// returns clone because calling SDF.parse(...) can change time zone
- return (SimpleDateFormat) dateFormats.get().get(0).clone();
+ final SimpleDateFormat sdf = (SimpleDateFormat)
+ ((HttpDateFormatterFromSimpleDateTimeFormat) list.get(0)).simpleDateFormat.clone();
+ simpleDateFormats.add(list);
+ return sdf;
}
/**
@@ -102,18 +190,106 @@ public static SimpleDateFormat getPreferredDateFormat() {
* @throws java.text.ParseException in case the date string cannot be parsed.
*/
public static Date readDate(final String date) throws ParseException {
- ParseException pe = null;
- for (final SimpleDateFormat f : HttpDateFormat.getDateFormats()) {
+ return USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER
+ ? readDateSDF(date)
+ : readDateDTF(date);
+ }
+
+ private static Date readDateDTF(final String date) throws ParseException {
+ final List list = dateFormats;
+ return readDate(date, list);
+ }
+
+ private static Date readDateSDF(final String date) throws ParseException {
+ List list = simpleDateFormats.poll();
+ if (list == null) {
+ list = createSimpleDateFormats();
+ }
+ final Date ret = readDate(date, list);
+ simpleDateFormats.add(list);
+ return ret;
+ }
+
+ private static Date readDate(final String date, List formatters) throws ParseException {
+ Exception pe = null;
+ for (final HttpDateFormatter f : formatters) {
try {
- Date result = f.parse(date);
- // parse can change time zone -> set it back to GMT
- f.setTimeZone(GMT_TIME_ZONE);
- return result;
- } catch (final ParseException e) {
+ return f.toDate(date);
+ } catch (final Exception e) {
pe = (pe == null) ? e : pe;
}
}
- throw pe;
+ throw ParseException.class.isInstance(pe) ? (ParseException) pe
+ : new ParseException(pe.getMessage(),
+ DateTimeParseException.class.isInstance(pe) ? ((DateTimeParseException) pe).getErrorIndex() : 0);
+ }
+
+ /**
+ * Warning! DateTimeFormatter is incompatible with SimpleDateFormat for two digits year, since SimpleDateFormat uses
+ * 80 years before now and 20 years after, whereas DateTimeFormatter uses years starting with 2000.
+ */
+ private static class HttpDateFormatterFromDateTimeFormatter implements HttpDateFormatter {
+ private final DateTimeFormatter dateTimeFormatter;
+
+ private HttpDateFormatterFromDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
+ this.dateTimeFormatter = dateTimeFormatter;
+ }
+
+ @Override
+ public Date toDate(String date) {
+ return new Date(Instant.from(dateTimeFormatter.parse(date)).toEpochMilli());
+ }
+
+ @Override
+ public LocalDateTime toDateTime(String date) {
+ return Instant.from(dateTimeFormatter.parse(date)).atZone(GMT_TIME_ZONE.toZoneId()).toLocalDateTime();
+ }
+
+ @Override
+ public String format(Date date) {
+ return dateTimeFormatter.format(date.toInstant());
+ }
+
+ @Override
+ public String format(LocalDateTime dateTime) {
+ return dateTimeFormatter.format(dateTime);
+ }
+ }
+
+ private static class HttpDateFormatterFromSimpleDateTimeFormat implements HttpDateFormatter {
+ private final SimpleDateFormat simpleDateFormat;
+
+ private HttpDateFormatterFromSimpleDateTimeFormat(SimpleDateFormat simpleDateFormat) {
+ this.simpleDateFormat = simpleDateFormat;
+ }
+
+ @Override
+ public Date toDate(String date) {
+ final Date result;
+ try {
+ result = simpleDateFormat.parse(date);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ // parse can change time zone -> set it back to GMT
+ simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
+ return result;
+ }
+
+ @Override
+ public LocalDateTime toDateTime(String date) {
+ return Instant.from(toDate(date).toInstant()).atZone(GMT_TIME_ZONE.toZoneId()).toLocalDateTime();
+ }
+
+ @Override
+ public String format(Date date) {
+ return simpleDateFormat.format(date);
+ }
+
+ @Override
+ public String format(LocalDateTime dateTime) {
+ return simpleDateFormat.format(Date.from(dateTime.atZone(GMT_TIME_ZONE.toZoneId()).toInstant()));
+ }
}
}
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java
index 8615aeb686..08fbf5a126 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -81,7 +81,7 @@ public String toString(final NewCookie cookie) {
}
if (cookie.getExpiry() != null) {
b.append(";Expires=");
- b.append(HttpDateFormat.getPreferredDateFormat().format(cookie.getExpiry()));
+ b.append(HttpDateFormat.getPreferredDateFormatter().format(cookie.getExpiry()));
}
return b.toString();
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java
index 34f29d6548..07b368e577 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -402,8 +402,8 @@ public void testFormParamDate() throws ExecutionException, InterruptedException
initiateWebApplication(FormResourceDate.class);
final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT";
- final String date_RFC1036 = "Sunday, 06-Nov-94 08:49:37 GMT";
- final String date_ANSI_C = "Sun Nov 6 08:49:37 1994";
+ final String date_RFC1036 = "Sunday, 07-Nov-04 08:49:37 GMT";
+ final String date_ANSI_C = "Sun Nov 6 08:49:37 1994";
final Form form = new Form();
form.param("a", date_RFC1123);
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java
index 6d77453aa3..f6c7165960 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java
@@ -202,7 +202,9 @@ protected void addStringParameter(final StringBuilder sb, final String name, fin
protected void addDateParameter(final StringBuilder sb, final String name, final Date p) {
if (p != null) {
- sb.append("; ").append(name).append("=\"").append(HttpDateFormat.getPreferredDateFormat().format(p)).append("\"");
+ sb.append("; ").append(name).append("=\"")
+ .append(HttpDateFormat.getPreferredDateFormatter().format(p))
+ .append("\"");
}
}
@@ -302,7 +304,7 @@ private Date createDate(final String name) throws ParseException {
if (value == null) {
return null;
}
- return HttpDateFormat.getPreferredDateFormat().parse(value);
+ return HttpDateFormat.getPreferredDateFormatter().toDate(value);
}
private long createLong(final String name) throws ParseException {
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java
index 8cecb6b1c1..138466ec6e 100644
--- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java
@@ -66,7 +66,7 @@ public void testCreate() {
contentDisposition = new ContentDisposition(header);
assertNotNull(contentDisposition);
assertEquals(contentDispositionType, contentDisposition.getType());
- final String dateString = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date);
header = contentDispositionType + ";filename=\"test.file\";creation-date=\""
+ dateString + "\";modification-date=\"" + dateString + "\";read-date=\""
+ dateString + "\";size=1222";
@@ -101,7 +101,7 @@ public void testToString() {
final Date date = new Date();
final ContentDisposition contentDisposition = ContentDisposition.type(contentDispositionType).fileName("test.file")
.creationDate(date).modificationDate(date).readDate(date).size(1222).build();
- final String dateString = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date);
final String header = contentDispositionType + "; filename=\"test.file\"; creation-date=\""
+ dateString + "\"; modification-date=\"" + dateString + "\"; read-date=\"" + dateString + "\"; size=1222";
assertEquals(header, contentDisposition.toString());
@@ -252,7 +252,7 @@ private void assertFileNameExt(
final boolean decode
) throws ParseException {
final Date date = new Date();
- final String dateString = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date);
final String prefixHeader = contentDispositionType + ";filename=\"" + actualFileName + "\";"
+ "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\""
+ dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\"";
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java
index c9318094b2..0595890319 100644
--- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -45,7 +45,7 @@ public void testCreate() {
.modificationDate(date).readDate(date).size(1222).build();
assertFormDataContentDisposition(contentDisposition, date);
try {
- final String dateString = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date);
final String header = contentDispositionType + ";filename=\"test.file\";creation-date=\"" + dateString
+ "\";modification-date=\"" + dateString + "\";read-date=\"" + dateString + "\";size=1222"
+ ";name=\"testData\"";
@@ -92,7 +92,7 @@ public void testToString() {
final FormDataContentDisposition contentDisposition = FormDataContentDisposition.name("testData")
.fileName("test.file").creationDate(date).modificationDate(date)
.readDate(date).size(1222).build();
- final String dateString = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date);
final String header = contentDispositionType + "; filename=\"test.file\"; creation-date=\"" + dateString
+ "\"; modification-date=\"" + dateString + "\"; read-date=\"" + dateString + "\"; size=1222"
+ "; name=\"testData\"";
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java
index 63305db921..4e1bfd34ec 100644
--- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -100,8 +100,8 @@ public void testAcceptableTokenList() throws Exception {
@Test
public void testDateParsing() throws ParseException {
final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT";
- final String date_RFC1036 = "Sunday, 06-Nov-94 08:49:37 GMT";
- final String date_ANSI_C = "Sun Nov 6 08:49:37 1994";
+ final String date_RFC1036 = "Sunday, 07-Nov-04 08:49:37 GMT";
+ final String date_ANSI_C = "Sun Nov 6 08:49:37 1994";
HttpHeaderReader.readDate(date_RFC1123);
HttpHeaderReader.readDate(date_RFC1036);
@@ -113,7 +113,7 @@ public void testDateFormatting() throws ParseException {
final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT";
final Date date = HttpHeaderReader.readDate(date_RFC1123);
- final String date_formatted = HttpDateFormat.getPreferredDateFormat().format(date);
+ final String date_formatted = HttpDateFormat.getPreferredDateFormatter().format(date);
assertEquals(date_RFC1123, date_formatted);
}