Skip to content

Commit

Permalink
feat (core/Date parsing): Accept invalid RFC822 wihtou TZ (assuming G…
Browse files Browse the repository at this point in the history
…MT) / ISO8601 parsing error not longer throw exception: just an error log
  • Loading branch information
JPPortier committed Jan 31, 2025
1 parent 139147c commit 3ef2b84
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 27 deletions.
112 changes: 92 additions & 20 deletions core/src/main/com/sinch/sdk/core/utils/DateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
Expand All @@ -15,6 +16,9 @@ public class DateUtil {
private static final DateTimeFormatter RFC822_ZONED_FORMAT =
DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss z");

private static final DateTimeFormatter RFC822_GMT_FORMAT =
DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss").withZone(ZoneId.of("GMT"));

private static final Logger LOGGER = Logger.getLogger(DateUtil.class.getName());

private DateUtil() {}
Expand Down Expand Up @@ -49,48 +53,116 @@ public static Instant toInstant(OffsetDateTime value) {
*/
public static Instant failSafeTimeStampToInstant(String value) {

if (null == value) {
String trimmed = null == value ? "" : value.trim();

if (trimmed.isEmpty()) {
return null;
}

Instant parsed = parseISO8601(trimmed);
if (null != parsed) {
return parsed;
}

parsed = parseISO8601WithOffset(trimmed);
if (null != parsed) {
return parsed;
}

// fallback: this is not an ISO8601 compliant format: give a chance and assume it is GMT zone
parsed = parseISO8601WithoutOffset(trimmed);
if (null != parsed) {
return parsed;
}

// do not break deserialization: fallback to empty value
LOGGER.severe(String.format("Unable to parse '%s' date string", value));

return null;
}

private static Instant parseISO8601(String trimmed) {
try {
return Instant.parse(trimmed);
} catch (DateTimeParseException _unused) {
return null;
}
}

Instant timestamp;
private static Instant parseISO8601WithOffset(String trimmed) {
try {
timestamp = Instant.parse(value);
} catch (DateTimeParseException e) {
try {
timestamp = OffsetDateTime.parse(value).toInstant();
} catch (DateTimeParseException dte) {
timestamp = LocalDateTime.parse(value).toInstant(ZoneOffset.UTC);
}
return OffsetDateTime.parse(trimmed).toInstant();
} catch (DateTimeParseException _unused) {
return null;
}
}

private static Instant parseISO8601WithoutOffset(String trimmed) {
try {
return LocalDateTime.parse(trimmed).toInstant(ZoneOffset.UTC);
} catch (DateTimeParseException _unused) {
return null;
}
return timestamp;
}

/**
* Convert String to Instant
*
* <p>Consume a date time in form of RFC-822 format
*
* @param value A RFC-822 compliant string
* @param value An RFC-822 compliant string
* @return Extracted Instant value
* @since __TO_BE_DEFINED__
*/
public static Instant RFC822StringToInstant(String value) {

if (null == value) {
String trimmed = null == value ? "" : value.trim();

if (trimmed.isEmpty()) {
return null;
}

Instant parsed = parseRFC822(trimmed);
if (null != parsed) {
return parsed;
}

parsed = parseRFC822WithZone(trimmed);
if (null != parsed) {
return parsed;
}

// fallback: this is not an RFC compliant format: give a chance and assume it is GMT zone
parsed = parseRFC822WithoutZoneNorOffset(trimmed);
if (null != parsed) {
return parsed;
}

LOGGER.severe(String.format("Unable to parse '%s' date string", value));
return null;
}

private static Instant parseRFC822(String trimmed) {
try {
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(value));
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(trimmed));
} catch (DateTimeParseException _unused) {
try {
return ZonedDateTime.parse(value, RFC822_ZONED_FORMAT).toInstant();
} catch (DateTimeParseException e) {
// do not break deserialization: fallback to empty value
LOGGER.severe(String.format("Unable to parse '%s' date string: %s", value, e.getMessage()));
return null;
}
return null;
}
}

private static Instant parseRFC822WithZone(String trimmed) {
try {
return ZonedDateTime.parse(trimmed, RFC822_ZONED_FORMAT).toInstant();
} catch (DateTimeParseException _unused) {
return null;
}
}

private static Instant parseRFC822WithoutZoneNorOffset(String trimmed) {
try {
return ZonedDateTime.parse(trimmed, RFC822_GMT_FORMAT).toInstant();
} catch (DateTimeParseException _unused) {
return null;
}
}
}
21 changes: 15 additions & 6 deletions core/src/main/com/sinch/sdk/core/utils/databind/Mapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,22 @@ public Instant deserialize(JsonParser parser, DeserializationContext context)
throws IOException {

String text = parser.getText();
Instant deserialized;
try {
deserialized = DateUtil.failSafeTimeStampToInstant(text);
} catch (Exception e) {
deserialized = DateUtil.RFC822StringToInstant(text);

if (null == text) {
return null;
}
return deserialized;

String trimmed = text.trim();
if (trimmed.isEmpty()) {
return null;
}

// RFC Date are starting with character not a digit
if (Character.isDigit(text.charAt(0))) {
return DateUtil.failSafeTimeStampToInstant(text);
}

return DateUtil.RFC822StringToInstant(text);
}
}

Expand Down
32 changes: 31 additions & 1 deletion core/src/test/java/com/sinch/sdk/core/utils/DateUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ void failSafeTimeStampNullGuard() {
assertNull(instant);
}

@Test
void failSafeTimeStampFromBlankString() {
Instant instant = DateUtil.failSafeTimeStampToInstant(" ");
assertNull(instant);
}

@Test
void failSafeTimeStampFromStringWithBlanks() {
Instant instant = DateUtil.failSafeTimeStampToInstant(" 2024-05-04T10:00:00.1234 ");
assertEquals("2024-05-04T10:00:00.123400Z", instant.toString());
}

@Test
void failSafeTimeStampNoTZ() {
Instant instant = DateUtil.failSafeTimeStampToInstant("2024-05-04T10:00:00.1234");
Expand All @@ -50,12 +62,30 @@ void failSafeTimeStampFromUTC() {
assertEquals("2024-05-04T10:00:00.123400Z", instant.toString());
}

@Test
void failSafeTimeStampInvalid() {
Instant instant = DateUtil.failSafeTimeStampToInstant("2024 05 04 10 00 00 1234 ");
assertNull(instant);
}

@Test
void RFC822NullGuard() {
Instant instant = DateUtil.RFC822StringToInstant(null);
assertNull(instant);
}

@Test
void RFC822FromBlankString() {
Instant instant = DateUtil.RFC822StringToInstant(" ");
assertNull(instant);
}

@Test
void RFC822FromStringWithBlanks() {
Instant instant = DateUtil.RFC822StringToInstant(" Mon, 2 Jan 2006 15:04:05 MST ");
assertEquals("2006-01-02T22:04:05Z", instant.toString());
}

@Test
void RFC822WithUnsupportedMST() {
Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 MST");
Expand Down Expand Up @@ -89,7 +119,7 @@ void RFC822WithOffset() {
@Test
void RFC822NoOffset() {
Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05");
assertNull(instant);
assertEquals("2006-01-02T15:04:05Z", instant.toString());
}

@Test
Expand Down

0 comments on commit 3ef2b84

Please sign in to comment.