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.RFC_1123_DATE_TIME;
21
22 import java.math.BigDecimal;
23 import java.time.Duration;
24 import java.time.Instant;
25 import java.time.ZoneOffset;
26 import java.time.ZonedDateTime;
27 import java.time.format.DateTimeFormatter;
28 import java.time.format.DateTimeFormatterBuilder;
29 import java.time.format.DateTimeParseException;
30 import software.amazon.awssdk.annotations.SdkProtectedApi;
31 import software.amazon.awssdk.annotations.ThreadSafe;
32
33 /**
34  * Utilities for parsing and formatting dates.
35  */

36 @ThreadSafe
37 @SdkProtectedApi
38 public final class DateUtils {
39
40     /**
41      * Alternate ISO 8601 format without fractional seconds.
42      */

43     static final DateTimeFormatter ALTERNATE_ISO_8601_DATE_FORMAT =
44         new DateTimeFormatterBuilder()
45             .appendPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
46             .toFormatter()
47             .withZone(UTC);
48
49     private static final int MILLI_SECOND_PRECISION = 3;
50
51     private DateUtils() {
52     }
53
54     /**
55      * Parses the specified date string as an ISO 8601 date (yyyy-MM-dd'T'HH:mm:ss.SSSZZ)
56      * and returns the {@link Instant} object.
57      *
58      * @param dateString
59      *            The date string to parse.
60      *
61      * @return The parsed Instant object.
62      */

63     public static Instant parseIso8601Date(String dateString) {
64         // For EC2 Spot Fleet.
65         if (dateString.endsWith("+0000")) {
66             dateString = dateString
67                              .substring(0, dateString.length() - 5)
68                              .concat("Z");
69         }
70
71         try {
72             return parseInstant(dateString, ISO_INSTANT);
73         } catch (DateTimeParseException e) {
74             return parseInstant(dateString, ALTERNATE_ISO_8601_DATE_FORMAT);
75         }
76     }
77
78     /**
79      * Formats the specified date as an ISO 8601 string.
80      *
81      * @param date the date to format
82      * @return the ISO-8601 string representing the specified date
83      */

84     public static String formatIso8601Date(Instant date) {
85         return ISO_INSTANT.format(date);
86     }
87
88     /**
89      * Parses the specified date string as an RFC 1123 date and returns the Date
90      * object.
91      *
92      * @param dateString
93      *            The date string to parse.
94      *
95      * @return The parsed Date object.
96      */

97     public static Instant parseRfc1123Date(String dateString) {
98         if (dateString == null) {
99             return null;
100         }
101         return parseInstant(dateString, RFC_1123_DATE_TIME);
102     }
103
104     /**
105      * Formats the specified date as an RFC 1123 string.
106      *
107      * @param instant
108      *            The instant to format.
109      *
110      * @return The RFC 1123 string representing the specified date.
111      */

112     public static String formatRfc1123Date(Instant instant) {
113         return RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, UTC));
114     }
115
116     /**
117      * Returns the number of days since epoch with respect to the given number
118      * of milliseconds since epoch.
119      */

120     public static long numberOfDaysSinceEpoch(long milliSinceEpoch) {
121         return Duration.ofMillis(milliSinceEpoch).toDays();
122     }
123
124     private static Instant parseInstant(String dateString, DateTimeFormatter formatter) {
125         return formatter.withZone(ZoneOffset.UTC).parse(dateString, Instant::from);
126     }
127
128     /**
129      * Parses the given string containing a Unix timestamp with millisecond decimal precision into an {@link Instant} object.
130      */

131     public static Instant parseUnixTimestampInstant(String dateString) throws NumberFormatException {
132         if (dateString == null) {
133             return null;
134         }
135         BigDecimal dateValue = new BigDecimal(dateString);
136         return Instant.ofEpochMilli(dateValue.scaleByPowerOfTen(MILLI_SECOND_PRECISION).longValue());
137     }
138
139     /**
140      * Parses the given string containing a Unix timestamp in epoch millis into a {@link Instant} object.
141      */

142     public static Instant parseUnixTimestampMillisInstant(String dateString) throws NumberFormatException {
143         if (dateString == null) {
144             return null;
145         }
146         return Instant.ofEpochMilli(Long.parseLong(dateString));
147     }
148
149     /**
150      * Formats the give {@link Instant} object into an Unix timestamp with millisecond decimal precision.
151      */

152     public static String formatUnixTimestampInstant(Instant instant) {
153         if (instant == null) {
154             return null;
155         }
156         BigDecimal dateValue = BigDecimal.valueOf(instant.toEpochMilli());
157         return dateValue.scaleByPowerOfTen(0 - MILLI_SECOND_PRECISION)
158                         .toPlainString();
159     }
160 }
161