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