Skip to content

Commit 377f2a2

Browse files
authored
Use RFC 822 format for marshalling date shapes (#3452)
* Use RFC 822 format for marshalling date shapes * Explicitly set ResolverStyle and Chronology
1 parent b1f8989 commit 377f2a2

File tree

11 files changed

+138
-11
lines changed

11 files changed

+138
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "dave-fn",
4+
"type": "bugfix",
5+
"description": "Build headers with two-digit day of month to meet RFC 822 reporting requirement"
6+
}

core/aws-core/src/test/java/software/amazon/awssdk/awscore/exception/AwsServiceExceptionTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public void assertSkewed(int clientSideTimeOffset,
8787
int statusCode,
8888
Instant serverDate) {
8989
AwsServiceException exception = exception(clientSideTimeOffset, errorCode, statusCode,
90-
DateUtils.formatRfc1123Date(serverDate));
90+
DateUtils.formatRfc822Date(serverDate));
9191
assertThat(exception.isClockSkewException()).isTrue();
9292
}
9393

@@ -96,7 +96,7 @@ public void assertNotSkewed(int clientSideTimeOffset,
9696
int statusCode,
9797
Instant serverDate) {
9898
AwsServiceException exception = exception(clientSideTimeOffset, errorCode, statusCode,
99-
DateUtils.formatRfc1123Date(serverDate));
99+
DateUtils.formatRfc822Date(serverDate));
100100
assertThat(exception.isClockSkewException()).isFalse();
101101
}
102102

