From dfc2086d2416af22eb97fc1a601aaadb486a5378 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sat, 23 Oct 2021 19:48:00 +0200 Subject: [PATCH] Replaced SimpleDateFormat and Calendar with Java 8 Time APIs; removed thread-local from DateUtils --- .../hc/client5/http/cache/HttpCacheEntry.java | 2 +- .../http/impl/cache/AsyncCachingExec.java | 3 +- .../http/impl/cache/CacheInvalidatorBase.java | 3 +- .../http/impl/cache/CacheUpdateHandler.java | 3 +- .../http/impl/cache/CacheValidityPolicy.java | 9 +- .../cache/CachedHttpResponseGenerator.java | 3 +- .../CachedResponseSuitabilityChecker.java | 16 +- .../client5/http/impl/cache/CachingExec.java | 3 +- .../http/impl/cache/CachingExecBase.java | 3 +- .../client5/http/impl/cache/DateSupport.java | 116 +++++++ .../impl/cache/ResponseCachingPolicy.java | 10 +- .../cache/ResponseProtocolCompliance.java | 5 +- .../client5/http/impl/cache/WarningValue.java | 4 +- .../http/impl/cache/TestDateSupport.java | 100 ++++++ .../impl/cache/TestResponseCachingPolicy.java | 118 ++++--- .../http/impl/cache/TestWarningValue.java | 14 +- .../hc/client5/http/fluent/Request.java | 30 +- .../impl/DefaultHttpRequestRetryStrategy.java | 6 +- .../http/impl/cookie/BasicExpiresHandler.java | 31 +- .../http/impl/cookie/LaxExpiresHandler.java | 55 ++- .../impl/cookie/RFC6265CookieSpecFactory.java | 5 +- .../http/impl/cookie/RFC6265StrictSpec.java | 8 +- .../hc/client5/http/utils/DateUtils.java | 327 ++++++++++++------ .../TestDefaultHttpRequestRetryStrategy.java | 7 +- .../cookie/TestBasicCookieAttribHandlers.java | 19 +- .../cookie/TestLaxCookieAttribHandlers.java | 129 +++---- .../hc/client5/http/utils/TestDateUtils.java | 103 ++---- 27 files changed, 716 insertions(+), 416 deletions(-) create mode 100644 httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DateSupport.java create mode 100644 httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDateSupport.java 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 8d071d600..578a7227e 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 70d7a47ae..bbbd75c38 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 604ed61d8..feaf95e4c 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 2302148c7..7416766e1 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 ef2b82c3f..f29159a85 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 090d7dc10..fab60b66b 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 4f3efeade..7e1200a9b 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 3f756fe84..127240185 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 b7b47ec13..3a7a462c5 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 000000000..70ac2ef0c --- /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 + * . + * + */ +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 940866963..5a01e505c 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 b3f4fc270..31dec99bd 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 905cf86f8..ed7687296 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 000000000..de3edbefd --- /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 + * . + * + */ + +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 a1f003088..b374d28fe 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 d53c055e4..de85b0ee9 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 52fa673f5..ea4b09461 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 b1be581ca..a50182bfd 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 25d338574..a3c202805 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 594a6714e..fcca1c3f0 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 MONTHS; + private static final Map MONTHS; static { - final ConcurrentHashMap 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 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 e213aaa8f..93575c25a 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 262fb955c..b155ba993 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 d59c6d3c1..e5d747e9a 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; @@ -55,35 +55,192 @@ 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"); - - 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(); + /** + * @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; + } + + /** + * 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. @@ -91,7 +248,10 @@ public final class DateUtils { * @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>> 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> ref = THREADLOCAL_FORMATS.get(); - Map 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 fc8c575dd..9f0531fb5 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 1d9d6738a..f685638c7 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 71378fce8..d86f14bcc 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 57b62ce60..0ef944dba 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)); - } }