Replace ThreadLocal with ConcurrentQueue in HttpDateFormat
Introduce a common facade for SimpleDateFormat and DateTimeFormatter
Able to switch to DateTimeFormatter for a small performance boost

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
index bcd7e5f..14a008d 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2021 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
@@ -168,7 +168,7 @@
                     cookie.sameSite = NewCookie.SameSite.valueOf(value.toUpperCase());
                 }  else if (param.startsWith("expires")) {
                     try {
-                        cookie.expiry = HttpDateFormat.readDate(value + ", " + bites[++i]);
+                        cookie.expiry = HttpDateFormat.readDate(value + ", " + bites[++i].trim());
                     } catch (ParseException e) {
                         LOGGER.log(Level.FINE, LocalizationMessages.ERROR_NEWCOOKIE_EXPIRES(value), e);
                     }
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java
index 09e7553..8417fb5 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2020 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
@@ -43,7 +43,7 @@
     @Override
     public String toString(final Date header) {
         throwIllegalArgumentExceptionIfNull(header, LocalizationMessages.DATE_IS_NULL());
-        return HttpDateFormat.getPreferredDateFormat().format(header);
+        return HttpDateFormat.getPreferredDateFormatter().format(header);
     }
 
     @Override
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java
index 0f76eed..9cf2abf 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java
@@ -18,12 +18,18 @@
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.TimeZone;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
  * Helper class for HTTP specified date formats.
@@ -33,6 +39,46 @@
  */
 public final class HttpDateFormat {
 
+    private static final boolean USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER = true;
+
+    /**
+     * <p>
+     *     A minimum formatter for converting java {@link Date} and {@link LocalDateTime} to {@code String} and vice-versa.
+     * </p>
+     * <p>
+     *     Works as a facade for implementation backed by {@link SimpleDateFormat} and {@link DateTimeFormatter}.
+     * </p>
+     */
+    public static interface HttpDateFormatter {
+        /**
+         *
+         * @param date
+         * @return
+         */
+        Date toDate(String date);
+
+        /**
+         *
+         * @param date
+         * @return
+         */
+        LocalDateTime toDateTime(String date);
+        /**
+         * Formats a {@link Date} into a date-time string.
+         *
+         * @param date the time value to be formatted into a date-time string.
+         * @return the formatted date-time string.
+         */
+        String format(Date date);
+        /**
+         * Formats a {@link LocalDateTime} into a date-time string.
+         *
+         * @param dateTime the time value to be formatted into a date-time string.
+         * @return the formatted date-time string.
+         */
+        String format(LocalDateTime dateTime);
+    }
+
     private HttpDateFormat() {
     }
     /**
@@ -50,33 +96,40 @@
 
     private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT");
 
-    private static final ThreadLocal<List<SimpleDateFormat>> dateFormats = ThreadLocal.withInitial(() -> createDateFormats());
+    private static final List<HttpDateFormatter> dateFormats = createDateFormats();
+    private static final Queue<List<HttpDateFormatter>> simpleDateFormats = new ConcurrentLinkedQueue<>();
 
-    private static List<SimpleDateFormat> createDateFormats() {
-        final SimpleDateFormat[] formats = new SimpleDateFormat[]{
-            new SimpleDateFormat(RFC1123_DATE_FORMAT_PATTERN, Locale.US),
-            new SimpleDateFormat(RFC1036_DATE_FORMAT_PATTERN, Locale.US),
-            new SimpleDateFormat(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US)
+    private static List<HttpDateFormatter> createDateFormats() {
+        final HttpDateFormatter[] formats = new HttpDateFormatter[]{
+                new HttpDateFormatterFromDateTimeFormatter(
+                        DateTimeFormatter.ofPattern(RFC1123_DATE_FORMAT_PATTERN, Locale.US).withZone(GMT_TIME_ZONE.toZoneId())),
+                new HttpDateFormatterFromDateTimeFormatter(
+                        DateTimeFormatter.ofPattern(RFC1123_DATE_FORMAT_PATTERN.replace("zzz", "ZZZ"), Locale.US)
+                                .withZone(GMT_TIME_ZONE.toZoneId())),
+                new HttpDateFormatterFromDateTimeFormatter(
+                        DateTimeFormatter.ofPattern(RFC1036_DATE_FORMAT_PATTERN, Locale.US).withZone(GMT_TIME_ZONE.toZoneId())),
+                new HttpDateFormatterFromDateTimeFormatter(
+                        DateTimeFormatter.ofPattern(RFC1036_DATE_FORMAT_PATTERN.replace("zzz", "ZZZ"), Locale.US)
+                                .withZone(GMT_TIME_ZONE.toZoneId())),
+                new HttpDateFormatterFromDateTimeFormatter(
+                        DateTimeFormatter.ofPattern(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US)
+                                .withZone(GMT_TIME_ZONE.toZoneId()))
         };
-        formats[0].setTimeZone(GMT_TIME_ZONE);
-        formats[1].setTimeZone(GMT_TIME_ZONE);
-        formats[2].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}.
-     * <p>
-     * The list of date formats are scoped to the current thread and may be
-     * used without requiring to synchronize access to the instances when
-     * parsing or formatting.
-     *
-     * @return the list of data formats.
-     */
-    private static List<SimpleDateFormat> getDateFormats() {
-        return dateFormats.get();
+    private static List<HttpDateFormatter> 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));
     }
 
     /**
@@ -88,9 +141,44 @@
      *
      * @return the preferred of data format.
      */
+    public static HttpDateFormatter getPreferredDateFormatter() {
+        if (USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER) {
+            List<HttpDateFormatter> 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);
+        }
+    }
+
+    /**
+     * Get the preferred HTTP specified date format (RFC 1123).
+     * <p>
+     * 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 preferred of data format.
+     * @deprecated Use getPreferredDateFormatter instead
+     */
+    // Unused in Jersey
+    @Deprecated(forRemoval = true)
     public static SimpleDateFormat getPreferredDateFormat() {
+        List<HttpDateFormatter> 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 @@
      * @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<HttpDateFormatter> list = dateFormats;
+        return readDate(date, list);
+    }
+
+    private static Date readDateSDF(final String date) throws ParseException {
+        List<HttpDateFormatter> 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<HttpDateFormatter> 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 8615aeb..08fbf5a 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 @@
         }
         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 34f29d6..07b368e 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 @@
         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 6d77453..f6c7165 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 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 @@
         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 8cecb6b..138466e 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 @@
             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 @@
         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 @@
             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 c931809..0595890 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 @@
                 .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 @@
         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 63305db..4e1bfd3 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 @@
     @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 @@
         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);
     }