From 0b647445b393bf922c3e411f0c721428955ecac3 Mon Sep 17 00:00:00 2001
From: Amanda <83655710+arp-0984@users.noreply.github.com>
Date: Tue, 18 Jun 2024 14:27:33 -0400
Subject: [PATCH] updating FlexibleDateTimeFormatter to cover edge cases (#770)
---
.../emissary/util/FlexibleDateTimeParser.java | 124 +++++-
.../emissary/util/FlexibleDateTimeParser.cfg | 368 +++++++++++++++++-
.../util/FlexibleDateTimeParserTest.java | 272 ++++++++++++-
3 files changed, 716 insertions(+), 48 deletions(-)
diff --git a/src/main/java/emissary/util/FlexibleDateTimeParser.java b/src/main/java/emissary/util/FlexibleDateTimeParser.java
index 3886279b6e..cb8fd0761e 100644
--- a/src/main/java/emissary/util/FlexibleDateTimeParser.java
+++ b/src/main/java/emissary/util/FlexibleDateTimeParser.java
@@ -13,15 +13,18 @@
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -35,13 +38,14 @@
*
* Natty - It handled a good chunk of the formats but not all.
*/
-public class FlexibleDateTimeParser {
+public final class FlexibleDateTimeParser {
/* Logger */
private static final Logger logger = LoggerFactory.getLogger(FlexibleDateTimeParser.class);
/* Configuration Variables */
- private static final String CFG_FORMAT = "FORMAT_DATETIME";
+ private static final String CFG_FORMAT_MAIN = "FORMAT_DATETIME_MAIN";
+ private static final String CFG_FORMAT_EXTRA = "FORMAT_DATETIME_EXTRA";
private static final String CFG_TIMEZONE = "TIMEZONE";
private static final String DEFAULT_TIMEZONE = "GMT";
private static final String SPACE = " ";
@@ -50,14 +54,25 @@ public class FlexibleDateTimeParser {
/* Remove all tabs and extra spaces */
private static final Pattern REPLACE = Pattern.compile("\t+|[ ]+", Pattern.DOTALL);
- /* Remove other junk */
- private static final Pattern REMOVE = Pattern.compile("<.+?>$|=0D$", Pattern.DOTALL);
+ /*
+ * Remove other junk -- anything in an html tag, all parenthesis and quotes, and any non-word characters at the
+ * beginning or end
+ */
+ private static final Pattern REMOVE = Pattern.compile("<.+?>$|=0D$|\\(|\\)|\"|\\[|]|\\W+$|^\\W+", Pattern.DOTALL);
+ /*
+ * This is our last ditch parsing effort if we failed to parse the string - remove all extra text after the numeric time
+ * zone offset
+ */
+ private static final Pattern EXTRA_TEXT_REMOVE = Pattern.compile("(\\+\\d{4}).*$");
/* timezone - config var: TIMEZONE */
private static ZoneId timezone = ZoneId.of(DEFAULT_TIMEZONE);
- /* date time formats - vars: FORMAT_DATETIME */
- private static List dateFormats = new ArrayList<>();
+ /* date time formats - vars: FORMAT_DATETIME_MAIN */
+ private static List dateFormatsMain = new ArrayList<>();
+
+ /* Extra date time formats - list to try if our main list has failed - vars: FORMAT_DATETIME_EXTRA */
+ private static List dateFormatsExtra = new ArrayList<>();
/* init */
static {
@@ -74,13 +89,74 @@ public static ZoneId getTimezone() {
}
/**
- * Attempts to parse a string date using pre-configured patterns
+ * Attempts to parse a string date using pre-configured patterns. Default not trying the extensive date/time format list
*
* @param dateString the string to parse
* @return the parsed immutable and thread-safe zoned-date, or null if it failed to parse
*/
public static ZonedDateTime parse(final String dateString) {
- return parse(dateString, dateFormats);
+ return parse(dateString, false);
+ }
+
+ /**
+ * Attempts to parse a string date using pre-configured patterns
+ *
+ * @param dateString the string to parse
+ * @param tryExtensiveParsing True if we want to try out complete list of date/time formats False if we only want to
+ * attempt the most common date/time formats
+ * @return the parsed immutable and thread-safe zoned-date, or null if it failed to parse
+ */
+ public static ZonedDateTime parse(final String dateString, boolean tryExtensiveParsing) {
+ ZonedDateTime zdt = parsingHelper(dateString, tryExtensiveParsing);
+
+ if (zdt != null || !tryExtensiveParsing) {
+ return zdt;
+ } else {
+ // if that all failed and we want to attempt extensive parsing, attempt the last ditch efforts we can try
+ return lastDitchParsingEffort(dateString);
+ }
+ }
+
+ /**
+ * If all our formats failed to parse a date string, give it one last try to parse it. Look for a numeric offset (e.g.
+ * +0000) and remove all text afterwards. This should cover another set of cases where there is random text appended to
+ * the end of the string, as well as removing invalid non-numeric time zone offsets while still picking up the numeric
+ * offset Assumption - that tryExtensiveParsing is true - we should only get to this point if we want to try our best to
+ * parse
+ *
+ * @param date The date string to parse
+ * @return the ZonedDateTime object if removing text at the end was successful, or null otherwise
+ */
+ static ZonedDateTime lastDitchParsingEffort(final String date) {
+
+ // Attempt to remove all text after the numeric offset and try again - this should give us a valid date string
+ // to work with
+ Matcher matcher = EXTRA_TEXT_REMOVE.matcher(date);
+ if (matcher.find()) {
+ String secondChanceDate = matcher.replaceAll(matcher.group(1));
+ // if we removed text, attempt to parse again to see if we are more successful this time
+ return parsingHelper(secondChanceDate, true);
+ }
+ return null;
+ }
+
+ /**
+ * Created to help against code duplication. Calls parse with the standard set of date formats, and then if that fails,
+ * attempt the extra set of date formats if tryExtensiveParsing is set to true.
+ *
+ * @param dateString The string we are attempting to parse
+ * @param tryExtensiveParsing Whether or not to use the extensive set of date formats
+ * @return The ZonedDateTime object if our parsing was successful, or null if not
+ */
+ private static ZonedDateTime parsingHelper(final String dateString, boolean tryExtensiveParsing) {
+ ZonedDateTime zdt = parse(dateString, dateFormatsMain);
+
+ // if we got a successful parse or we don't want to attempt "extensive parsing", return here
+ if (!tryExtensiveParsing || zdt != null) {
+ return zdt;
+ }
+ zdt = parse(dateString, dateFormatsExtra);
+ return zdt;
}
/**
@@ -116,9 +192,12 @@ public static ZonedDateTime parse(final String dateString, final List configEntries) {
- List dateTimeFormats = getConfigFormats(configEntries);
- if (CollectionUtils.isNotEmpty(dateTimeFormats)) {
- dateFormats = Collections.unmodifiableList(dateTimeFormats);
- logger.debug("Created successfully. Created {} of {} formats from config", dateFormats.size(), configEntries.size());
+ private static void setupDateFormats(final List configEntriesMain, final List configEntriesExtra) {
+ List dateTimeFormatsMain = getConfigFormats(configEntriesMain);
+ if (CollectionUtils.isNotEmpty(dateTimeFormatsMain)) {
+ dateFormatsMain = Collections.unmodifiableList(dateTimeFormatsMain);
+ logger.debug("Created successfully. Created {} of {} formats from config", dateFormatsMain.size(), configEntriesMain.size());
+ } else {
+ logger.error("Could not create with configured variables");
+ throw new IllegalArgumentException("No date/time formats configured!!");
+ }
+
+ List dateTimeFormatsExtra = getConfigFormats(configEntriesExtra);
+ if (CollectionUtils.isNotEmpty(dateTimeFormatsExtra)) {
+ dateFormatsExtra = Collections.unmodifiableList(dateTimeFormatsExtra);
+ logger.debug("Created successfully. Created {} of {} formats from config", dateFormatsExtra.size(), configEntriesExtra.size());
} else {
logger.error("Could not create with configured variables");
throw new IllegalArgumentException("No date/time formats configured!!");
@@ -206,7 +295,7 @@ private static List getConfigFormats(final List
@Nullable
private static DateTimeFormatter getFormatter(ConfigEntry entry) {
try {
- return DateTimeFormatter.ofPattern(entry.getValue());
+ return new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern(entry.getValue()).toFormatter();
} catch (IllegalArgumentException e) {
// log the bad one and move on because there could be other possible patterns
logger.error("Error parsing pattern [{}]: {}", entry.getValue(), e.getLocalizedMessage());
@@ -229,6 +318,7 @@ private static String cleanDateString(final String date) {
String cleanedDateString = StringUtils.substring(date, 0, 100);
cleanedDateString = REPLACE.matcher(cleanedDateString).replaceAll(SPACE);
cleanedDateString = REMOVE.matcher(cleanedDateString).replaceAll(EMPTY);
+
return StringUtils.trimToNull(cleanedDateString);
}
diff --git a/src/main/resources/emissary/util/FlexibleDateTimeParser.cfg b/src/main/resources/emissary/util/FlexibleDateTimeParser.cfg
index 6dc658702b..ce5fb16a55 100644
--- a/src/main/resources/emissary/util/FlexibleDateTimeParser.cfg
+++ b/src/main/resources/emissary/util/FlexibleDateTimeParser.cfg
@@ -1,24 +1,350 @@
# timezone GMT or UTC or +0000 or +00:00
TIMEZONE = "GMT"
-# Formatter Types
-
-FORMAT_DATETIME = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSS][ ][z][ ][Z][X]]"
-FORMAT_DATETIME = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z]]"
-FORMAT_DATETIME = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z]"
-FORMAT_DATETIME = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][[(]z[)]]]"
-FORMAT_DATETIME = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a"
-FORMAT_DATETIME = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z]]"
-FORMAT_DATETIME = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z]]"
-FORMAT_DATETIME = "[E[,][ ]]MMM d H:mm[:ss][ z] yyyy"
-FORMAT_DATETIME = "M/d/yy[ ]K:mm[:ss][ ]a"
-FORMAT_DATETIME = "M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z]"
-FORMAT_DATETIME = "[HHmm]dd[-][.][/]MM[-][.][/]yyyy"
-FORMAT_DATETIME = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z]]"
-FORMAT_DATETIME = "yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z]]"
-FORMAT_DATETIME = "yyyyMMddHHmmss"
-FORMAT_DATETIME = "yyyyMMdd"
-FORMAT_DATETIME = "yyyyDDDHHmmss"
-FORMAT_DATETIME = "yyyyDDDHHmm"
-FORMAT_DATETIME = "yyyyDDD"
-FORMAT_DATETIME = "yyyy-DDD"
+# What we're dong here is that we have a base set of patterns "FORMAT_DATETIME_MAIN" that cover most of the cases.
+# The "FORMAT_DATETIME_EXTRA" patterns cover edge cases, since the dates given to the DateTimeFormatter must exactly
+# match the provided patterns. The idea is to try all of the main patterns first, and if they all fail, attempt the extra
+# patterns.
+# I tried to keep this as organized as possible - each pattern containing offsets is repeated 7 times to cover the most
+# likely combinations of the following:
+# z = short non-location Format (PDT)
+# Z = ISO basic format with hours, minutes (-0800)
+# X = ISO basic format with hours (-08)
+
+# Additionally, each pattern containing days of the week (DOW) and the month are repeated 4 times to cover combinations
+# of the short and long forms of the days of the week
+# E = DOW short form (Mon)
+# EEEE = DOW long form (Monday)
+# MMM = Month short form (Jan)
+# MMMM = Month long form (January)
+
+# I attempted to have longer patterns that contained more optional offsets instead of separate patterns with more combinations,
+# but this became too complicated and I encountered issues with some strings not being parsed correctly
+
+# yyyyMMddTHHmmssSSSX
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSSSSSSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSSSSSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSSSSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSSSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_MAIN = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SSS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]SS][ ][z][ ][Z][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy-M-d[['T'][ ][/]H[:][/]m[:s][[.]S][ ][z][ ][Z][X]]"
+
+
+# EdMMMyyHmmssZ
+# DOW day month 2-digit year timestamp offsets
+FORMAT_DATETIME_MAIN = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[,][ ]yy[ H:mm[:ss][ ][X][ ][z][ ][Z]]"
+
+
+# EdMMMyyyyKmmssaZ
+# DOW day month year AM/PM timestamp offset
+FORMAT_DATETIME_MAIN = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMM yyyy K:mm:ss a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMM yyyy K:mm:ss a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d MMMM yyyy K:mm:ss a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d MMMM yyyy K:mm:ss a[ ][X][ ][z][ ][Z]"
+
+
+# EdMMMyyyyHmmssZz
+# DOW day month year timestamp offset
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_MAIN = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]d[ ]MMMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+
+# EMMMdyyyyHmmssz
+# DOW month day year timestamp offset
+FORMAT_DATETIME_MAIN = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]]"
+
+
+# EMMMdyyyyKmma
+# DOW month day year AM/PM timestamp offset
+FORMAT_DATETIME_MAIN = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] yyyy[,] K:mm[:ss] a[ ][X][ ][z][ ][Z]"
+
+
+# EddMMMyyyyHmmssZ
+# DOW day-month-year timestamp offset
+FORMAT_DATETIME_MAIN = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][X][ ][z][ ][Z]]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][X][ ][z][ ][Z]]"
+
+
+# EMMMdHHmmsszzzyyyy
+# Year is after the timestamp
+# DOW month day timestamp offset year offset
+FORMAT_DATETIME_MAIN = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMM d[,] H:mm[:ss][ z] yyyy[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[EEEE[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][X][ ][z][ ][Z]"
+
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "[E[,][ ]]MMMM d[,] H:mm[:ss][ z] yyyy[ ][X][ ][z][ ][Z]"
+
+
+# MdyyKmma
+FORMAT_DATETIME_MAIN = "M/d/yy[ ]K:mm[:ss][ ]a"
+
+
+# MdyyHmmssaz
+FORMAT_DATETIME_MAIN = "M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "M/d/yy[ ]H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]"
+
+
+# MdyyyyKmma
+FORMAT_DATETIME_MAIN = "M/d/yyyy[ ]K:mm[:ss][ ]a"
+
+
+# MdyyyyHmmssaz
+FORMAT_DATETIME_MAIN = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "M/d/yyyy[ ]H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]"
+
+
+# dMyyyKmmssa
+FORMAT_DATETIME_MAIN = "d.M.yyyy[ ]K:mm[:ss][ ]a"
+
+
+# dMyyyHmmssaz
+FORMAT_DATETIME_MAIN = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "d.M.yyyy[ ]H:mm[:ss][ ][a][ ][X][ ][z][ ][Z]"
+
+
+# HHmmddMMyyyy
+FORMAT_DATETIME_MAIN = "[HHmm]dd[-][.][/]MM[-][.][/]yyyy"
+
+# yyyyMMddHHmmssS
+FORMAT_DATETIME_MAIN = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z][ ][z]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][Z][ ][z][ ][Z]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][Z][ ][X]"
+FORMAT_DATETIME_EXTRA = "yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][X][ ][z][ ][Z]"
+
+# yyyy_MM_ddHHmmssS
+FORMAT_DATETIME_MAIN = "yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z][ ][z]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][Z][ ][z][ ][Z]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][Z][ ][X]]"
+FORMAT_DATETIME_EXTRA = "yyyy:MM:dd[ H:m[:ss[.S]][ ][X][ ][z][ ][Z]]"
+
+
+# yyyyMMddHHmmss
+FORMAT_DATETIME_MAIN = "yyyyMMddHHmmss"
+
+
+# yyyyMMdd
+FORMAT_DATETIME_MAIN = "yyyyMMdd"
+
+
+# yyyyDDDHHmmss
+FORMAT_DATETIME_MAIN = "yyyyDDDHHmmss"
+
+
+# yyyyDDDHHmm
+FORMAT_DATETIME_MAIN = "yyyyDDDHHmm"
+
+
+# yyyyDDD
+FORMAT_DATETIME_MAIN = "yyyyDDD"
+
+
+# yyyy_DDD
+FORMAT_DATETIME_MAIN = "yyyy-DDD"
diff --git a/src/test/java/emissary/util/FlexibleDateTimeParserTest.java b/src/test/java/emissary/util/FlexibleDateTimeParserTest.java
index 0638ecb78b..5383433f2d 100644
--- a/src/test/java/emissary/util/FlexibleDateTimeParserTest.java
+++ b/src/test/java/emissary/util/FlexibleDateTimeParserTest.java
@@ -49,6 +49,43 @@ private static void test(@Nullable String date, long expected, String msg) {
Assertions.assertEquals(expected, unknownParse == null ? 0L : unknownParse.toEpochSecond(), "Error on: " + msg);
}
+ /**
+ * Test parsing date strings with tryExtensiveParsing set to true
+ *
+ * @param date the string representation of a date
+ * @param expected the expected parsed and formatted date
+ * @param msg the error message to display if the test fails
+ */
+ private static void testExtensiveParsing(@Nullable String date, long expected, String msg) {
+ ZonedDateTime unknownParse = FlexibleDateTimeParser.parse(date, true);
+ Assertions.assertEquals(expected, unknownParse == null ? 0L : unknownParse.toEpochSecond(), "Error on: " + msg);
+ }
+
+
+ /**
+ * Helps assist in testing all the possible offset patterns that we want to attempt to parse for a given date
+ *
+ * @param dateString the string representation of a date
+ * @param expected the expected parsed and formatted date
+ */
+ private void testAllOffsets(String dateString, long expected) {
+ String[] offsets = {"EST-0500 -0500", // zZZ
+ "EST EST-0500", // zzZ
+ "EST-0500 EST", // zZz
+ "EST-0500 -05", // zZX
+ "-0500 EST-0500", // ZzZ
+ "-0500 -05", // ZX
+ "-05", // X
+ "-05 EST", // Xz
+ "-05 -0500", // XZ
+ "-05 (EST-0500)"}; // XzZ
+
+ for (String offset : offsets) {
+ String dateAndOffset = dateString + " " + offset;
+ testExtensiveParsing(dateAndOffset, expected, "Did not parse this string correctly: " + dateAndOffset);
+ }
+ }
+
/**
* Test the date string against the expected output
*
@@ -75,7 +112,7 @@ private static void test(String date, long expected, DateTimeFormatter formatter
*/
@Test
void parseOffsetWhenThereIsAThreeLetterTimeZone() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][[(]z[)]]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]");
// without offset we expect the default ZoneId
test("Mon, 4 Jan 2016 13:20:30 EST", EXPECTED_FULL, pattern);
@@ -96,6 +133,59 @@ void parseOffsetWhenThereIsAThreeLetterTimeZone() {
test("Mon, 4 Jan 2016 18:20:30 +0000 CST", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 18:20:30 +0000 ACT", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 18:20:30 +0000 BST", EXPECTED_FULL, pattern);
+
+ // test full set of short offsets to make sure DateTimeFormatter can handle them
+ // list taken from https://www.iana.org/time-zones
+ test("Mon, 4 Jan 2016 07:20:30 -1100 SST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 08:20:30 -1000 HST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 09:20:30 -0900 HDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 09:20:30 -0900 AKST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 10:20:30 -0800 AKDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 10:20:30 -0800 PST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 11:20:30 -0700 PDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 11:20:30 -0700 MST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 12:20:30 -0600 MDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 12:20:30 -0600 CST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 13:20:30 -0500 CDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 13:20:30 -0500 EST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 13:20:30 -0500 CST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 14:20:30 -0400 CDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 14:20:30 -0400 AST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 14:50:30 -0330 NST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 15:20:30 -0300 ADT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 15:50:30 -0230 NDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 18:20:30 +0000 GMT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 19:20:30 +0100 BST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 18:20:30 +0000 WET", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 19:20:30 +0100 WEST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 19:20:30 +0100 CET", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 19:20:30 +0100 WAT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 19:20:30 +0100 IST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 20:20:30 +0200 IST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 20:20:30 +0200 CEST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 20:20:30 +0200 CAT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 20:20:30 +0200 EET", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 20:20:30 +0200 SAST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 21:20:30 +0300 EEST", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 21:20:30 +0300 IDT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 21:20:30 +0300 EAT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 21:20:30 +0300 MSK", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 23:20:30 +0500 PKT", EXPECTED_FULL, pattern);
+ test("Mon, 4 Jan 2016 23:50:30 +0530 IST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 01:20:30 +0700 WIB", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 02:20:30 +0800 AWST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 02:20:30 +0800 CST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 02:20:30 +0800 HKT", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 02:20:30 +0800 PT", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 02:20:30 +0800 WITA", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 03:20:30 +0900 JST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 03:20:30 +0900 KST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 03:20:30 +0900 WIT", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 03:50:30 +0930 ACST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 04:50:30 +1030 ACDT", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 04:20:30 +1000 AEST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 04:20:30 +1000 ChST", EXPECTED_FULL, pattern);
+ test("Tue, 5 Jan 2016 05:20:30 +1100 AEDT", EXPECTED_FULL, pattern);
}
/**
@@ -145,9 +235,21 @@ void parse_yyyyMMddTHHmmssSSSX() {
test("2016-1-4", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_yyyyMMddTHHmmssSSSX_Extra() {
+ testExtensiveParsing("2016-01-04T18:20:30", EXPECTED_FULL, "0 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.0", EXPECTED_FULL, "1 digit for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.00", EXPECTED_FULL, "2 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.0000", EXPECTED_FULL, "4 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.00000", EXPECTED_FULL, "5 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.000000", EXPECTED_FULL, "6 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.0000000", EXPECTED_FULL, "7 digits for fraction of a second");
+ testExtensiveParsing("2016-01-04T18:20:30.00000000", EXPECTED_FULL, "8 digits for fraction of a second");
+ }
+
@Test
void parse_EdMMMyyHmmssZ() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[,][ ]yy[ H:mm[:ss][ ][z][ ][Z][ ][Z]]");
test("Mon, 4 Jan 16 18:20:30 +0000", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 16 18:20:30+0000", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 16 18:20:30 GMT+0000", EXPECTED_FULL, pattern);
@@ -167,10 +269,18 @@ void parse_EdMMMyyHmmssZ() {
test("4Jan16", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_EdMMMyyHmmssZ_Extra() {
+ testAllOffsets("Mon, 4 Jan 16 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 Jan 16 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 January 16 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Mon, 4 January 16 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_EdMMMyyyyKmmssaZ() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d MMM yyyy K:mm:ss a[ ][z][ ][Z][ ][Z]");
test("Mon, 4 Jan 2016 06:20:30 PM +0000", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 06:20:30 PM GMT+0000", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 06:20:30 PM GMT", EXPECTED_FULL, pattern);
@@ -183,9 +293,17 @@ void parse_EdMMMyyyyKmmssaZ() {
test("4 Jan 2016 06:20:30 PM +0000", EXPECTED_FULL, pattern);
}
+ @Test
+ void parse_EdMMMyyyyKmmssaZ_Extra() {
+ testAllOffsets("Mon, 4 Jan 2016 01:20:30 PM", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 Jan 2016 01:20:30 PM", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 January 2016 01:20:30 PM", EXPECTED_FULL);
+ testAllOffsets("Mon, 4 January 2016 01:20:30 PM", EXPECTED_FULL);
+ }
+
@Test
void parse_EdMMMyyyyHmmssZz() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][(z)]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]d[ ]MMM[.][,][ ]yyyy[ H:mm[:ss][ ][a][ ][z][ ][Z][ ][z]]");
test("Mon, 4 Jan 2016 18:20:30 +0000 (Europe/Dublin)", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 18:20:30 +0000 (Zulu)", EXPECTED_FULL, pattern);
test("Mon, 4 Jan 2016 18:20:30 +0000 (UTC)", EXPECTED_FULL, pattern);
@@ -224,9 +342,17 @@ void parse_EdMMMyyyyHmmssZz() {
test("4Jan2016", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_EdMMMyyyyHmmssZz_Extra() {
+ testAllOffsets("Mon, 4 Jan 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 Jan 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, 4 January 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Mon, 4 January 2016 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_EMMMdyyyyKmma() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d[,] yyyy[,] K:mm[:ss] a[ ][z][ ][Z][ ][Z]");
test("Mon, Jan 4, 2016 06:20 PM", EXPECTED_NO_SECS, pattern);
test("Mon, Jan 4, 2016 6:20 PM", EXPECTED_NO_SECS, pattern);
test("Mon, Jan 04, 2016 06:20 PM", EXPECTED_NO_SECS, pattern);
@@ -235,9 +361,17 @@ void parse_EMMMdyyyyKmma() {
test("Mon,Jan 04 2016 06:20 PM", EXPECTED_NO_SECS, pattern);
}
+ @Test
+ void parse_EMMMdyyyyKmma_Extra() {
+ testAllOffsets("Mon, Jan 4, 2016 01:20 PM", EXPECTED_NO_SECS);
+ testAllOffsets("Monday, Jan 4, 2016 01:20 PM", EXPECTED_NO_SECS);
+ testAllOffsets("Monday, January 4, 2016 01:20 PM", EXPECTED_NO_SECS);
+ testAllOffsets("Mon, January 4, 2016 01:20 PM", EXPECTED_NO_SECS);
+ }
+
@Test
void parse_EMMMdyyyyHmmssz() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d[,] yyyy[[,] H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]]");
test("Mon, Jan 4, 2016 18:20:30 UTC", EXPECTED_FULL, pattern);
test("Mon, Jan 04, 2016 18:20:30 UTC", EXPECTED_FULL, pattern);
test("Mon, Jan 4, 2016 13:20:30 -0500", EXPECTED_FULL, pattern);
@@ -266,6 +400,14 @@ void parse_EMMMdyyyyHmmssz() {
test("Jan 4, 2016", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_EMMMdyyyyHmmssz_Extra() {
+ testAllOffsets("Mon, Jan 04, 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, Jan 04, 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday, January 04, 2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Mon, January 04, 2016 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_EddMMMyyyyHmmssZ() {
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]dd-MMM-yyyy[ H:mm:ss[ ][z][ ][Z]]");
@@ -286,9 +428,15 @@ void parse_EddMMMyyyyHmmssZ() {
test("04-Jan-2016", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_EddMMMyyyyHmmssZ_Extra() {
+ testAllOffsets("Mon 04-Jan-2016 13:20:30", EXPECTED_FULL);
+ testAllOffsets("Monday 04-Jan-2016 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_EMMMdHHmmsszzzyyyy() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d H:mm[:ss][ z] yyyy");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[E[,][ ]]MMM d H:mm[:ss][ z] yyyy[ ][z][ ][Z][ ][Z]");
test("Mon Jan 04 18:20:30 GMT 2016", EXPECTED_FULL, pattern);
test("Mon Jan 04 13:20:30 EST 2016", EXPECTED_FULL, pattern);
test("Mon Jan 04 13:20 EST 2016", EXPECTED_NO_SECS, pattern);
@@ -304,6 +452,14 @@ void parse_EMMMdHHmmsszzzyyyy() {
test("Jan 04 18:20:30 2016", EXPECTED_FULL, pattern);
}
+ @Test
+ void parse_EMMMdHHmmsszzzyyyy_Extra() {
+ testAllOffsets("Mon Jan 04 13:20:30 EST 2016", EXPECTED_FULL);
+ testAllOffsets("Monday Jan 04 13:20:30 EST 2016", EXPECTED_FULL);
+ testAllOffsets("Monday January 04 13:20:30 EST 2016", EXPECTED_FULL);
+ testAllOffsets("Mon January 04 13:20:30 EST 2016", EXPECTED_FULL);
+ }
+
@Test
void parse_MdyyKmma() {
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("M/d/yy[ ]K:mm[:ss][ ]a");
@@ -319,7 +475,7 @@ void parse_MdyyKmma() {
@Test
void parse_MdyyHmmssaz() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("M/d/yy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]");
test("01/04/16 18:20:30 GMT", EXPECTED_FULL, pattern);
test("1/4/16 18:20:30 GMT", EXPECTED_FULL, pattern);
test("01/04/16 18:20:30 PM +0000", EXPECTED_FULL, pattern);
@@ -342,6 +498,75 @@ void parse_MdyyHmmssaz() {
test("01/04/1618:20", EXPECTED_NO_SECS, pattern);
}
+ @Test
+ void parse_MdyyHmmssaz_Extra() {
+ testAllOffsets("01/04/16 13:20:30", EXPECTED_FULL);
+ }
+
+ @Test
+ void parse_MdyyyyKmma() {
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("M/d/yyyy[ ]K:mm[:ss][ ]a");
+ test("01/04/2016 06:20 PM", EXPECTED_NO_SECS, pattern);
+ test("1/4/2016 6:20 PM", EXPECTED_NO_SECS, pattern);
+ test("01/04/2016 06:20:30 PM", EXPECTED_FULL, pattern);
+ test("1/4/2016 06:20:30 PM", EXPECTED_FULL, pattern);
+ test("01/04/2016 00:20 AM", EXPECTED_NO_HR_SEC, pattern);
+ }
+
+ @Test
+ void parse_MdyyyyHmmssaz() {
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("M/d/yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]");
+ test("01/04/2016 18:20:30 GMT", EXPECTED_FULL, pattern);
+ test("1/4/2016 18:20:30 GMT", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20:30 PM +0000", EXPECTED_FULL, pattern);
+ test("1/4/2016 18:20:30 PM +0000", EXPECTED_FULL, pattern);
+ test("1/4/2016 8:20:30 AM -1000", EXPECTED_FULL, pattern);
+ test("1/4/2016 8:20:30 -1000", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20:30 PM GMT", EXPECTED_FULL, pattern);
+ test("1/4/2016 18:20:30 PM GMT", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20:30 PM GMT+0000", EXPECTED_FULL, pattern);
+ test("1/4/2016 18:20:30 PM GMT+0000", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20:30 PM", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20:30", EXPECTED_FULL, pattern);
+ test("01/04/2016 18:20", EXPECTED_NO_SECS, pattern);
+ test("1/4/2016 18:20", EXPECTED_NO_SECS, pattern);
+ }
+
+ @Test
+ void parse_MdyyyyHmmssaz_Extra() {
+ testAllOffsets("01/04/2016 13:20:30", EXPECTED_FULL);
+ }
+
+ @Test
+ void parse_dMyyyKmmssa() {
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("d.M.yyyy[ ]K:mm[:ss][ ]a");
+ test("04.01.2016 06:20 PM", EXPECTED_NO_SECS, pattern);
+ test("04.01.2016 06:20PM", EXPECTED_NO_SECS, pattern);
+ test("04.01.2016 06:20:30 PM", EXPECTED_FULL, pattern);
+ test("04.01.2016 06:20:30PM", EXPECTED_FULL, pattern);
+ test("4.1.2016 6:20 PM", EXPECTED_NO_SECS, pattern);
+ test("4.1.2016 6:20PM", EXPECTED_NO_SECS, pattern);
+ test("4.1.2016 6:20:30 PM", EXPECTED_FULL, pattern);
+ test("4.1.2016 6:20:30PM", EXPECTED_FULL, pattern);
+ }
+
+ @Test
+ void parse_dMyyyHmmssaz() {
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("d.M.yyyy[ ]H:mm[:ss][ ][a][ ][z][ ][Z][ ][Z]");
+ test("04.01.2016 18:20:30", EXPECTED_FULL, pattern);
+ test("4.1.2016 18:20:30", EXPECTED_FULL, pattern);
+ test("4.1.2016 18:20", EXPECTED_NO_SECS, pattern);
+ test("04.01.2016 18:20:30 +0000", EXPECTED_FULL, pattern);
+ test("04.01.2016 18:20:30 GMT+0000", EXPECTED_FULL, pattern);
+ test("04.01.2016 18:20:30 GMT", EXPECTED_FULL, pattern);
+ test("04.01.2016 18:20:30+0000", EXPECTED_FULL, pattern);
+ }
+
+ @Test
+ void parse_dMyyyHmmssaz_Extra() {
+ testAllOffsets("04.01.2016 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_HHmmddMMyyyy() {
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("[HHmm]dd[-][.][/]MM[-][.][/]yyyy");
@@ -353,7 +578,7 @@ void parse_HHmmddMMyyyy() {
@Test
void parse_yyyyMMddHHmmssS() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy/MM/dd[[ ]HH[:]mm[[:]ss[[.]S]][ ][z][ ][Z][ ][Z]]");
test("2016/01/04 18:20:30.0", EXPECTED_FULL, pattern);
test("2016/01/0418:20:30.0", EXPECTED_FULL, pattern);
test("2016/01/041820300", EXPECTED_FULL, pattern);
@@ -369,9 +594,14 @@ void parse_yyyyMMddHHmmssS() {
test("2016/01/04", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_yyyyMMddHHmmssS_Extra() {
+ testAllOffsets("2016/01/04 13:20:30", EXPECTED_FULL);
+ }
+
@Test
void parse_yyyy_MM_ddHHmmssS() {
- DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z]]");
+ DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy:MM:dd[ H:m[:ss[.S]][ ][z][ ][Z][ ][Z]]");
test("2016:01:04 18:20:30.0", EXPECTED_FULL, pattern);
test("2016:01:04 18:20:30", EXPECTED_FULL, pattern);
test("2016:01:04 18:20:30 +0000", EXPECTED_FULL, pattern);
@@ -382,6 +612,11 @@ void parse_yyyy_MM_ddHHmmssS() {
test("2016:01:04", EXPECTED_NO_TIME, pattern);
}
+ @Test
+ void parse_yyyy_MM_ddHHmmssS_Extra() {
+ testAllOffsets("2016:01:04 13:20:30.0", EXPECTED_FULL);
+ }
+
@Test
void parse_yyyyMMddHHmmss() {
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
@@ -425,6 +660,23 @@ void testCleanDateString() {
test("2016-01-04\t\t18:20", EXPECTED_NO_SECS, "TABS");
test("2016-01-04 18:20", EXPECTED_NO_SECS, "SPACES");
test("2016-01-04 18:20=0D", EXPECTED_NO_SECS, "qp'ified ending");
+ test("$$2016-01-04 18:20:00$$", EXPECTED_NO_SECS, "Extra characters at the beginning and end");
+ test("2016-01-04 (18:20:00)", EXPECTED_NO_SECS, "Extra parenthesis");
+ test("2016-01-04 18:20:00 [GMT]", EXPECTED_NO_SECS, "Extra brackets");
+ test("\"Mon\", 4 Jan 2016 18:20 +0000 \"EST\"", EXPECTED_NO_SECS, "Extra quotes");
+ }
+
+ @Test
+ void testLastDitchEffortParsing() {
+ testExtensiveParsing("Jan 04 2016 18:20:30 +0000.5555555", EXPECTED_FULL, "Removing text at the end should be successful");
+ testExtensiveParsing("Jan 04 2016 18:20:30 +0000 (This is not a date offset)", EXPECTED_FULL,
+ "Removing text at the end should be successful");
+ testExtensiveParsing("Tue, 5 Jan 2016 02:20:30 +0800 PHT", EXPECTED_FULL,
+ "PHT is a timezone not parsed by DateTimeFormatter, but we should still parse correctly with the numeric time zone offset.");
+
+ // we should not attempt to remove random text at the end when we are not specifying tryExtensiveFormatList to be true
+ test("Jan 04 2016 18:20:30 +0000.5555555", 0L, "Fail to parse when tryExtensiveFormatList is false");
+ test("Jan 04 2016 18:20:30 +0000 (This is not a date offset)", 0L, "Fail to parse when tryExtensiveFormatList is false");
}
@Test