You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2021/10/24 14:50:28 UTC

[httpcomponents-client] branch master updated: Replaced SimpleDateFormat and Calendar with Java 8 Time APIs; removed thread-local from DateUtils

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new dfc2086  Replaced SimpleDateFormat and Calendar with Java 8 Time APIs; removed thread-local from DateUtils
dfc2086 is described below

commit dfc2086d2416af22eb97fc1a601aaadb486a5378
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Oct 23 19:48:00 2021 +0200

    Replaced SimpleDateFormat and Calendar with Java 8 Time APIs; removed thread-local from DateUtils
---
 .../hc/client5/http/cache/HttpCacheEntry.java      |   2 +-
 .../client5/http/impl/cache/AsyncCachingExec.java  |   3 +-
 .../http/impl/cache/CacheInvalidatorBase.java      |   3 +-
 .../http/impl/cache/CacheUpdateHandler.java        |   3 +-
 .../http/impl/cache/CacheValidityPolicy.java       |   9 +-
 .../impl/cache/CachedHttpResponseGenerator.java    |   3 +-
 .../cache/CachedResponseSuitabilityChecker.java    |  16 +-
 .../hc/client5/http/impl/cache/CachingExec.java    |   3 +-
 .../client5/http/impl/cache/CachingExecBase.java   |   3 +-
 .../hc/client5/http/impl/cache/DateSupport.java    | 116 ++++++++
 .../http/impl/cache/ResponseCachingPolicy.java     |  10 +-
 .../impl/cache/ResponseProtocolCompliance.java     |   5 +-
 .../hc/client5/http/impl/cache/WarningValue.java   |   4 +-
 .../client5/http/impl/cache/TestDateSupport.java   | 100 +++++++
 .../http/impl/cache/TestResponseCachingPolicy.java | 118 ++++----
 .../client5/http/impl/cache/TestWarningValue.java  |  14 +-
 .../org/apache/hc/client5/http/fluent/Request.java |  30 +-
 .../http/impl/DefaultHttpRequestRetryStrategy.java |   6 +-
 .../http/impl/cookie/BasicExpiresHandler.java      |  31 +-
 .../http/impl/cookie/LaxExpiresHandler.java        |  55 ++--
 .../http/impl/cookie/RFC6265CookieSpecFactory.java |   5 +-
 .../http/impl/cookie/RFC6265StrictSpec.java        |   8 +-
 .../apache/hc/client5/http/utils/DateUtils.java    | 325 ++++++++++++++-------
 .../impl/TestDefaultHttpRequestRetryStrategy.java  |   7 +-
 .../impl/cookie/TestBasicCookieAttribHandlers.java |  19 +-
 .../impl/cookie/TestLaxCookieAttribHandlers.java   | 129 ++++----
 .../hc/client5/http/utils/TestDateUtils.java       | 103 +++----
 27 files changed, 715 insertions(+), 415 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
index 8d071d6..578a722 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
@@ -132,7 +132,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
      * @return the Date value of the header or null if the header is not present
      */
     private Date parseDate() {
-        return DateUtils.parseDate(this, HttpHeaders.DATE);
+        return DateUtils.toDate(DateUtils.parseStandardDate(this, HttpHeaders.DATE));
     }
 
     /**
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 70d7a47..bbbd75c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -51,7 +51,6 @@ import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.concurrent.CancellableDependency;
@@ -574,7 +573,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
                         @Override
                         public void completed(final HttpCacheEntry existingEntry) {
-                            if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
+                            if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
                                 LOG.debug("Backend already contains fresher cache entry");
                                 try {
                                     final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
index 604ed61..feaf95e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
@@ -30,7 +30,6 @@ import java.net.URI;
 
 import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.client5.http.utils.URIUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHeaders;
@@ -99,7 +98,7 @@ class CacheInvalidatorBase {
     }
 
     static boolean responseDateOlderThanEntryDate(final HttpResponse response, final HttpCacheEntry entry) {
-        return DateUtils.isBefore(response, entry, HttpHeaders.DATE);
+        return DateSupport.isBefore(response, entry, HttpHeaders.DATE);
     }
 
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java
index 2302148..7416766 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java
@@ -36,7 +36,6 @@ import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.Resource;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
-import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
@@ -132,7 +131,7 @@ class CacheUpdateHandler {
     }
 
     private Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) {
-        if (DateUtils.isAfter(entry, response, HttpHeaders.DATE)) {
+        if (DateSupport.isAfter(entry, response, HttpHeaders.DATE)) {
             return entry.getHeaders();
         }
         final HeaderGroup headerGroup = new HeaderGroup();
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java
index ef2b82c..f29159a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java
@@ -26,6 +26,7 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.time.Instant;
 import java.util.Date;
 import java.util.Iterator;
 
@@ -64,11 +65,11 @@ class CacheValidityPolicy {
             return TimeValue.ZERO_MILLISECONDS;
         }
 
-        final Date expiry = DateUtils.parseDate(entry, HeaderConstants.EXPIRES);
+        final Instant expiry = DateUtils.parseStandardDate(entry, HeaderConstants.EXPIRES);
         if (expiry == null) {
             return TimeValue.ZERO_MILLISECONDS;
         }
-        final long diff = expiry.getTime() - dateValue.getTime();
+        final long diff = expiry.toEpochMilli() - dateValue.getTime();
         return TimeValue.ofSeconds(diff / 1000);
     }
 
@@ -98,10 +99,10 @@ class CacheValidityPolicy {
     public TimeValue getHeuristicFreshnessLifetime(final HttpCacheEntry entry,
             final float coefficient, final TimeValue defaultLifetime) {
         final Date dateValue = entry.getDate();
-        final Date lastModifiedValue = DateUtils.parseDate(entry, HeaderConstants.LAST_MODIFIED);
+        final Instant lastModifiedValue = DateUtils.parseStandardDate(entry, HeaderConstants.LAST_MODIFIED);
 
         if (dateValue != null && lastModifiedValue != null) {
-            final long diff = dateValue.getTime() - lastModifiedValue.getTime();
+            final long diff = dateValue.getTime() - lastModifiedValue.toEpochMilli();
             if (diff < 0) {
                 return TimeValue.ZERO_MILLISECONDS;
             }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
index 090d7dc..fab60b6 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
@@ -26,6 +26,7 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.time.Instant;
 import java.util.Date;
 
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
@@ -105,7 +106,7 @@ class CachedHttpResponseGenerator {
         // - Date, unless its omission is required by section 14.8.1
         Header dateHeader = entry.getFirstHeader(HttpHeaders.DATE);
         if (dateHeader == null) {
-            dateHeader = new BasicHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date()));
+            dateHeader = new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
         }
         response.addHeader(dateHeader);
 
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
index 4f3efea..7e1200a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
@@ -26,6 +26,7 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.time.Instant;
 import java.util.Date;
 import java.util.Iterator;
 
@@ -278,7 +279,8 @@ class CachedResponseSuitabilityChecker {
         final boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request);
 
         final boolean etagValidatorMatches = (hasEtagValidator) && etagValidatorMatches(request, entry);
-        final boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry, now);
+        final boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry,
+                DateUtils.toInstant(now));
 
         if ((hasEtagValidator && hasLastModifiedValidator)
             && !(etagValidatorMatches && lastModifiedValidatorMatches)) {
@@ -332,16 +334,16 @@ class CachedResponseSuitabilityChecker {
      * @param now right NOW in time
      * @return  boolean Does the last modified header match
      */