core/aws-core/src/test/java/software/amazon/awssdk/awscore/retry/AwsRetryPolicyTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private Consumer<RetryPolicyContext.Builder> applyErrorCode(String errorCode) {
125125

126126
private Consumer<RetryPolicyContext.Builder> applyErrorCode(String errorCode, Duration clockSkew, Instant dateHeader) {
127127
SdkHttpFullResponse response = SdkHttpFullResponse.builder()
128-
.putHeader("Date", DateUtils.formatRfc1123Date(dateHeader))
128+
.putHeader("Date", DateUtils.formatRfc822Date(dateHeader))
129129
.build();
130130

131131
AwsErrorDetails errorDetails = AwsErrorDetails.builder()

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/SimpleTypeJsonMarshaller.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public void marshall(Boolean val, StructuredJsonGenerator jsonGenerator, JsonMar
113113
jsonGenerator.writeNumber(DateUtils.formatUnixTimestampInstant(val));
114114
break;
115115
case RFC_822:
116-
jsonGenerator.writeValue(DateUtils.formatRfc1123Date(val));
116+
jsonGenerator.writeValue(DateUtils.formatRfc822Date(val));
117117
break;
118118
case ISO_8601:
119119
jsonGenerator.writeValue(DateUtils.formatIso8601Date(val));

core/protocols/protocol-core/src/main/java/software/amazon/awssdk/protocols/core/InstantToString.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public String convert(Instant val, SdkField<Instant> sdkField) {
5151
case ISO_8601:
5252
return DateUtils.formatIso8601Date(val);
5353
case RFC_822:
54-
return DateUtils.formatRfc1123Date(val);
54+
return DateUtils.formatRfc822Date(val);
5555
case UNIX_TIMESTAMP:
5656
return DateUtils.formatUnixTimestampInstant(val);
5757
default:

core/protocols/protocol-core/src/main/java/software/amazon/awssdk/protocols/core/StringToInstant.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public Instant convert(String value, SdkField<Instant> field) {
5555
case UNIX_TIMESTAMP_MILLIS:
5656
return safeParseDate(DateUtils::parseUnixTimestampMillisInstant).apply(value);
5757
case RFC_822:
58-
return DateUtils.parseRfc1123Date(value);
58+
return DateUtils.parseRfc822Date(value);
5959
default:
6060
throw SdkClientException.create("Unrecognized timestamp format - " + format);
6161
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/ClockSkew.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static Optional<Instant> getServerTime(SdkHttpResponse serviceResponse) {
7676
log.debug(() -> "Reported service date: " + serverDate);
7777

7878
try {
79-
return Optional.of(DateUtils.parseRfc1123Date(serverDate));
79+
return Optional.of(DateUtils.parseRfc822Date(serverDate));
8080
} catch (RuntimeException e) {
8181
log.warn(() -> "Unable to parse clock skew offset from response: " + serverDate, e);
8282
return Optional.empty();

core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/retry/ClockSkewAdjusterTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void badDateTranslatesToZero() {
4747

4848
private SdkHttpFullResponse responseWithDateOffset(int value, ChronoUnit unit) {
4949
return SdkHttpFullResponse.builder()
50-
.putHeader("Date", DateUtils.formatRfc1123Date(Instant.now().plus(value, unit)))
50+
.putHeader("Date", DateUtils.formatRfc822Date(Instant.now().plus(value, unit)))
5151
.build();
5252
}
5353

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/clockskew/ClockSkewAdjustmentTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private void stubForResponse(Instant serviceTime, int statusCode, String errorCo
166166
.willReturn(aResponse()
167167
.withStatus(statusCode)
168168
.withHeader("x-amzn-ErrorType", errorCode)
169-
.withHeader("Date", DateUtils.formatRfc1123Date(serviceTime))
169+
.withHeader("Date", DateUtils.formatRfc822Date(serviceTime))
170170
.withBody("{}")));
171171
}
172172

@@ -180,7 +180,7 @@ private void stubForClockSkewFailureThenSuccess(Instant serviceTime, int statusC
180180
.willReturn(aResponse()
181181
.withStatus(statusCode)
182182
.withHeader("x-amzn-ErrorType", errorCode)
183-
.withHeader("Date", DateUtils.formatRfc1123Date(serviceTime))
183+
.withHeader("Date", DateUtils.formatRfc822Date(serviceTime))
184184
.withBody("{}")));
185185

186186
stubFor(post(urlEqualTo(PATH))

utils/src/main/java/software/amazon/awssdk/utils/DateUtils.java

+44
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
import java.time.Instant;
2626
import java.time.ZoneOffset;
2727
import java.time.ZonedDateTime;
28+
import java.time.chrono.IsoChronology;
2829
import java.time.format.DateTimeFormatter;
2930
import java.time.format.DateTimeFormatterBuilder;
3031
import java.time.format.DateTimeParseException;
32+
import java.time.format.ResolverStyle;
3133
import java.util.Arrays;
3234
import java.util.List;
35+
import java.util.Locale;
3336
import software.amazon.awssdk.annotations.SdkProtectedApi;
3437
import software.amazon.awssdk.annotations.ThreadSafe;
3538

@@ -48,6 +51,20 @@ public final class DateUtils {
4851
.toFormatter()
4952
.withZone(UTC);
5053

54+
/**
55+
* RFC 822 date/time formatter.
56+
*/
57+
static final DateTimeFormatter RFC_822_DATE_TIME = new DateTimeFormatterBuilder()
58+
.parseCaseInsensitive()
59+
.parseLenient()
60+
.appendPattern("EEE, dd MMM yyyy HH:mm:ss")
61+
.appendLiteral(' ')
62+
.appendOffset("+HHMM", "GMT")
63+
.toFormatter()
64+
.withLocale(Locale.US)
65+
.withResolverStyle(ResolverStyle.SMART)
66+
.withChronology(IsoChronology.INSTANCE);
67+
5168
// ISO_INSTANT does not handle offsets in Java 12-. See https://bugs.openjdk.java.net/browse/JDK-8166138
5269
private static final List<DateTimeFormatter> ALTERNATE_ISO_8601_FORMATTERS =
5370
Arrays.asList(ISO_INSTANT, ALTERNATE_ISO_8601_DATE_FORMAT, ISO_OFFSET_DATE_TIME);
@@ -102,6 +119,33 @@ public static String formatIso8601Date(Instant date) {
102119
return ISO_INSTANT.format(date);
103120
}
104121

122+
/**
123+
* Parses the specified date string as an RFC 822 date and returns the Date object.
124+
*
125+
* @param dateString
126+
* The date string to parse.
127+
*
128+
* @return The parsed Date object.
129+
*/
130+
public static Instant parseRfc822Date(String dateString) {
131+
if (dateString == null) {
132+
return null;
133+
}
134+
return parseInstant(dateString, RFC_822_DATE_TIME);
135+
}
136+
137+
/**
138+
* Formats the specified date as an RFC 822 string.
139+
*
140+
* @param instant
141+
* The instant to format.
142+
*
143+
* @return The RFC 822 string representing the specified date.
144+
*/
145+
public static String formatRfc822Date(Instant instant) {
146+
return RFC_822_DATE_TIME.format(ZonedDateTime.ofInstant(instant, UTC));
147+
}
148+
105149
/**
106150
* Parses the specified date string as an RFC 1123 date and returns the Date
107151
* object.

utils/src/test/java/software/amazon/awssdk/utils/DateUtilsTest.java

+78-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import static java.time.ZoneOffset.UTC;
1919
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
2020
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
21+
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
22+
import static org.assertj.core.api.Assertions.assertThat;
2123
import static org.junit.Assert.assertEquals;
2224
import static org.junit.Assert.assertTrue;
2325
import static software.amazon.awssdk.utils.DateUtils.ALTERNATE_ISO_8601_DATE_FORMAT;
26+
import static software.amazon.awssdk.utils.DateUtils.RFC_822_DATE_TIME;
2427

2528
import java.text.ParseException;
2629
import java.text.SimpleDateFormat;
@@ -72,6 +75,80 @@ public void formatIso8601Date() throws ParseException {
7275
assertEquals(expectedDate, actualDate);
7376
}
7477

78+
@Test
79+
public void formatRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsFormattedString() throws ParseException {
80+
String string = DateUtils.formatRfc822Date(INSTANT);
81+
Instant parsedDateAsInstant = LONG_DATE_FORMAT.parse(string).toInstant();
82+
assertThat(parsedDateAsInstant).isEqualTo(INSTANT);
83+
}
84+
85+
@Test
86+
public void formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsFormattedString() throws ParseException {
87+
Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
88+
String string = DateUtils.formatRfc822Date(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
89+
Instant parsedDateAsInstant = LONG_DATE_FORMAT.parse(string).toInstant();
90+
assertThat(parsedDateAsInstant).isEqualTo(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
91+
}
92+
93+
@Test
94+
public void formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsStringWithZeroLeadingDayOfMonth() throws ParseException {
95+
final Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
96+
String string = DateUtils.formatRfc822Date(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
97+
String expectedString = "Wed, 07 May 2014 17:43:26 GMT";
98+
assertThat(string).isEqualTo(expectedString);
99+
}
100+
101+
@Test
102+
public void parseRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsInstantObject() throws ParseException {
103+
String formattedDate = LONG_DATE_FORMAT.format(Date.from(INSTANT));
104+
Instant parsedInstant = DateUtils.parseRfc822Date(formattedDate);
105+
assertThat(parsedInstant).isEqualTo(INSTANT);
106+
}
107+
108+
@Test
109+
public void parseRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsInstantObject() throws ParseException {
110+
final Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
111+
String formattedDate = LONG_DATE_FORMAT.format(Date.from(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH));
112+
Instant parsedInstant = DateUtils.parseRfc822Date(formattedDate);
113+
assertThat(parsedInstant).isEqualTo(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
114+
}
115+
116+
@Test
117+
public void parseRfc822Date_DateWithInvalidDayOfMonth_IsParsedWithSmartResolverStyle() {
118+
String badDateString = "Wed, 31 Apr 2014 17:43:26 GMT";
119+
String validDateString = "Wed, 30 Apr 2014 17:43:26 GMT";
120+
Instant badDateParsedInstant = DateUtils.parseRfc822Date(badDateString);
121+
Instant validDateParsedInstant = DateUtils.parseRfc1123Date(validDateString);
122+
assertThat(badDateParsedInstant).isEqualTo(validDateParsedInstant);
123+
}
124+
125+
@Test
126+
public void parseRfc822Date_DateWithInvalidDayOfMonth_MatchesRfc1123Behavior() {
127+
String dateString = "Wed, 31 Apr 2014 17:43:26 GMT";
128+
Instant parsedInstantFromRfc822Parser = DateUtils.parseRfc822Date(dateString);
129+
Instant parsedInstantFromRfc1123arser = DateUtils.parseRfc1123Date(dateString);
130+
assertThat(parsedInstantFromRfc822Parser).isEqualTo(parsedInstantFromRfc1123arser);
131+
}
132+
133+
@Test
134+
public void parseRfc822Date_DateWithDayOfMonthLessThan10th_MatchesRfc1123Behavior() {
135+
String rfc822DateString = "Wed, 02 Apr 2014 17:43:26 GMT";
136+
String rfc1123DateString = "Wed, 2 Apr 2014 17:43:26 GMT";
137+
Instant parsedInstantFromRfc822Parser = DateUtils.parseRfc822Date(rfc822DateString);
138+
Instant parsedInstantFromRfc1123arser = DateUtils.parseRfc1123Date(rfc1123DateString);
139+
assertThat(parsedInstantFromRfc822Parser).isEqualTo(parsedInstantFromRfc1123arser);
140+
}
141+
142+
@Test
143+
public void resolverStyleOfRfc822FormatterMatchesRfc1123Formatter() {
144+
assertThat(RFC_822_DATE_TIME.getResolverStyle()).isSameAs(RFC_1123_DATE_TIME.getResolverStyle());
145+
}
146+
147+
@Test
148+
public void chronologyOfRfc822FormatterMatchesRfc1123Formatter() {
149+
assertThat(RFC_822_DATE_TIME.getChronology()).isSameAs(RFC_1123_DATE_TIME.getChronology());
150+
}
151+
75152
@Test
76153
public void formatRfc1123Date() throws ParseException {
77154
String string = DateUtils.formatRfc1123Date(INSTANT);
@@ -84,7 +161,7 @@ public void formatRfc1123Date() throws ParseException {
84161
}
85162

86163
@Test
87-
public void parseRfc822Date() throws ParseException {
164+
public void parseRfc1123Date() throws ParseException {
88165
String formatted = LONG_DATE_FORMAT.format(Date.from(INSTANT));
89166
Instant expected = LONG_DATE_FORMAT.parse(formatted).toInstant();
90167
Instant actual = DateUtils.parseRfc1123Date(formatted);

0 commit comments

Comments
 (0)