xref: /aosp_15_r20/external/aws-sdk-java-v2/utils/src/test/java/software/amazon/awssdk/utils/DateUtilsTest.java (revision 8a52c7834d808308836a99fc2a6e0ed8db339086)
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.utils;
17 
18 import static java.time.ZoneOffset.UTC;
19 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
20 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;
23 import static org.assertj.core.api.Assertions.assertThatThrownBy;
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertTrue;
26 import static software.amazon.awssdk.utils.DateUtils.ALTERNATE_ISO_8601_DATE_FORMAT;
27 import static software.amazon.awssdk.utils.DateUtils.RFC_822_DATE_TIME;
28 
29 import java.text.ParseException;
30 import java.text.SimpleDateFormat;
31 import java.time.Duration;
32 import java.time.Instant;
33 import java.time.OffsetDateTime;
34 import java.time.ZoneId;
35 import java.time.ZonedDateTime;
36 import java.time.format.DateTimeFormatter;
37 import java.time.format.DateTimeParseException;
38 import java.util.Arrays;
39 import java.util.Date;
40 import java.util.Locale;
41 import java.util.TimeZone;
42 import java.util.concurrent.TimeUnit;
43 import java.util.stream.Collectors;
44 import java.util.stream.Stream;
45 import org.junit.Test;
46 
47 public class DateUtilsTest {
48     private static final boolean DEBUG = false;
49     private static final int MAX_MILLIS_YEAR = 292278994;
50     private static final SimpleDateFormat COMMON_DATE_FORMAT =
51             new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
52     private static final SimpleDateFormat LONG_DATE_FORMAT =
53             new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
54 
55     static {
TimeZone.getTimeZone(UTC)56         COMMON_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(UTC));
TimeZone.getTimeZone(UTC)57         LONG_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(UTC));
58     }
59 
60     private static final Instant INSTANT = Instant.ofEpochMilli(1400284606000L);
61 
62     @Test
tt0031561767()63     public void tt0031561767() {
64         String input = "Fri, 16 May 2014 23:56:46 GMT";
65         Instant instant = DateUtils.parseRfc1123Date(input);
66         assertEquals(input, DateUtils.formatRfc1123Date(instant));
67     }
68 
69     @Test
formatIso8601Date()70     public void formatIso8601Date() throws ParseException {
71         Date date = Date.from(INSTANT);
72         String expected = COMMON_DATE_FORMAT.format(date);
73         String actual = DateUtils.formatIso8601Date(date.toInstant());
74         assertEquals(expected, actual);
75 
76         Instant expectedDate = COMMON_DATE_FORMAT.parse(expected).toInstant();
77         Instant actualDate = DateUtils.parseIso8601Date(actual);
78         assertEquals(expectedDate, actualDate);
79     }
80 
81     @Test
formatRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsFormattedString()82     public void formatRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsFormattedString() throws ParseException {
83         String string = DateUtils.formatRfc822Date(INSTANT);
84         Instant parsedDateAsInstant = LONG_DATE_FORMAT.parse(string).toInstant();
85         assertThat(parsedDateAsInstant).isEqualTo(INSTANT);
86     }
87 
88     @Test
formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsFormattedString()89     public void formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsFormattedString() throws ParseException {
90         Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
91         String string = DateUtils.formatRfc822Date(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
92         Instant parsedDateAsInstant = LONG_DATE_FORMAT.parse(string).toInstant();
93         assertThat(parsedDateAsInstant).isEqualTo(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
94     }
95 
96     @Test
formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsStringWithZeroLeadingDayOfMonth()97     public void formatRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsStringWithZeroLeadingDayOfMonth() throws ParseException {
98         final Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
99         String string = DateUtils.formatRfc822Date(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
100         String expectedString = "Wed, 07 May 2014 17:43:26 GMT";
101         assertThat(string).isEqualTo(expectedString);
102     }
103 
104     @Test
parseRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsInstantObject()105     public void parseRfc822Date_DateWithTwoDigitDayOfMonth_ReturnsInstantObject() throws ParseException {
106         String formattedDate = LONG_DATE_FORMAT.format(Date.from(INSTANT));
107         Instant parsedInstant = DateUtils.parseRfc822Date(formattedDate);
108         assertThat(parsedInstant).isEqualTo(INSTANT);
109     }
110 
111     @Test
parseRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsInstantObject()112     public void parseRfc822Date_DateWithSingleDigitDayOfMonth_ReturnsInstantObject() throws ParseException {
113         final Instant INSTANT_SINGLE_DIGIT_DAY_OF_MONTH = Instant.ofEpochMilli(1399484606000L);;
114         String formattedDate = LONG_DATE_FORMAT.format(Date.from(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH));
115         Instant parsedInstant = DateUtils.parseRfc822Date(formattedDate);
116         assertThat(parsedInstant).isEqualTo(INSTANT_SINGLE_DIGIT_DAY_OF_MONTH);
117     }
118 
119     @Test
parseRfc822Date_DateWithInvalidDayOfMonth_IsParsedWithSmartResolverStyle()120     public void parseRfc822Date_DateWithInvalidDayOfMonth_IsParsedWithSmartResolverStyle() {
121         String badDateString = "Wed, 31 Apr 2014 17:43:26 GMT";
122         String validDateString = "Wed, 30 Apr 2014 17:43:26 GMT";
123         Instant badDateParsedInstant = DateUtils.parseRfc822Date(badDateString);
124         Instant validDateParsedInstant = DateUtils.parseRfc1123Date(validDateString);
125         assertThat(badDateParsedInstant).isEqualTo(validDateParsedInstant);
126     }
127 
128     @Test
parseRfc822Date_DateWithInvalidDayOfMonth_MatchesRfc1123Behavior()129     public void parseRfc822Date_DateWithInvalidDayOfMonth_MatchesRfc1123Behavior() {
130         String dateString = "Wed, 31 Apr 2014 17:43:26 GMT";
131         Instant parsedInstantFromRfc822Parser = DateUtils.parseRfc822Date(dateString);
132         Instant parsedInstantFromRfc1123arser = DateUtils.parseRfc1123Date(dateString);
133         assertThat(parsedInstantFromRfc822Parser).isEqualTo(parsedInstantFromRfc1123arser);
134     }
135 
136     @Test
parseRfc822Date_DateWithDayOfMonthLessThan10th_MatchesRfc1123Behavior()137     public void parseRfc822Date_DateWithDayOfMonthLessThan10th_MatchesRfc1123Behavior() {
138         String rfc822DateString = "Wed, 02 Apr 2014 17:43:26 GMT";
139         String rfc1123DateString = "Wed, 2 Apr 2014 17:43:26 GMT";
140         Instant parsedInstantFromRfc822Parser = DateUtils.parseRfc822Date(rfc822DateString);
141         Instant parsedInstantFromRfc1123arser = DateUtils.parseRfc1123Date(rfc1123DateString);
142         assertThat(parsedInstantFromRfc822Parser).isEqualTo(parsedInstantFromRfc1123arser);
143     }
144 
145     @Test
resolverStyleOfRfc822FormatterMatchesRfc1123Formatter()146     public void resolverStyleOfRfc822FormatterMatchesRfc1123Formatter() {
147         assertThat(RFC_822_DATE_TIME.getResolverStyle()).isSameAs(RFC_1123_DATE_TIME.getResolverStyle());
148     }
149 
150     @Test
chronologyOfRfc822FormatterMatchesRfc1123Formatter()151     public void chronologyOfRfc822FormatterMatchesRfc1123Formatter() {
152         assertThat(RFC_822_DATE_TIME.getChronology()).isSameAs(RFC_1123_DATE_TIME.getChronology());
153     }
154 
155     @Test
formatRfc1123Date()156     public void formatRfc1123Date() throws ParseException {
157         String string = DateUtils.formatRfc1123Date(INSTANT);
158         Instant parsedDateAsInstant = LONG_DATE_FORMAT.parse(string).toInstant();
159         assertEquals(INSTANT, parsedDateAsInstant);
160 
161         String formattedDate = LONG_DATE_FORMAT.format(Date.from(INSTANT));
162         Instant parsedInstant = DateUtils.parseRfc1123Date(formattedDate);
163         assertEquals(INSTANT, parsedInstant);
164     }
165 
166     @Test
parseRfc1123Date()167     public void parseRfc1123Date() throws ParseException {
168         String formatted = LONG_DATE_FORMAT.format(Date.from(INSTANT));
169         Instant expected = LONG_DATE_FORMAT.parse(formatted).toInstant();
170         Instant actual = DateUtils.parseRfc1123Date(formatted);
171         assertEquals(expected, actual);
172     }
173 
174     @Test
parseIso8601Date()175     public void parseIso8601Date() throws ParseException {
176         checkParsing(DateTimeFormatter.ISO_INSTANT, COMMON_DATE_FORMAT);
177     }
178 
179     @Test
parseIso8601Date_withUtcOffset()180     public void parseIso8601Date_withUtcOffset() {
181         String formatted = "2021-05-10T17:12:13-07:00";
182         Instant expected = ISO_OFFSET_DATE_TIME.parse(formatted, Instant::from);
183         Instant actual = DateUtils.parseIso8601Date(formatted);
184         assertEquals(expected, actual);
185 
186         String actualString = OffsetDateTime.ofInstant(actual, ZoneId.of("-7")).toString();
187         assertEquals(formatted, actualString);
188     }
189 
190     @Test
parseIso8601Date_usingAlternativeFormat()191     public void parseIso8601Date_usingAlternativeFormat() throws ParseException {
192         checkParsing(ALTERNATE_ISO_8601_DATE_FORMAT, COMMON_DATE_FORMAT);
193     }
194 
checkParsing(DateTimeFormatter dateTimeFormatter, SimpleDateFormat dateFormat)195     private void checkParsing(DateTimeFormatter dateTimeFormatter, SimpleDateFormat dateFormat) throws ParseException {
196         String formatted = dateFormat.format(Date.from(INSTANT));
197         String alternative = dateTimeFormatter.format(INSTANT);
198         assertEquals(formatted, alternative);
199         Instant expected = dateFormat.parse(formatted).toInstant();
200         Instant actualDate = DateUtils.parseIso8601Date(formatted);
201         assertEquals(expected, actualDate);
202     }
203 
204     @Test
alternateIso8601DateFormat()205     public void alternateIso8601DateFormat() throws ParseException {
206         String expected = COMMON_DATE_FORMAT.format(Date.from(INSTANT));
207         String actual = ALTERNATE_ISO_8601_DATE_FORMAT.format(INSTANT);
208         assertEquals(expected, actual);
209 
210         Date expectedDate = COMMON_DATE_FORMAT.parse(expected);
211         ZonedDateTime actualDateTime = ZonedDateTime.parse(actual, ALTERNATE_ISO_8601_DATE_FORMAT);
212         assertEquals(expectedDate, Date.from(actualDateTime.toInstant()));
213     }
214 
215     @Test(expected = ParseException.class)
legacyHandlingOfInvalidDate()216     public void legacyHandlingOfInvalidDate() throws ParseException {
217         final String input = "2014-03-06T14:28:58.000Z.000Z";
218         COMMON_DATE_FORMAT.parse(input);
219     }
220 
221     @Test(expected = DateTimeParseException.class)
invalidDate()222     public void invalidDate() {
223         final String input = "2014-03-06T14:28:58.000Z.000Z";
224         DateUtils.parseIso8601Date(input);
225     }
226 
227     @Test
testIssue233()228     public void testIssue233() throws ParseException {
229         // https://github.com/aws/aws-sdk-java/issues/233
230         final String edgeCase = String.valueOf(MAX_MILLIS_YEAR) + "-08-17T07:12:00Z";
231         Instant expected = COMMON_DATE_FORMAT.parse(edgeCase).toInstant();
232         if (DEBUG) {
233             System.out.println("date: " + expected);
234         }
235         String formatted = DateUtils.formatIso8601Date(expected);
236         if (DEBUG) {
237             System.out.println("formatted: " + formatted);
238         }
239         // we have '+' sign as prefix for years. See java.time.format.SignStyle.EXCEEDS_PAD
240         assertEquals(edgeCase, formatted.substring(1));
241 
242         Instant parsed = DateUtils.parseIso8601Date(formatted);
243         if (DEBUG) {
244             System.out.println("parsed: " + parsed);
245         }
246         assertEquals(expected, parsed);
247         String reformatted = ISO_INSTANT.format(parsed);
248         // we have '+' sign as prefix for years. See java.time.format.SignStyle.EXCEEDS_PAD
249         assertEquals(edgeCase, reformatted.substring(1));
250     }
251 
252     @Test
testIssue233JavaTimeLimit()253     public void testIssue233JavaTimeLimit() {
254         // https://github.com/aws/aws-sdk-java/issues/233
255         String s = ALTERNATE_ISO_8601_DATE_FORMAT.format(
256                 ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE), UTC));
257         System.out.println("s: " + s);
258 
259         Instant parsed = DateUtils.parseIso8601Date(s);
260         assertEquals(ZonedDateTime.ofInstant(parsed, UTC).getYear(), MAX_MILLIS_YEAR);
261     }
262 
263     @Test
testIssueDaysDiff()264     public void testIssueDaysDiff() throws ParseException {
265         // https://github.com/aws/aws-sdk-java/issues/233
266         COMMON_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(UTC));
267         String edgeCase = String.valueOf(MAX_MILLIS_YEAR) + "-08-17T07:12:55Z";
268         String testCase = String.valueOf(MAX_MILLIS_YEAR - 1) + "-08-17T07:12:55Z";
269         Date edgeDate = COMMON_DATE_FORMAT.parse(edgeCase);
270         Date testDate = COMMON_DATE_FORMAT.parse(testCase);
271         long diff = edgeDate.getTime() - testDate.getTime();
272         assertTrue(diff == TimeUnit.DAYS.toMillis(365));
273     }
274 
275     @Test
numberOfDaysSinceEpoch()276     public void numberOfDaysSinceEpoch() {
277         final long now = System.currentTimeMillis();
278         final long days = DateUtils.numberOfDaysSinceEpoch(now);
279         final long oneDayMilli = Duration.ofDays(1).toMillis();
280         // Could be equal at 00:00:00.
281         assertTrue(now >=  Duration.ofDays(days).toMillis());
282         assertTrue((now -  Duration.ofDays(days).toMillis()) <= oneDayMilli);
283     }
284 
285     /**
286      * Tests the Date marshalling and unmarshalling. Asserts that the value is
287      * same before and after marshalling/unmarshalling
288      */
289     @Test
testUnixTimestampRoundtrip()290     public void testUnixTimestampRoundtrip() throws Exception {
291         long[] testValues = new long[] {System.currentTimeMillis(), 1L, 0L};
292         Arrays.stream(testValues)
293               .mapToObj(Instant::ofEpochMilli)
294               .forEach(instant -> {
295                   String serverSpecificDateFormat = DateUtils.formatUnixTimestampInstant(instant);
296                   Instant parsed = DateUtils.parseUnixTimestampInstant(String.valueOf(serverSpecificDateFormat));
297                   assertEquals(instant, parsed);
298               });
299     }
300 
301     @Test
parseUnixTimestampInstant_longerThan20Char_throws()302     public void parseUnixTimestampInstant_longerThan20Char_throws() {
303         String largeNum = Stream.generate(() -> "9").limit(21).collect(Collectors.joining());
304         assertThatThrownBy(() -> DateUtils.parseUnixTimestampInstant(largeNum))
305             .isInstanceOf(RuntimeException.class)
306             .hasMessageContaining("20");
307     }
308 
309 }
310