-    private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
-        final Date lastModified = DateUtils.parseDate(entry, HeaderConstants.LAST_MODIFIED);
+    private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
+        final Instant lastModified = DateUtils.parseStandardDate(entry, HeaderConstants.LAST_MODIFIED);
         if (lastModified == null) {
             return false;
         }
 
         for (final Header h : request.getHeaders(HeaderConstants.IF_MODIFIED_SINCE)) {
-            final Date ifModifiedSince = DateUtils.parseDate(h.getValue());
+            final Instant ifModifiedSince = DateUtils.parseStandardDate(h.getValue());
             if (ifModifiedSince != null) {
-                if (ifModifiedSince.after(now) || lastModified.after(ifModifiedSince)) {
+                if (ifModifiedSince.isAfter(now) || lastModified.isAfter(ifModifiedSince)) {
                     return false;
                 }
             }
@@ -351,8 +353,8 @@ class CachedResponseSuitabilityChecker {
 
     private boolean hasValidDateField(final HttpRequest request, final String headerName) {
         for(final Header h : request.getHeaders(headerName)) {
-            final Date date = DateUtils.parseDate(h.getValue());
-            return date != null;
+            final Instant instant = DateUtils.parseStandardDate(h.getValue());
+            return instant != null;
         }
         return false;
     }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 3f756fe..1272401 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -47,7 +47,6 @@ import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.ContentType;
@@ -413,7 +412,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         final HttpCacheEntry cacheEntry;
         if (cacheConfig.isFreshnessCheckEnabled()) {
             final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
-            if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
+            if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
                 LOG.debug("Backend already contains fresher cache entry");
                 cacheEntry = existingEntry;
             } else {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index b7b47ec..3a7a462 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -40,7 +40,6 @@ import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.ResourceIOException;
-import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HeaderElement;
 import org.apache.hc.core5.http.HttpHeaders;
@@ -333,7 +332,7 @@ public class CachingExecBase {
         // Date header, so we can't tell if they are out of order
         // according to the origin clock; thus we can skip the
         // unconditional retry recommended in 13.2.6 of RFC 2616.
-        return DateUtils.isBefore(backendResponse, cacheEntry, HttpHeaders.DATE);
+        return DateSupport.isBefore(backendResponse, cacheEntry, HttpHeaders.DATE);
     }
 
     boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DateSupport.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DateSupport.java
new file mode 100644
index 0000000..70ac2ef
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DateSupport.java
@@ -0,0 +1,116 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import java.time.Instant;
+
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.MessageHeaders;
+
+/**
+ * HTTP cache date support utilities.
+ *
+ * @since 5.2
+ */
+@Internal
+public final class DateSupport {
+
+    /**
+     * Tests if the first message is after (newer) than second one
+     * using the given message header for comparison.
+     *
+     * @param message1 the first message
+     * @param message2 the second message
+     * @param headerName header name
+     *
+     * @return {@code true} if both messages contain a header with the given name
+     *  and the value of the header from the first message is newer that of
+     *  the second message.
+     *
+     * @since 5.0
+     */
+    public static boolean isAfter(
+            final MessageHeaders message1,
+            final MessageHeaders message2,
+            final String headerName) {
+        if (message1 != null && message2 != null) {
+            final Header dateHeader1 = message1.getFirstHeader(headerName);
+            if (dateHeader1 != null) {
+                final Header dateHeader2 = message2.getFirstHeader(headerName);
+                if (dateHeader2 != null) {
+                    final Instant date1 = DateUtils.parseStandardDate(dateHeader1.getValue());
+                    if (date1 != null) {
+                        final Instant date2 = DateUtils.parseStandardDate(dateHeader2.getValue());
+                        if (date2 != null) {
+                            return date1.isAfter(date2);
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tests if the first message is before (older) than the second one
+     * using the given message header for comparison.
+     *
+     * @param message1 the first message
+     * @param message2 the second message
+     * @param headerName header name
+     *
+     * @return {@code true} if both messages contain a header with the given name
+     *  and the value of the header from the first message is older that of
+     *  the second message.
+     *
+     * @since 5.0
+     */
+    public static boolean isBefore(
+            final MessageHeaders message1,
+            final MessageHeaders message2,
+            final String headerName) {
+        if (message1 != null && message2 != null) {
+            final Header dateHeader1 = message1.getFirstHeader(headerName);
+            if (dateHeader1 != null) {
+                final Header dateHeader2 = message2.getFirstHeader(headerName);
+                if (dateHeader2 != null) {
+                    final Instant date1 = DateUtils.parseStandardDate(dateHeader1.getValue());
+                    if (date1 != null) {
+                        final Instant date2 = DateUtils.parseStandardDate(dateHeader2.getValue());
+                        if (date2 != null) {
+                            return date1.isBefore(date2);
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
index 9408669..5a01e50 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
@@ -26,9 +26,9 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
@@ -154,7 +154,7 @@ class ResponseCachingPolicy {
             return false;
         }
 
-        final Date date = DateUtils.parseDate(response, HttpHeaders.DATE);
+        final Instant date = DateUtils.parseStandardDate(response, HttpHeaders.DATE);
         if (date == null) {
             LOG.debug("Invalid / missing Date header");
             return false;
@@ -292,12 +292,12 @@ class ResponseCachingPolicy {
         if (expiresHdr == null || dateHdr == null) {
             return false;
         }
-        final Date expires = DateUtils.parseDate(expiresHdr.getValue());
-        final Date date = DateUtils.parseDate(dateHdr.getValue());
+        final Instant expires = DateUtils.parseStandardDate(expiresHdr.getValue());
+        final Instant date = DateUtils.parseStandardDate(dateHdr.getValue());
         if (expires == null || date == null) {
             return false;
         }
-        return expires.equals(date) || expires.before(date);
+        return expires.equals(date) || expires.isBefore(date);
     }
 
     private boolean from1_0Origin(final HttpResponse response) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
index b3f4fc2..31dec99 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
@@ -27,6 +27,7 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -85,7 +86,7 @@ class ResponseProtocolCompliance {
 
     private void warningsWithNonMatchingWarnDatesAreRemoved(
             final HttpResponse response) {
-        final Date responseDate = DateUtils.parseDate(response, HttpHeaders.DATE);
+        final Instant responseDate = DateUtils.parseStandardDate(response, HttpHeaders.DATE);
         if (responseDate == null) {
             return;
         }
@@ -153,7 +154,7 @@ class ResponseProtocolCompliance {
 
     private void ensure206ContainsDateHeader(final HttpResponse response) {
         if (response.getFirstHeader(HttpHeaders.DATE) == null) {
-            response.addHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date()));
+            response.addHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
         }
 
     }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/WarningValue.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/WarningValue.java
index 905cf86..ed76872 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/WarningValue.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/WarningValue.java
@@ -265,7 +265,7 @@ class WarningValue {
             parseError();
         }
         offs += m.end();
-        warnDate = DateUtils.parseDate(src.substring(curr+1,offs-1));
+        warnDate = DateUtils.toDate(DateUtils.parseStandardDate(src.substring(curr+1,offs-1)));
     }
 
     /*
@@ -359,7 +359,7 @@ class WarningValue {
     public String toString() {
         if (warnDate != null) {
             return String.format("%d %s %s \"%s\"", warnCode,
-                    warnAgent, warnText, DateUtils.formatDate(warnDate));
+                    warnAgent, warnText, DateUtils.formatStandardDate(DateUtils.toInstant(warnDate)));
         } else {
             return String.format("%d %s %s", warnCode, warnAgent, warnText);
         }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDateSupport.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDateSupport.java
new file mode 100644
index 0000000..de3edbe
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDateSupport.java
@@ -0,0 +1,100 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.cache;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
+
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.HeaderGroup;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link DateSupport}.
+ */
+public class TestDateSupport {
+
+    private static Instant createInstant(final int year, final Month month, final int day) {
+        return LocalDate.of(year, month, day).atStartOfDay(ZoneId.of("GMT")).toInstant();
+    }
+
+    @Test
+    public void testIsBefore() throws Exception {
+        final HeaderGroup message1 = new HeaderGroup();
+        final HeaderGroup message2 = new HeaderGroup();
+        MatcherAssert.assertThat(DateSupport.isBefore(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+        MatcherAssert.assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
+        MatcherAssert.assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+    }
+
+    @Test
+    public void testIsAfter() throws Exception {
+        final HeaderGroup message1 = new HeaderGroup();
+        final HeaderGroup message2 = new HeaderGroup();
+        MatcherAssert.assertThat(DateSupport.isAfter(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+        MatcherAssert.assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
+        MatcherAssert.assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
+
+        message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
+        message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
+        MatcherAssert.assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
+    }
+
+}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
index a1f0030..b374d28 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
@@ -56,6 +56,10 @@ public class TestResponseCachingPolicy {
     private Date tenSecondsFromNow;
     private Date sixSecondsAgo;
 
+    static String formatDate(final Date date) {
+        return DateUtils.formatStandardDate(DateUtils.toInstant(date));
+    }
+
     @Before
     public void setUp() throws Exception {
         now = new Date();
@@ -65,7 +69,7 @@ public class TestResponseCachingPolicy {
         policy = new ResponseCachingPolicy(0, true, false, false);
         request = new BasicHttpRequest("GET","/");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "");
-        response.setHeader("Date", DateUtils.formatDate(new Date()));
+        response.setHeader("Date", formatDate(new Date()));
         response.setHeader("Content-Length", "0");
     }
 
@@ -205,7 +209,7 @@ public class TestResponseCachingPolicy {
     public void testNon206WithExplicitExpiresIsCacheable() {
         final int status = getRandomStatus();
         response.setCode(status);
-        response.setHeader("Expires", DateUtils.formatDate(new Date()));
+        response.setHeader("Expires", formatDate(new Date()));
         Assert.assertTrue(policy.isResponseCacheable("GET", response));
     }
 
@@ -360,7 +364,7 @@ public class TestResponseCachingPolicy {
         Assert.assertTrue(policy.isResponseCacheable("GET", response));
 
         response = new BasicHttpResponse(HttpStatus.SC_OK, "");
-        response.setHeader("Date", DateUtils.formatDate(new Date()));
+        response.setHeader("Date", formatDate(new Date()));
         response.addHeader("Cache-Control", "no-transform");
         response.setHeader("Content-Length", "0");
 
@@ -375,7 +379,7 @@ public class TestResponseCachingPolicy {
         Assert.assertTrue(policy.isResponseCacheable("HEAD", response));
 
         response = new BasicHttpResponse(HttpStatus.SC_OK, "");
-        response.setHeader("Date", DateUtils.formatDate(new Date()));
+        response.setHeader("Date", formatDate(new Date()));
         response.addHeader("Cache-Control", "no-transform");
         response.setHeader("Content-Length", "0");
 
@@ -480,8 +484,8 @@ public class TestResponseCachingPolicy {
 
     @Test
     public void testResponsesWithMultipleDateHeadersAreNotCacheable() {
-        response.addHeader("Date", DateUtils.formatDate(now));
-        response.addHeader("Date", DateUtils.formatDate(sixSecondsAgo));
+        response.addHeader("Date", formatDate(now));
+        response.addHeader("Date", formatDate(sixSecondsAgo));
         Assert.assertFalse(policy.isResponseCacheable("GET", response));
     }
 
@@ -491,8 +495,8 @@ public class TestResponseCachingPolicy {
 
         request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
         response.setHeader("Cache-Control", "public");
-        response.addHeader("Date", DateUtils.formatDate(now));
-        response.addHeader("Date", DateUtils.formatDate(sixSecondsAgo));
+        response.addHeader("Date", formatDate(now));
+        response.addHeader("Date", formatDate(sixSecondsAgo));
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
 
@@ -514,8 +518,8 @@ public class TestResponseCachingPolicy {
 
     @Test
     public void testResponsesWithMultipleExpiresHeadersAreNotCacheable() {
-        response.addHeader("Expires", DateUtils.formatDate(now));
-        response.addHeader("Expires", DateUtils.formatDate(sixSecondsAgo));
+        response.addHeader("Expires", formatDate(now));
+        response.addHeader("Expires", formatDate(sixSecondsAgo));
         Assert.assertFalse(policy.isResponseCacheable("GET", response));
     }
 
@@ -525,8 +529,8 @@ public class TestResponseCachingPolicy {
 
         request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
         response.setHeader("Cache-Control", "public");
-        response.addHeader("Expires", DateUtils.formatDate(now));
-        response.addHeader("Expires", DateUtils.formatDate(sixSecondsAgo));
+        response.addHeader("Expires", formatDate(now));
+        response.addHeader("Expires", formatDate(sixSecondsAgo));
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
 
@@ -587,8 +591,8 @@ public class TestResponseCachingPolicy {
     @Test
     public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -596,8 +600,8 @@ public class TestResponseCachingPolicy {
     public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheable() {
         policy = new ResponseCachingPolicy(0, true, false, false);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -605,8 +609,8 @@ public class TestResponseCachingPolicy {
     public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
         policy = new ResponseCachingPolicy(0, true, true, false);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -614,8 +618,8 @@ public class TestResponseCachingPolicy {
     public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
         policy = new ResponseCachingPolicy(0, true, true, false);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -658,8 +662,8 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -669,8 +673,8 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
@@ -680,8 +684,8 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
 
@@ -691,8 +695,8 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
 
@@ -713,8 +717,8 @@ public class TestResponseCachingPolicy {
     @Test
     public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.0 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -723,8 +727,8 @@ public class TestResponseCachingPolicy {
     public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() {
         policy = new ResponseCachingPolicy(0, true, false, false);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.0 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -733,8 +737,8 @@ public class TestResponseCachingPolicy {
     public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
         policy = new ResponseCachingPolicy(0, true, true, true);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.0 someproxy");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -743,8 +747,8 @@ public class TestResponseCachingPolicy {
     public void headsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
         policy = new ResponseCachingPolicy(0, true, true, true);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.0 someproxy");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -752,8 +756,8 @@ public class TestResponseCachingPolicy {
     @Test
     public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "HTTP/1.0 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -762,8 +766,8 @@ public class TestResponseCachingPolicy {
     public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() {
         policy = new ResponseCachingPolicy(0, true, false, false);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "HTTP/1.0 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -772,8 +776,8 @@ public class TestResponseCachingPolicy {
     public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
         policy = new ResponseCachingPolicy(0, true, true, true);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "HTTP/1.0 someproxy");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -782,8 +786,8 @@ public class TestResponseCachingPolicy {
     public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
         policy = new ResponseCachingPolicy(0, true, true, true);
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "HTTP/1.0 someproxy");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -793,8 +797,8 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.1 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -805,24 +809,24 @@ public class TestResponseCachingPolicy {
         request = new BasicHttpRequest("HEAD", "/foo?s=bar");
         response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
         response.setVersion(HttpVersion.HTTP_1_0);
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
         response.setHeader("Via", "1.1 someproxy");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
 
     @Test
     public void notCacheableIfExpiresEqualsDateAndNoCacheControl() {
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(now));
         response.removeHeaders("Cache-Control");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
 
     @Test
     public void notCacheableIfExpiresPrecedesDateAndNoCacheControl() {
-        response.setHeader("Date", DateUtils.formatDate(now));
-        response.setHeader("Expires", DateUtils.formatDate(sixSecondsAgo));
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(sixSecondsAgo));
         response.removeHeaders("Cache-Control");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -830,7 +834,7 @@ public class TestResponseCachingPolicy {
     @Test
     public void test302WithExplicitCachingHeaders() {
         response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-        response.setHeader("Date", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
         response.setHeader("Cache-Control","max-age=300");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -839,7 +843,7 @@ public class TestResponseCachingPolicy {
     public void test303WithExplicitCachingHeadersUnderDefaultBehavior() {
         // RFC 2616 says: 303 should not be cached
         response.setCode(HttpStatus.SC_SEE_OTHER);
-        response.setHeader("Date", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
         response.setHeader("Cache-Control","max-age=300");
         Assert.assertFalse(policy.isResponseCacheable(request, response));
     }
@@ -850,7 +854,7 @@ public class TestResponseCachingPolicy {
         // response headers
         policy = new ResponseCachingPolicy(0, true, false, true);
         response.setCode(HttpStatus.SC_SEE_OTHER);
-        response.setHeader("Date", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
         response.setHeader("Cache-Control","max-age=300");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -858,7 +862,7 @@ public class TestResponseCachingPolicy {
     @Test
     public void test307WithExplicitCachingHeaders() {
         response.setCode(HttpStatus.SC_TEMPORARY_REDIRECT);
-        response.setHeader("Date", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
         response.setHeader("Cache-Control","max-age=300");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
@@ -866,7 +870,7 @@ public class TestResponseCachingPolicy {
     @Test
     public void otherStatusCodesAreCacheableWithExplicitCachingHeaders() {
         response.setCode(HttpStatus.SC_NOT_FOUND);
-        response.setHeader("Date", DateUtils.formatDate(now));
+        response.setHeader("Date", formatDate(now));
         response.setHeader("Cache-Control","max-age=300");
         Assert.assertTrue(policy.isResponseCacheable(request, response));
     }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestWarningValue.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestWarningValue.java
index d53c055..de85b0e 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestWarningValue.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestWarningValue.java
@@ -26,7 +26,7 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
-import java.util.Date;
+import java.time.Instant;
 
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
@@ -203,8 +203,8 @@ public class TestWarningValue {
         Assert.assertEquals(110, impl.getWarnCode());
         Assert.assertEquals("fred", impl.getWarnAgent());
         Assert.assertEquals("\"stale\"", impl.getWarnText());
-        final Date target = DateUtils.parseDate("Sun Nov  6 08:49:37 1994");
-        Assert.assertEquals(target, impl.getWarnDate());
+        final Instant target = DateUtils.parseStandardDate("Sun Nov  6 08:49:37 1994");
+        Assert.assertEquals(target, DateUtils.toInstant(impl.getWarnDate()));
     }
 
     @Test
@@ -213,8 +213,8 @@ public class TestWarningValue {
         Assert.assertEquals(110, impl.getWarnCode());
         Assert.assertEquals("fred", impl.getWarnAgent());
         Assert.assertEquals("\"stale\"", impl.getWarnText());
-        final Date target = DateUtils.parseDate("Sunday, 06-Nov-94 08:49:37 GMT");
-        Assert.assertEquals(target, impl.getWarnDate());
+        final Instant target = DateUtils.parseStandardDate("Sunday, 06-Nov-94 08:49:37 GMT");
+        Assert.assertEquals(target, DateUtils.toInstant(impl.getWarnDate()));
     }
 
     @Test
@@ -223,8 +223,8 @@ public class TestWarningValue {
         Assert.assertEquals(110, impl.getWarnCode());
         Assert.assertEquals("fred", impl.getWarnAgent());
         Assert.assertEquals("\"stale\"", impl.getWarnText());
-        final Date target = DateUtils.parseDate("Sun, 06 Nov 1994 08:49:37 GMT");
-        Assert.assertEquals(target, impl.getWarnDate());
+        final Instant target = DateUtils.parseStandardDate("Sun, 06 Nov 1994 08:49:37 GMT");
+        Assert.assertEquals(target, DateUtils.toInstant(impl.getWarnDate()));
     }
 
 }
diff --git a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java
index 52fa673..ea4b094 100644
--- a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java
+++ b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java
@@ -33,7 +33,6 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -46,6 +45,7 @@ import org.apache.hc.client5.http.config.Configurable;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.ContentType;
@@ -70,8 +70,20 @@ import org.apache.hc.core5.util.Timeout;
  */
 public class Request {
 
+    /**
+     * @deprecated This attribute is no longer supported as a part of the public API.
+     */
+    @Deprecated
     public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+    /**
+     * @deprecated This attribute is no longer supported as a part of the public API.
+     */
+    @Deprecated
     public static final Locale DATE_LOCALE = Locale.US;
+    /**
+     * @deprecated This attribute is no longer supported as a part of the public API.
+     */
+    @Deprecated
     public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("GMT");
 
     private final ClassicHttpRequest request;
@@ -80,8 +92,6 @@ public class Request {
     private Timeout responseTimeout;
     private HttpHost proxy;
 
-    private SimpleDateFormat dateFormatter;
-
     public static Request create(final Method method, final URI uri) {
       return new Request(new HttpUriRequestBase(method.name(), uri));
   }
@@ -246,30 +256,22 @@ public class Request {
         return this;
     }
 
-    private SimpleDateFormat getDateFormat() {
-        if (this.dateFormatter == null) {
-            this.dateFormatter = new SimpleDateFormat(DATE_FORMAT, DATE_LOCALE);
-            this.dateFormatter.setTimeZone(TIME_ZONE);
-        }
-        return this.dateFormatter;
-    }
-
     ClassicHttpRequest getRequest() {
       return request;
     }
 
     public Request setDate(final Date date) {
-        this.request.setHeader(HttpHeader.DATE, getDateFormat().format(date));
+        this.request.setHeader(HttpHeader.DATE, DateUtils.formatStandardDate(DateUtils.toInstant(date)));
         return this;
     }
 
     public Request setIfModifiedSince(final Date date) {
-        this.request.setHeader(HttpHeader.IF_MODIFIED_SINCE, getDateFormat().format(date));
+        this.request.setHeader(HttpHeader.IF_MODIFIED_SINCE, DateUtils.formatStandardDate(DateUtils.toInstant(date)));
         return this;
     }
 
     public Request setIfUnmodifiedSince(final Date date) {
-        this.request.setHeader(HttpHeader.IF_UNMODIFIED_SINCE, getDateFormat().format(date));
+        this.request.setHeader(HttpHeader.IF_UNMODIFIED_SINCE, DateUtils.formatStandardDate(DateUtils.toInstant(date)));
         return this;
     }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java
index b1be581..a50182b 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java
@@ -32,9 +32,9 @@ import java.io.InterruptedIOException;
 import java.net.ConnectException;
 import java.net.NoRouteToHostException;
 import java.net.UnknownHostException;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -214,10 +214,10 @@ public class DefaultHttpRequestRetryStrategy implements HttpRequestRetryStrategy
             try {
                 retryAfter = TimeValue.ofSeconds(Long.parseLong(value));
             } catch (final NumberFormatException ignore) {
-                final Date retryAfterDate = DateUtils.parseDate(value);
+                final Instant retryAfterDate = DateUtils.parseStandardDate(value);
                 if (retryAfterDate != null) {
                     retryAfter =
-                            TimeValue.ofMilliseconds(retryAfterDate.getTime() - System.currentTimeMillis());
+                            TimeValue.ofMilliseconds(retryAfterDate.toEpochMilli() - System.currentTimeMillis());
                 }
             }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/BasicExpiresHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/BasicExpiresHandler.java
index 25d3385..a3c2028 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/BasicExpiresHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/BasicExpiresHandler.java
@@ -26,7 +26,9 @@
  */
 package org.apache.hc.client5.http.impl.cookie;
 
-import java.util.Date;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
 
 import org.apache.hc.client5.http.cookie.CommonCookieAttributeHandler;
 import org.apache.hc.client5.http.cookie.Cookie;
@@ -46,11 +48,30 @@ import org.apache.hc.core5.util.Args;
 public class BasicExpiresHandler extends AbstractCookieAttributeHandler implements CommonCookieAttributeHandler {
 
     /** Valid date patterns */
-    private final String[] datePatterns;
+    private final DateTimeFormatter[] datePatterns;
 
+    /**
+     * @since 5.2
+     */
+    public BasicExpiresHandler(final DateTimeFormatter... datePatterns) {
+        this.datePatterns = datePatterns;
+    }
+
+    /**
+     * @deprecated Use {@link #BasicExpiresHandler(DateTimeFormatter...)}
+     */
+    @Deprecated
     public BasicExpiresHandler(final String[] datePatterns) {
         Args.notNull(datePatterns, "Array of date patterns");
-        this.datePatterns = datePatterns.clone();
+        this.datePatterns = new DateTimeFormatter[datePatterns.length];
+        for (int i = 0; i < datePatterns.length; i++) {
+            this.datePatterns[i] = new DateTimeFormatterBuilder()
+                    .parseLenient()
+                    .parseCaseInsensitive()
+                    .appendPattern(datePatterns[i])
+                    .toFormatter();
+        }
+
     }
 
     @Override
@@ -60,12 +81,12 @@ public class BasicExpiresHandler extends AbstractCookieAttributeHandler implemen
         if (value == null) {
             throw new MalformedCookieException("Missing value for 'expires' attribute");
         }
-        final Date expiry = DateUtils.parseDate(value, this.datePatterns);
+        final Instant expiry = DateUtils.parseDate(value, this.datePatterns);
         if (expiry == null) {
             throw new MalformedCookieException("Invalid 'expires' attribute: "
                     + value);
         }
-        cookie.setExpiryDate(expiry);
+        cookie.setExpiryDate(DateUtils.toDate(expiry));
     }
 
     @Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/LaxExpiresHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/LaxExpiresHandler.java
index 594a671..fcca1c3 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/LaxExpiresHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/LaxExpiresHandler.java
@@ -26,11 +26,13 @@
  */
 package org.apache.hc.client5.http.impl.cookie;
 
+import java.time.Instant;
+import java.time.Month;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.BitSet;
-import java.util.Calendar;
 import java.util.Locale;
 import java.util.Map;
-import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -39,6 +41,7 @@ import org.apache.hc.client5.http.cookie.CommonCookieAttributeHandler;
 import org.apache.hc.client5.http.cookie.Cookie;
 import org.apache.hc.client5.http.cookie.MalformedCookieException;
 import org.apache.hc.client5.http.cookie.SetCookie;
+import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.util.Args;
@@ -54,8 +57,6 @@ import org.apache.hc.core5.util.Tokenizer;
 @Contract(threading = ThreadingBehavior.STATELESS)
 public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements CommonCookieAttributeHandler {
 
-    static final TimeZone UTC = TimeZone.getTimeZone("UTC");
-
     private static final BitSet DELIMS;
     static {
         final BitSet bitSet = new BitSet();
@@ -74,21 +75,21 @@ public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements
         }
         DELIMS = bitSet;
     }
-    private static final Map<String, Integer> MONTHS;
+    private static final Map<String, Month> MONTHS;
     static {
-        final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(12);
-        map.put("jan", Calendar.JANUARY);
-        map.put("feb", Calendar.FEBRUARY);
-        map.put("mar", Calendar.MARCH);
-        map.put("apr", Calendar.APRIL);
-        map.put("may", Calendar.MAY);
-        map.put("jun", Calendar.JUNE);
-        map.put("jul", Calendar.JULY);
-        map.put("aug", Calendar.AUGUST);
-        map.put("sep", Calendar.SEPTEMBER);
-        map.put("oct", Calendar.OCTOBER);
-        map.put("nov", Calendar.NOVEMBER);
-        map.put("dec", Calendar.DECEMBER);
+        final ConcurrentHashMap<String, Month> map = new ConcurrentHashMap<>(12);
+        map.put("jan", Month.JANUARY);
+        map.put("feb", Month.FEBRUARY);
+        map.put("mar", Month.MARCH);
+        map.put("apr", Month.APRIL);
+        map.put("may", Month.MAY);
+        map.put("jun", Month.JUNE);
+        map.put("jul", Month.JULY);
+        map.put("aug", Month.AUGUST);
+        map.put("sep", Month.SEPTEMBER);
+        map.put("oct", Month.OCTOBER);
+        map.put("nov", Month.NOVEMBER);
+        map.put("dec", Month.DECEMBER);
         MONTHS = map;
     }
 
@@ -114,7 +115,8 @@ public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements
         final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, value.length());
         final StringBuilder content = new StringBuilder();
 
-        int second = 0, minute = 0, hour = 0, day = 0, month = 0, year = 0;
+        int second = 0, minute = 0, hour = 0, day = 0, year = 0;
+        Month month = Month.JANUARY;
         boolean foundTime = false, foundDayOfMonth = false, foundMonth = false, foundYear = false;
         try {
             while (!cursor.atEnd()) {
@@ -147,7 +149,7 @@ public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements
                     final Matcher matcher = MONTH_PATTERN.matcher(content);
                     if (matcher.matches()) {
                         foundMonth = true;
-                        month = MONTHS.get(matcher.group(1).toLowerCase(Locale.ROOT)).intValue();
+                        month = MONTHS.get(matcher.group(1).toLowerCase(Locale.ROOT));
                         continue;
                     }
                 }
@@ -176,16 +178,9 @@ public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements
             throw new MalformedCookieException("Invalid 'expires' attribute: " + value);
         }
 
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(UTC);
-        c.setTimeInMillis(0L);
-        c.set(Calendar.SECOND, second);
-        c.set(Calendar.MINUTE, minute);
-        c.set(Calendar.HOUR_OF_DAY, hour);
-        c.set(Calendar.DAY_OF_MONTH, day);
-        c.set(Calendar.MONTH, month);
-        c.set(Calendar.YEAR, year);
-        cookie.setExpiryDate(c.getTime());
+        final Instant expiryDate = ZonedDateTime.of(year, month.getValue(), day, hour, minute, second, 0,
+                ZoneId.of("UTC")).toInstant();
+        cookie.setExpiryDate(DateUtils.toDate(expiryDate));
     }
 
     private void skipDelims(final CharSequence buf, final Tokenizer.Cursor cursor) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpecFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpecFactory.java
index e213aaa..93575c2 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpecFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpecFactory.java
@@ -33,6 +33,7 @@ import org.apache.hc.client5.http.cookie.CookieSpec;
 import org.apache.hc.client5.http.cookie.CookieSpecFactory;
 import org.apache.hc.client5.http.cookie.MalformedCookieException;
 import org.apache.hc.client5.http.psl.PublicSuffixMatcher;
+import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.protocol.HttpContext;
@@ -88,7 +89,7 @@ public class RFC6265CookieSpecFactory implements CookieSpecFactory {
                                     new BasicMaxAgeHandler(),
                                     new BasicSecureHandler(),
                                     new BasicHttpOnlyHandler(),
-                                    new BasicExpiresHandler(RFC6265StrictSpec.DATE_PATTERNS));
+                                    new BasicExpiresHandler(DateUtils.STANDARD_PATTERNS));
                             break;
                         case IE_MEDIUM_SECURITY:
                             this.cookieSpec = new RFC6265LaxSpec(
@@ -105,7 +106,7 @@ public class RFC6265CookieSpecFactory implements CookieSpecFactory {
                                     new BasicMaxAgeHandler(),
                                     new BasicSecureHandler(),
                                     new BasicHttpOnlyHandler(),
-                                    new BasicExpiresHandler(RFC6265StrictSpec.DATE_PATTERNS));
+                                    new BasicExpiresHandler(DateUtils.STANDARD_PATTERNS));
                             break;
                         default:
                             this.cookieSpec = new RFC6265LaxSpec(
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265StrictSpec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265StrictSpec.java
index 262fb95..b155ba9 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265StrictSpec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265StrictSpec.java
@@ -42,19 +42,13 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
 @Contract(threading = ThreadingBehavior.SAFE)
 public class RFC6265StrictSpec extends RFC6265CookieSpecBase {
 
-    final static String[] DATE_PATTERNS = {
-        DateUtils.PATTERN_RFC1123,
-        DateUtils.PATTERN_RFC1036,
-        DateUtils.PATTERN_ASCTIME
-    };
-
     public RFC6265StrictSpec() {
         super(new BasicPathHandler(),
                 new BasicDomainHandler(),
                 new BasicMaxAgeHandler(),
                 new BasicSecureHandler(),
                 new BasicHttpOnlyHandler(),
-                new BasicExpiresHandler(DATE_PATTERNS));
+                new BasicExpiresHandler(DateUtils.STANDARD_PATTERNS));
     }
 
     RFC6265StrictSpec(final CommonCookieAttributeHandler... handlers) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DateUtils.java b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DateUtils.java
index d59c6d3..e5d747e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DateUtils.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DateUtils.java
@@ -27,14 +27,14 @@
 
 package org.apache.hc.client5.http.utils;
 
-import java.lang.ref.SoftReference;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
 import java.util.TimeZone;
 
 import org.apache.hc.core5.http.Header;
@@ -56,42 +56,202 @@ public final class DateUtils {
     public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
 
     /**
+     * Date formatter used to parse HTTP date headers in RFC 1123 format.
+     *
+     * @since 5.2
+     */
+    public static final DateTimeFormatter FORMATTER_RFC1123 = new DateTimeFormatterBuilder()
+            .parseLenient()
+            .parseCaseInsensitive()
+            .appendPattern(PATTERN_RFC1123)
+            .toFormatter();
+
+    /**
      * Date format pattern used to parse HTTP date headers in RFC 1036 format.
      */
     public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
 
     /**
+     * Date formatter used to parse HTTP date headers in RFC 1036 format.
+     *
+     * @since 5.2
+     */
+    public static final DateTimeFormatter FORMATTER_RFC1036 = new DateTimeFormatterBuilder()
+            .parseLenient()
+            .parseCaseInsensitive()
+            .appendPattern(PATTERN_RFC1036)
+            .toFormatter();
+
+    /**
      * Date format pattern used to parse HTTP date headers in ANSI C
      * {@code asctime()} format.
      */
     public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
 
-    private static final String[] DEFAULT_PATTERNS = new String[] {
-        PATTERN_RFC1123,
-        PATTERN_RFC1036,
-        PATTERN_ASCTIME
+    /**
+     * Date formatter used to parse HTTP date headers in in ANSI C {@code asctime()} format.
+     *
+     * @since 5.2
+     */
+    public static final DateTimeFormatter FORMATTER_ASCTIME = new DateTimeFormatterBuilder()
+            .parseLenient()
+            .parseCaseInsensitive()
+            .appendPattern(PATTERN_ASCTIME)
+            .toFormatter();
+
+    /**
+     * Standard date formatters: {@link #FORMATTER_RFC1123}, {@link #FORMATTER_RFC1036}, {@link #FORMATTER_ASCTIME}.
+     *
+     * @since 5.2
+     */
+    public static final DateTimeFormatter[] STANDARD_PATTERNS = new DateTimeFormatter[] {
+            FORMATTER_RFC1123,
+            FORMATTER_RFC1036,
+            FORMATTER_ASCTIME
     };
 
-    private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
+    static final ZoneId GMT_ID = ZoneId.of("GMT");
 
-    public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+    /**
+     * @since 5.2
+     */
+    public static Date toDate(final Instant instant) {
+        return instant != null ? new Date(instant.toEpochMilli()) : null;
+    }
+
+    /**
+     * @since 5.2
+     */
+    public static Instant toInstant(final Date date) {
+        return date != null ? Instant.ofEpochMilli(date.getTime()) : null;
+    }
+
+    /**
+     * @since 5.2
+     */
+    public static LocalDateTime toUTC(final Instant instant) {
+        return instant != null ? instant.atZone(ZoneOffset.UTC).toLocalDateTime() : null;
+    }
+
+    /**
+     * @since 5.2
+     */
+    public static LocalDateTime toUTC(final Date date) {
+        return toUTC(toInstant(date));
+    }
+
+    /**
+     * Parses the date value using the given date/time formats.
+     *
+     * @param dateValue the instant value to parse
+     * @param dateFormatters the date/time formats to use
+     *
+     * @return the parsed instant or null if input could not be parsed
+     *
+     * @since 5.2
+     */
+    public static Instant parseDate(final String dateValue, final DateTimeFormatter... dateFormatters) {
+        Args.notNull(dateValue, "Date value");
+        String v = dateValue;
+        // trim single quotes around date if present
+        // see issue #5279
+        if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
+            v = v.substring (1, v.length() - 1);
+        }
+
+        for (final DateTimeFormatter dateFormatter : dateFormatters) {
+            try {
+                return Instant.from(dateFormatter.parse(v));
+            } catch (final DateTimeParseException ignore) {
+            }
+        }
+        return null;
+    }
 
-    static {
-        final Calendar calendar = Calendar.getInstance();
-        calendar.setTimeZone(GMT);
-        calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
-        calendar.set(Calendar.MILLISECOND, 0);
-        DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
+    /**
+     * Parses the instant value using the standard date/time formats ({@link #PATTERN_RFC1123},
+     * {@link #PATTERN_RFC1036}, {@link #PATTERN_ASCTIME}).
+     *
+     * @param dateValue the instant value to parse
+     * @param dateFormatters the date/time formats to use
+     *
+     * @return the parsed instant or null if input could not be parsed
+     *
+     * @since 5.2
+     */
+    public static Instant parseStandardDate(final String dateValue) {
+        return parseDate(dateValue, STANDARD_PATTERNS);
     }
 
     /**
+     * Parses an instant value from a header with the given name.
+     *
+     * @param headers message headers
+     * @param headerName header name
+     *
+     * @return the parsed instant or null if input could not be parsed
+     *
+     * @since 5.2
+     */
+    public static Instant parseStandardDate(final MessageHeaders headers, final String headerName) {
+        if (headers == null) {
+            return null;
+        }
+        final Header header = headers.getFirstHeader(headerName);
+        if (header == null) {
+            return null;
+        }
+        return parseStandardDate(header.getValue());
+    }
+
+    /**
+     * Formats the given instant according to the RFC 1123 pattern.
+     *
+     * @param instant Instant to format.
+     * @return An RFC 1123 formatted instant string.
+     *
+     * @see #PATTERN_RFC1123
+     *
+     * @since 5.2
+     */
+    public static String formatStandardDate(final Instant instant) {
+        return formatDate(instant, FORMATTER_RFC1123);
+    }
+
+    /**
+     * Formats the given date according to the specified pattern.
+     *
+     * @param instant Instant to format.
+     * @param dateTimeFormatter The pattern to use for formatting the instant.
+     * @return A formatted instant string.
+     *
+     * @throws IllegalArgumentException If the given date pattern is invalid.
+     *
+     * @since 5.2
+     */
+    public static String formatDate(final Instant instant, final DateTimeFormatter dateTimeFormatter) {
+        Args.notNull(instant, "Instant");
+        Args.notNull(dateTimeFormatter, "DateTimeFormatter");
+        return dateTimeFormatter.format(instant.atZone(GMT_ID));
+    }
+
+    /**
+     * @deprecated This attribute is no longer supported as a part of the public API.
+     */
+    @Deprecated
+    public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+    /**
      * Parses a date value.  The formats used for parsing the date value are retrieved from
      * the default http params.
      *
      * @param dateValue the date value to parse
      *
      * @return the parsed date or null if input could not be parsed
+     *
+     * @deprecated Use {@link #parseStandardDate(String)}
      */
+    @Deprecated
     public static Date parseDate(final String dateValue) {
         return parseDate(dateValue, null, null);
     }
@@ -105,16 +265,12 @@ public final class DateUtils {
      * @return the parsed date or null if input could not be parsed
      *
      * @since 5.0
+     *
+     * @deprecated Use {@link #parseStandardDate(MessageHeaders, String)}
      */
+    @Deprecated
     public static Date parseDate(final MessageHeaders headers, final String headerName) {
-        if (headers == null) {
-            return null;
-        }
-        final Header header = headers.getFirstHeader(headerName);
-        if (header == null) {
-            return null;
-        }
-        return parseDate(header.getValue(), null, null);
+        return toDate(parseStandardDate(headers, headerName));
     }
 
     /**
@@ -130,7 +286,10 @@ public final class DateUtils {
      *  the second message.
      *
      * @since 5.0
+     *
+     * @deprecated This method is no longer supported as a part of the public API.
      */
+    @Deprecated
     public static boolean isAfter(
             final MessageHeaders message1,
             final MessageHeaders message2,
@@ -166,7 +325,10 @@ public final class DateUtils {
      *  the second message.
      *
      * @since 5.0
+     *
+     * @deprecated This method is no longer supported as a part of the public API.
      */
+    @Deprecated
     public static boolean isBefore(
             final MessageHeaders message1,
             final MessageHeaders message2,
@@ -190,53 +352,53 @@ public final class DateUtils {
     }
 
     /**
-     * Parses the date value using the given date formats.
+     * Parses the date value using the given date/time formats.
      *
      * @param dateValue the date value to parse
-     * @param dateFormats the date formats to use
+     * @param dateFormats the date/time formats to use
      *
      * @return the parsed date or null if input could not be parsed
+     *
+     * @deprecated Use {@link #parseDate(String, DateTimeFormatter...)}
      */
+    @Deprecated
     public static Date parseDate(final String dateValue, final String[] dateFormats) {
         return parseDate(dateValue, dateFormats, null);
     }
 
     /**
-     * Parses the date value using the given date formats.
+     * Parses the date value using the given date/time formats.
      *
      * @param dateValue the date value to parse
-     * @param dateFormats the date formats to use
+     * @param dateFormats the date/time formats to use
      * @param startDate During parsing, two digit years will be placed in the range
      * {@code startDate} to {@code startDate + 100 years}. This value may
      * be {@code null}. When {@code null} is given as a parameter, year
      * {@code 2000} will be used.
      *
      * @return the parsed date or null if input could not be parsed
+     *
+     * @deprecated Use {@link #parseDate(String, DateTimeFormatter...)}
      */
+    @Deprecated
     public static Date parseDate(
             final String dateValue,
             final String[] dateFormats,
             final Date startDate) {
-        Args.notNull(dateValue, "Date value");
-        final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
-        final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
-        String v = dateValue;
-        // trim single quotes around date if present
-        // see issue #5279
-        if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
-            v = v.substring (1, v.length() - 1);
-        }
-
-        for (final String dateFormat : localDateFormats) {
-            final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
-            dateParser.set2DigitYearStart(localStartDate);
-            final ParsePosition pos = new ParsePosition(0);
-            final Date result = dateParser.parse(v, pos);
-            if (pos.getIndex() != 0) {
-                return result;
+        final DateTimeFormatter[] dateTimeFormatters;
+        if (dateFormats != null) {
+            dateTimeFormatters = new DateTimeFormatter[dateFormats.length];
+            for (int i = 0; i < dateFormats.length; i++) {
+                dateTimeFormatters[i] = new DateTimeFormatterBuilder()
+                        .parseLenient()
+                        .parseCaseInsensitive()
+                        .appendPattern(dateFormats[i])
+                        .toFormatter();
             }
+        } else {
+            dateTimeFormatters = STANDARD_PATTERNS;
         }
-        return null;
+        return toDate(parseDate(dateValue, dateTimeFormatters));
     }
 
     /**
@@ -246,15 +408,16 @@ public final class DateUtils {
      * @return An RFC 1123 formatted date string.
      *
      * @see #PATTERN_RFC1123
+     *
+     * @deprecated Use {@link #formatStandardDate(Instant)}
      */
+    @Deprecated
     public static String formatDate(final Date date) {
-        return formatDate(date, PATTERN_RFC1123);
+        return formatStandardDate(toInstant(date));
     }
 
     /**
-     * Formats the given date according to the specified pattern.  The pattern
-     * must conform to that used by the {@link SimpleDateFormat simple date
-     * format} class.
+     * Formats the given date according to the specified pattern.
      *
      * @param date The date to format.
      * @param pattern The pattern to use for formatting the date.
@@ -262,72 +425,28 @@ public final class DateUtils {
      *
      * @throws IllegalArgumentException If the given date pattern is invalid.
      *
-     * @see SimpleDateFormat
+     * @deprecated Use {@link #formatDate(Instant, DateTimeFormatter)}
      */
+    @Deprecated
     public static String formatDate(final Date date, final String pattern) {
         Args.notNull(date, "Date");
         Args.notNull(pattern, "Pattern");
-        final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
-        return formatter.format(date);
+        return DateTimeFormatter.ofPattern(pattern).format(toInstant(date).atZone(GMT_ID));
     }
 
     /**
      * Clears thread-local variable containing {@link java.text.DateFormat} cache.
      *
      * @since 4.3
+     *
+     * @deprecated Noop method. Do not use.
      */
+    @Deprecated
     public static void clearThreadLocal() {
-        DateFormatHolder.clearThreadLocal();
     }
 
     /** This class should not be instantiated. */
     private DateUtils() {
     }
 
-    /**
-     * A factory for {@link SimpleDateFormat}s. The instances are stored in a
-     * threadlocal way because SimpleDateFormat is not threadsafe as noted in
-     * {@link SimpleDateFormat its javadoc}.
-     *
-     */
-    final static class DateFormatHolder {
-
-        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<>();
-
-        /**
-         * creates a {@link SimpleDateFormat} for the requested format string.
-         *
-         * @param pattern
-         *            a non-{@code null} format String according to
-         *            {@link SimpleDateFormat}. The format is not checked against
-         *            {@code null} since all paths go through
-         *            {@link DateUtils}.
-         * @return the requested format. This simple dateformat should not be used
-         *         to {@link SimpleDateFormat#applyPattern(String) apply} to a
-         *         different pattern.
-         */
-        public static SimpleDateFormat formatFor(final String pattern) {
-            final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
-            Map<String, SimpleDateFormat> formats = ref == null ? null : ref.get();
-            if (formats == null) {
-                formats = new HashMap<>();
-                THREADLOCAL_FORMATS.set(new SoftReference<>(formats));
-            }
-
-            SimpleDateFormat format = formats.get(pattern);
-            if (format == null) {
-                format = new SimpleDateFormat(pattern, Locale.US);
-                format.setTimeZone(TimeZone.getTimeZone("GMT"));
-                formats.put(pattern, format);
-            }
-
-            return format;
-        }
-
-        public static void clearThreadLocal() {
-            THREADLOCAL_FORMATS.remove();
-        }
-
-    }
-
 }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java
index fc8c575..9f0531f 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java
@@ -31,7 +31,8 @@ import java.net.ConnectException;
 import java.net.NoRouteToHostException;
 import java.net.SocketTimeoutException;
 import java.net.UnknownHostException;
-import java.util.Date;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 
 import javax.net.ssl.SSLException;
 
@@ -85,7 +86,7 @@ public class TestDefaultHttpRequestRetryStrategy {
     public void testRetryAfterHeaderAsDate() throws Exception {
         this.retryStrategy = new DefaultHttpRequestRetryStrategy(3, TimeValue.ZERO_MILLISECONDS);
         final HttpResponse response = new BasicHttpResponse(503, "Oopsie");
-        response.setHeader(HttpHeaders.RETRY_AFTER, DateUtils.formatDate(new Date(System.currentTimeMillis() + 100000L)));
+        response.setHeader(HttpHeaders.RETRY_AFTER, DateUtils.formatStandardDate(Instant.now().plus(100, ChronoUnit.SECONDS)));
 
         Assert.assertTrue(this.retryStrategy.getRetryInterval(response, 3, null).compareTo(TimeValue.ZERO_MILLISECONDS) > 0);
     }
@@ -93,7 +94,7 @@ public class TestDefaultHttpRequestRetryStrategy {
     @Test
     public void testRetryAfterHeaderAsPastDate() throws Exception {
         final HttpResponse response = new BasicHttpResponse(503, "Oopsie");
-        response.setHeader(HttpHeaders.RETRY_AFTER, DateUtils.formatDate(new Date(System.currentTimeMillis() - 100000L)));
+        response.setHeader(HttpHeaders.RETRY_AFTER, DateUtils.formatStandardDate(Instant.now().minus(100, ChronoUnit.SECONDS)));
 
         Assert.assertEquals(TimeValue.ofMilliseconds(1234L), this.retryStrategy.getRetryInterval(response, 3, null));
     }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestBasicCookieAttribHandlers.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestBasicCookieAttribHandlers.java
index 1d9d673..f685638 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestBasicCookieAttribHandlers.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestBasicCookieAttribHandlers.java
@@ -27,11 +27,8 @@
 
 package org.apache.hc.client5.http.impl.cookie;
 
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.Arrays;
-import java.util.Date;
-import java.util.Locale;
 
 import org.apache.hc.client5.http.cookie.Cookie;
 import org.apache.hc.client5.http.cookie.CookieAttributeHandler;
@@ -327,21 +324,16 @@ public class TestBasicCookieAttribHandlers {
     @Test
     public void testBasicExpiresParse() throws Exception {
         final BasicClientCookie cookie = new BasicClientCookie("name", "value");
-        final CookieAttributeHandler h = new BasicExpiresHandler(new String[] {DateUtils.PATTERN_RFC1123});
+        final CookieAttributeHandler h = new BasicExpiresHandler(DateUtils.FORMATTER_RFC1123);
 
-        final DateFormat dateformat = new SimpleDateFormat(DateUtils.PATTERN_RFC1123, Locale.US);
-        dateformat.setTimeZone(DateUtils.GMT);
-
-        final Date now = new Date();
-
-        h.parse(cookie, dateformat.format(now));
+        h.parse(cookie, DateUtils.formatStandardDate(Instant.now()));
         Assert.assertNotNull(cookie.getExpiryDate());
     }
 
     @Test
     public void testBasicExpiresParseInvalid() throws Exception {
         final BasicClientCookie cookie = new BasicClientCookie("name", "value");
-        final CookieAttributeHandler h = new BasicExpiresHandler(new String[] {DateUtils.PATTERN_RFC1123});
+        final CookieAttributeHandler h = new BasicExpiresHandler(DateUtils.FORMATTER_RFC1123);
         Assert.assertThrows(MalformedCookieException.class, () ->
                 h.parse(cookie, "garbage"));
         Assert.assertThrows(MalformedCookieException.class, () ->
@@ -351,8 +343,7 @@ public class TestBasicCookieAttribHandlers {
     @SuppressWarnings("unused")
     @Test
     public void testBasicExpiresInvalidInput() throws Exception {
-        Assert.assertThrows(NullPointerException.class, () -> new BasicExpiresHandler(null));
-        final CookieAttributeHandler h = new BasicExpiresHandler(new String[] {DateUtils.PATTERN_RFC1123});
+        final CookieAttributeHandler h = new BasicExpiresHandler(DateUtils.FORMATTER_RFC1123);
         Assert.assertThrows(NullPointerException.class, () -> h.parse(null, null));
     }
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestLaxCookieAttribHandlers.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestLaxCookieAttribHandlers.java
index 71378fc..d86f14b 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestLaxCookieAttribHandlers.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestLaxCookieAttribHandlers.java
@@ -27,11 +27,12 @@
 
 package org.apache.hc.client5.http.impl.cookie;
 
-import java.util.Calendar;
-import java.util.Date;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoField;
 
 import org.apache.hc.client5.http.cookie.CookieAttributeHandler;
 import org.apache.hc.client5.http.cookie.MalformedCookieException;
+import org.apache.hc.client5.http.utils.DateUtils;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -97,18 +98,16 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "1:0:12 8-jan-2012");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(2012, c.get(Calendar.YEAR));
-        Assert.assertEquals(Calendar.JANUARY, c.get(Calendar.MONTH));
-        Assert.assertEquals(8, c.get(Calendar.DAY_OF_MONTH));
-        Assert.assertEquals(1, c.get(Calendar.HOUR_OF_DAY));
-        Assert.assertEquals(0, c.get(Calendar.MINUTE));
-        Assert.assertEquals(12, c.get(Calendar.SECOND));
-        Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
+
+        Assert.assertEquals(2012, expiryDate.get(ChronoField.YEAR));
+        Assert.assertEquals(1, expiryDate.get(ChronoField.MONTH_OF_YEAR));
+        Assert.assertEquals(8, expiryDate.get(ChronoField.DAY_OF_MONTH));
+        Assert.assertEquals(1, expiryDate.get(ChronoField.HOUR_OF_DAY));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MINUTE_OF_HOUR));
+        Assert.assertEquals(12, expiryDate.get(ChronoField.SECOND_OF_MINUTE));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MILLI_OF_SECOND));
     }
 
     @Test
@@ -157,18 +156,16 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "1:59:00blah; 8-feb-2000");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(2000, c.get(Calendar.YEAR));
-        Assert.assertEquals(Calendar.FEBRUARY, c.get(Calendar.MONTH));
-        Assert.assertEquals(8, c.get(Calendar.DAY_OF_MONTH));
-        Assert.assertEquals(1, c.get(Calendar.HOUR_OF_DAY));
-        Assert.assertEquals(59, c.get(Calendar.MINUTE));
-        Assert.assertEquals(0, c.get(Calendar.SECOND));
-        Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
+
+        Assert.assertEquals(2000, expiryDate.get(ChronoField.YEAR_OF_ERA));
+        Assert.assertEquals(2, expiryDate.get(ChronoField.MONTH_OF_YEAR));
+        Assert.assertEquals(8, expiryDate.get(ChronoField.DAY_OF_MONTH));
+        Assert.assertEquals(1, expiryDate.get(ChronoField.HOUR_OF_DAY));
+        Assert.assertEquals(59, expiryDate.get(ChronoField.MINUTE_OF_HOUR));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.SECOND_OF_MINUTE));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MILLI_OF_SECOND));
     }
 
     @Test
@@ -201,18 +198,16 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "12:00:00 8blah;mar;1880");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(1880, c.get(Calendar.YEAR));
-        Assert.assertEquals(Calendar.MARCH, c.get(Calendar.MONTH));
-        Assert.assertEquals(8, c.get(Calendar.DAY_OF_MONTH));
-        Assert.assertEquals(12, c.get(Calendar.HOUR_OF_DAY));
-        Assert.assertEquals(0, c.get(Calendar.MINUTE));
-        Assert.assertEquals(0, c.get(Calendar.SECOND));
-        Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
+
+        Assert.assertEquals(1880, expiryDate.get(ChronoField.YEAR));
+        Assert.assertEquals(3, expiryDate.get(ChronoField.MONTH_OF_YEAR));
+        Assert.assertEquals(8, expiryDate.get(ChronoField.DAY_OF_MONTH));
+        Assert.assertEquals(12, expiryDate.get(ChronoField.HOUR_OF_DAY));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MINUTE_OF_HOUR));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.SECOND_OF_MINUTE));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MILLI_OF_SECOND));
     }
 
     @Test
@@ -229,18 +224,16 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "23:59:59; 1-ApriLLLLL-2008");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(2008, c.get(Calendar.YEAR));
-        Assert.assertEquals(Calendar.APRIL, c.get(Calendar.MONTH));
-        Assert.assertEquals(1, c.get(Calendar.DAY_OF_MONTH));
-        Assert.assertEquals(23, c.get(Calendar.HOUR_OF_DAY));
-        Assert.assertEquals(59, c.get(Calendar.MINUTE));
-        Assert.assertEquals(59, c.get(Calendar.SECOND));
-        Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
+
+        Assert.assertEquals(2008, expiryDate.get(ChronoField.YEAR));
+        Assert.assertEquals(4, expiryDate.get(ChronoField.MONTH_OF_YEAR));
+        Assert.assertEquals(1, expiryDate.get(ChronoField.DAY_OF_MONTH));
+        Assert.assertEquals(23, expiryDate.get(ChronoField.HOUR_OF_DAY));
+        Assert.assertEquals(59, expiryDate.get(ChronoField.MINUTE_OF_HOUR));
+        Assert.assertEquals(59, expiryDate.get(ChronoField.SECOND_OF_MINUTE));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MILLI_OF_SECOND));
     }
 
     @Test
@@ -273,18 +266,16 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "23:59:59; 1-Apr-2008blah");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(2008, c.get(Calendar.YEAR));
-        Assert.assertEquals(Calendar.APRIL, c.get(Calendar.MONTH));
-        Assert.assertEquals(1, c.get(Calendar.DAY_OF_MONTH));
-        Assert.assertEquals(23, c.get(Calendar.HOUR_OF_DAY));
-        Assert.assertEquals(59, c.get(Calendar.MINUTE));
-        Assert.assertEquals(59, c.get(Calendar.SECOND));
-        Assert.assertEquals(0, c.get(Calendar.MILLISECOND));
+
+        Assert.assertEquals(2008, expiryDate.get(ChronoField.YEAR));
+        Assert.assertEquals(4, expiryDate.get(ChronoField.MONTH_OF_YEAR));
+        Assert.assertEquals(1, expiryDate.get(ChronoField.DAY_OF_MONTH));
+        Assert.assertEquals(23, expiryDate.get(ChronoField.HOUR_OF_DAY));
+        Assert.assertEquals(59, expiryDate.get(ChronoField.MINUTE_OF_HOUR));
+        Assert.assertEquals(59, expiryDate.get(ChronoField.SECOND_OF_MINUTE));
+        Assert.assertEquals(0, expiryDate.get(ChronoField.MILLI_OF_SECOND));
     }
 
     @Test
@@ -293,12 +284,10 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "23:59:59; 1-Apr-70");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(1970, c.get(Calendar.YEAR));
+
+        Assert.assertEquals(1970, expiryDate.get(ChronoField.YEAR));
     }
 
     @Test
@@ -307,12 +296,10 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "23:59:59; 1-Apr-99");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(1999, c.get(Calendar.YEAR));
+
+        Assert.assertEquals(1999, expiryDate.get(ChronoField.YEAR));
     }
 
     @Test
@@ -321,12 +308,10 @@ public class TestLaxCookieAttribHandlers {
         final CookieAttributeHandler h = new LaxExpiresHandler();
         h.parse(cookie, "23:59:59; 1-Apr-00");
 
-        final Date expiryDate = cookie.getExpiryDate();
+        final LocalDateTime expiryDate = DateUtils.toUTC(cookie.getExpiryDate());
         Assert.assertNotNull(expiryDate);
-        final Calendar c = Calendar.getInstance();
-        c.setTimeZone(LaxExpiresHandler.UTC);
-        c.setTime(expiryDate);
-        Assert.assertEquals(2000, c.get(Calendar.YEAR));
+
+        Assert.assertEquals(2000, expiryDate.get(ChronoField.YEAR));
     }
 
 }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestDateUtils.java b/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestDateUtils.java
index 57b62ce..0ef944d 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestDateUtils.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestDateUtils.java
@@ -27,14 +27,17 @@
 
 package org.apache.hc.client5.http.utils;
 
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.message.HeaderGroup;
-import org.hamcrest.CoreMatchers;
-import org.hamcrest.MatcherAssert;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -43,103 +46,71 @@ import org.junit.Test;
  */
 public class TestDateUtils {
 
-    private static Date createDate(final int year, final int month, final int day) {
-        final Calendar calendar = Calendar.getInstance();
-        calendar.setTimeZone(DateUtils.GMT);
-        calendar.setTimeInMillis(0);
-        calendar.set(year, month, day);
-        return calendar.getTime();
+    private static Instant createInstant(final int year, final Month month, final int day) {
+        return LocalDate.of(year, month, day).atStartOfDay(ZoneId.of("GMT")).toInstant();
+    }
+
+    private static Date createDate(final int year, final Month month, final int day) {
+        final Instant instant = createInstant(year, month, day);
+        return new Date(instant.toEpochMilli());
     }
 
     @Test
     public void testBasicDateParse() throws Exception {
-        final Date date = createDate(2005, Calendar.OCTOBER, 14);
-        final String[] formats = new String[] { DateUtils.PATTERN_RFC1123 };
-        Assert.assertEquals(date, DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT", formats, null));
-        Assert.assertEquals(date, DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT", formats));
-        Assert.assertEquals(date, DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT"));
+        final Instant instant = createInstant(2005, Month.OCTOBER, 14);
+        Assert.assertEquals(instant, DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT", DateUtils.FORMATTER_RFC1123));
+        Assert.assertEquals(instant, DateUtils.parseDate("Friday, 14 Oct 2005 00:00:00 GMT", DateUtils.FORMATTER_RFC1123));
+        Assert.assertEquals(instant, DateUtils.parseDate("Fri, 14-Oct-2005 00:00:00 GMT", DateUtils.FORMATTER_RFC1036));
+        Assert.assertEquals(instant, DateUtils.parseDate("Friday, 14-Oct-2005 00:00:00 GMT", DateUtils.FORMATTER_RFC1036));
+        Assert.assertEquals(instant.minus(2, ChronoUnit.HOURS),
+                DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 CET", DateUtils.FORMATTER_RFC1123));
+        Assert.assertEquals(instant.minus(2, ChronoUnit.HOURS),
+                DateUtils.parseDate("Fri, 14-Oct-05 00:00:00 CET", DateUtils.FORMATTER_RFC1036));
+        Assert.assertEquals(instant, DateUtils.parseStandardDate("Fri, 14 Oct 2005 00:00:00 GMT"));
     }
 
     @Test
     public void testDateParseMessage() throws Exception {
         final HeaderGroup message1 = new HeaderGroup();
         message1.setHeader(new BasicHeader(HttpHeaders.DATE, "Fri, 14 Oct 2005 00:00:00 GMT"));
-        Assert.assertEquals(createDate(2005, Calendar.OCTOBER, 14), DateUtils.parseDate(message1, HttpHeaders.DATE));
+        Assert.assertEquals(createInstant(2005, Month.OCTOBER, 14), DateUtils.parseStandardDate(message1, HttpHeaders.DATE));
 
         final HeaderGroup message2 = new HeaderGroup();
         message2.addHeader(new BasicHeader(HttpHeaders.DATE, "Fri, 14 Oct 2005 00:00:00 GMT"));
         message2.addHeader(new BasicHeader(HttpHeaders.DATE, "Fri, 21 Oct 2005 00:00:00 GMT"));
-        Assert.assertEquals(createDate(2005, Calendar.OCTOBER, 14), DateUtils.parseDate(message2, HttpHeaders.DATE));
+        Assert.assertEquals(createInstant(2005, Month.OCTOBER, 14), DateUtils.parseStandardDate(message2, HttpHeaders.DATE));
     }
 
     @Test
     public void testMalformedDate() {
-        Assert.assertNull(DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT", new String[] {}, null));
+        Assert.assertNull(DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT", new DateTimeFormatter[] {}));
     }
 
     @Test
     public void testInvalidInput() throws Exception {
-        Assert.assertThrows(NullPointerException.class, () -> DateUtils.parseDate(null, null, null));
-        Assert.assertThrows(NullPointerException.class, () -> DateUtils.formatDate(null));
-        Assert.assertThrows(NullPointerException.class, () -> DateUtils.formatDate(new Date(), null));
+        Assert.assertThrows(NullPointerException.class, () -> DateUtils.parseStandardDate(null));
+        Assert.assertThrows(NullPointerException.class, () -> DateUtils.formatStandardDate(null));
     }
 
     @Test
     public void testTwoDigitYearDateParse() throws Exception {
-        final String[] formats = new String[] { DateUtils.PATTERN_RFC1036 };
-        Assert.assertEquals(createDate(2005, Calendar.OCTOBER, 14), DateUtils.parseDate("Friday, 14-Oct-05 00:00:00 GMT", formats, null));
-        Assert.assertEquals(createDate(1905, Calendar.OCTOBER, 14), DateUtils.parseDate("Friday, 14-Oct-05 00:00:00 GMT", formats,
-                createDate(1900, Calendar.JANUARY, 0)));
+        Assert.assertEquals(createInstant(2005, Month.OCTOBER, 14),
+                DateUtils.parseDate("Friday, 14-Oct-05 00:00:00 GMT", DateUtils.FORMATTER_RFC1036));
     }
 
     @Test
     public void testParseQuotedDate() throws Exception {
-        final Date date1 = createDate(2005, Calendar.OCTOBER, 14);
-        final String[] formats = new String[] { DateUtils.PATTERN_RFC1123 };
-        final Date date2 = DateUtils.parseDate("'Fri, 14 Oct 2005 00:00:00 GMT'", formats);
-        Assert.assertEquals(date1, date2);
+        Assert.assertEquals(createInstant(2005, Month.OCTOBER, 14),
+                DateUtils.parseDate("'Fri, 14 Oct 2005 00:00:00 GMT'", DateUtils.FORMATTER_RFC1123));
     }
 
     @Test
     public void testBasicDateFormat() throws Exception {
-        final Date date = createDate(2005, Calendar.OCTOBER, 14);
-        Assert.assertEquals("Fri, 14 Oct 2005 00:00:00 GMT", DateUtils.formatDate(date));
-        Assert.assertEquals("Fri, 14 Oct 2005 00:00:00 GMT", DateUtils.formatDate(date, DateUtils.PATTERN_RFC1123));
+        final Instant instant = createInstant(2005, Month.OCTOBER, 14);
+        Assert.assertEquals("Fri, 14 Oct 2005 00:00:00 GMT", DateUtils.formatStandardDate(instant));
+        Assert.assertEquals("Fri, 14 Oct 2005 00:00:00 GMT", DateUtils.formatDate(instant, DateUtils.FORMATTER_RFC1123));
+        Assert.assertEquals("Fri, 14-Oct-05 00:00:00 GMT", DateUtils.formatDate(instant, DateUtils.FORMATTER_RFC1036));
+        Assert.assertEquals("Fri Oct 14 00:00:00 2005", DateUtils.formatDate(instant, DateUtils.FORMATTER_ASCTIME));
     }
 
-    @Test
-    public void testIsBefore() throws Exception {
-        final HeaderGroup message1 = new HeaderGroup();
-        final HeaderGroup message2 = new HeaderGroup();
-        MatcherAssert.assertThat(DateUtils.isBefore(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        MatcherAssert.assertThat(DateUtils.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
-        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
-        MatcherAssert.assertThat(DateUtils.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
-        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "Tuesday, 26-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "Wednesday, 25-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "Thursday, 27-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-    }
-
-    @Test
-    public void testIsAfter() throws Exception {
-        final HeaderGroup message1 = new HeaderGroup();
-        final HeaderGroup message2 = new HeaderGroup();
-        MatcherAssert.assertThat(DateUtils.isAfter(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        MatcherAssert.assertThat(DateUtils.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
-        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
-        MatcherAssert.assertThat(DateUtils.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
-        message2.setHeader(new BasicHeader(HttpHeaders.DATE, "Tuesday, 26-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "Thursday, 27-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
-        message1.setHeader(new BasicHeader(HttpHeaders.DATE, "Wednesday, 25-Dec-2017 00:00:00 GMT"));
-        MatcherAssert.assertThat(DateUtils.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
-    }
 }