From 47f5f06da61d7b7c439e409b12749fa9ce1fc150 Mon Sep 17 00:00:00 2001 From: Vasyl Khrystiuk Date: Fri, 6 Dec 2024 04:37:48 +0200 Subject: [PATCH] [WIP] --- .../liqp/filters/date/FuzzyDateParser.java | 84 +++++++++++++++---- .../date/FuzzyDateParserParametrizedTest.java | 23 +++++ .../filters/date/FuzzyDateParserTest.java | 33 +++++++- 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/src/main/java/liqp/filters/date/FuzzyDateParser.java b/src/main/java/liqp/filters/date/FuzzyDateParser.java index 0a909fe4..3e5dc747 100644 --- a/src/main/java/liqp/filters/date/FuzzyDateParser.java +++ b/src/main/java/liqp/filters/date/FuzzyDateParser.java @@ -35,6 +35,12 @@ public class FuzzyDateParser extends BasicDateParser { @Override public ZonedDateTime parse(String valAsString, Locale locale, ZoneId defaultZone) { + if (locale == null) { + locale = Locale.ROOT; + } + if (defaultZone == null) { + defaultZone = ZoneId.systemDefault(); + } String normalized = valAsString.toLowerCase(); ZonedDateTime zonedDateTime = parseUsingCachedPatterns(normalized, locale, defaultZone); if (zonedDateTime != null) { @@ -66,6 +72,20 @@ String guessPattern(String normalized, Locale locale) { private List parsePart(List parts, DateParseContext ctx) { + if (notSet(ctx.weekDay)) { + LookupResult result = lookup(parts, fullWeekdaysExtractor(ctx.locale)); + if (result.found) { + ctx.weekDay = true; + return result.parts; + } + result = lookup(parts, shortWeekdaysExtractor(ctx.locale)); + if (result.found) { + ctx.weekDay = true; + return result.parts; + } + ctx.weekDay = false; + } + if (notSet(ctx.hasYear)) { LookupResult result = lookup(parts, yearWithEraExtractor); if (result.found) { @@ -127,9 +147,13 @@ static class DateParseContext { private final Locale locale; Boolean hasYear; Boolean hasMonthName; + Boolean weekDay; Boolean hasTime; public DateParseContext(Locale locale) { + if (locale == null) { + locale = Locale.ROOT; + } this.locale = locale; } } @@ -213,43 +237,52 @@ public PartExtractorResult extract(String source) { return delegate.extract(source); } } - static class FullMonthExtractor extends PartExtractorDelegate { - public FullMonthExtractor(Locale locale, String formatterPattern) { + static abstract class EnumExtractor extends PartExtractorDelegate { + public EnumExtractor(Locale locale, String formatterPattern) { if (locale == null || Locale.ROOT.equals(locale)) { locale = Locale.US; } - String[] months = withoutNulls(getMonthsNamesFromLocale(locale)); - String monthPattern = String.join("|", months); - super.delegate = new RegexPartExtractor(".*\\b?(" + monthPattern + ")\\b?.*", formatterPattern); + String[] values = withoutNulls(getEnumValues(locale)); + String valuesPattern = String.join("|", values); + super.delegate = new RegexPartExtractor(".*\\b?(" + valuesPattern + ")\\b?.*", formatterPattern); } - protected String[] getMonthsNamesFromLocale(Locale locale) { - return new DateFormatSymbols(locale).getMonths(); - } + abstract protected String[] getEnumValues(Locale locale); - protected String[] withoutNulls(String[] shortMonths) { - return Arrays.stream(shortMonths) - .filter(month -> month != null && !month.isEmpty()) + protected String[] withoutNulls(String[] enumValues) { + return Arrays.stream(enumValues) + .filter(val -> val != null && !val.isEmpty()) .map(Pattern::quote) - .map(this::convertMonthName) + .map(this::convertValName) .toArray(String[]::new); } - protected String convertMonthName(String monthName) { - return monthName; + protected String convertValName(String val) { + return val; + } + } + + static class FullMonthExtractor extends EnumExtractor { + public FullMonthExtractor(Locale locale) { + super(locale, "MMMM"); + } + + @Override + protected String[] getEnumValues(Locale locale) { + return new DateFormatSymbols(locale).getMonths(); } } private PartExtractor fullMonthExtractor(Locale locale) { - return new FullMonthExtractor(locale, "MMMM"); + return new FullMonthExtractor(locale); } - static class ShortMonthExtractor extends FullMonthExtractor { + static class ShortMonthExtractor extends EnumExtractor { public ShortMonthExtractor(Locale locale) { super(locale, "MMM"); } @Override - protected String[] getMonthsNamesFromLocale(Locale locale) { + protected String[] getEnumValues(Locale locale) { return new DateFormatSymbols(locale).getShortMonths(); } } @@ -329,6 +362,23 @@ public PartExtractorResult extract(String source) { static PartExtractor regularTimeExtractor = new RegularTimeExtractor(); + PartExtractor shortWeekdaysExtractor(Locale locale) { + return new EnumExtractor(locale, "EEE") { + @Override + protected String[] getEnumValues(Locale locale) { + return new DateFormatSymbols(locale).getShortWeekdays(); + } + }; + } + PartExtractor fullWeekdaysExtractor(Locale locale) { + return new EnumExtractor(locale, "EEEE") { + @Override + protected String[] getEnumValues(Locale locale) { + return new DateFormatSymbols(locale).getWeekdays(); + } + }; + } + static class LookupResult { final List parts; final boolean found; diff --git a/src/test/java/liqp/filters/date/FuzzyDateParserParametrizedTest.java b/src/test/java/liqp/filters/date/FuzzyDateParserParametrizedTest.java index 3d0e1f09..1d9818af 100644 --- a/src/test/java/liqp/filters/date/FuzzyDateParserParametrizedTest.java +++ b/src/test/java/liqp/filters/date/FuzzyDateParserParametrizedTest.java @@ -69,9 +69,32 @@ public static Collection data() { {null, " 12 Anno Domini ", " yyyy GGGG "}, {null, " 12345 Before Christ ", " yyyy GGGG "}, {null, " 0 BC ", " yyyy GG "}, + {null, "12 January", "12 MMMM"}, + {null, " 12 January ", " 12 MMMM "}, + {null, "12 Jan", "12 MMM"}, + {null, " 12 Jan ", " 12 MMM "}, {null, " 12 BC 12 Jan 01:23:45.678 ", " yyyy GG 12 MMM HH:mm:ss.SSS "}, {null, "12 Jan 01:23:45.678 12 Anno Domini", "12 MMM HH:mm:ss.SSS yyyy GGGG"}, + {null, "Monday", "EEEE"}, + {null, " Monday ", " EEEE "}, + {null, "Monday ", "EEEE "}, + {null, " Monday", " EEEE"}, + {null, "Mon", "EEE"}, + {null, " Mon ", " EEE "}, + {null, " Mon", " EEE"}, + {null, "Mon ", "EEE "}, + {Locale.GERMAN, "Montag", "EEEE"}, + {Locale.GERMAN, " Montag ", " EEEE "}, + {Locale.GERMAN, "Montag ", "EEEE "}, + {Locale.GERMAN, " Montag", " EEEE"}, + FuzzyDateParser.CLDR_LOADED ? + new Object[]{ + Locale.GERMAN, "Mo.", "EEE"} + : new Object[]{ + Locale.GERMAN, "Mo", "EEE"} + , + {null, "Monday 17th September 1999 BC at 12:34:56.000 AM", "EEEE 17th MMMM yyyy GG at h:mm:ss.SSS a"}, }); } diff --git a/src/test/java/liqp/filters/date/FuzzyDateParserTest.java b/src/test/java/liqp/filters/date/FuzzyDateParserTest.java index 6dcfacf9..0b67c62d 100644 --- a/src/test/java/liqp/filters/date/FuzzyDateParserTest.java +++ b/src/test/java/liqp/filters/date/FuzzyDateParserTest.java @@ -3,8 +3,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.Callable; import liqp.filters.date.FuzzyDateParser.PartExtractor; import liqp.filters.date.FuzzyDateParser.PartExtractorResult; import org.junit.Test; @@ -17,6 +18,32 @@ public void testTimeRegexp() { assertTrue(result.found); assertEquals( 1, result.start); assertEquals( 6, result.end); - assertEquals(result.formatterPattern, "HH:mm"); + assertEquals("HH:mm", result.formatterPattern); + } + + @Test + public void testSelfCorrectLowToBig() { + unchecked(() -> Class.forName(FuzzyDateParser.class.getName())); + final FuzzyDateParser parser = new FuzzyDateParser(); + ZonedDateTime parsed = parser.parse("1:00 am", null, null); + assertEquals("01:00:00", parsed.format(DateTimeFormatter.ISO_LOCAL_TIME)); + parsed = parser.parse("01:00 am", null, null); + assertEquals("01:00:00", parsed.format(DateTimeFormatter.ISO_LOCAL_TIME)); + } + + @Test + public void testSelfCorrectBigToLow() { + unchecked(() -> Class.forName(FuzzyDateParser.class.getName())); + final FuzzyDateParser parser = new FuzzyDateParser(); + ZonedDateTime parsed = parser.parse("01:00 am", null, null); + assertEquals("01:00:00", parsed.format(DateTimeFormatter.ISO_LOCAL_TIME)); + parsed = parser.parse("1:00 am", null, null); + assertEquals("01:00:00", parsed.format(DateTimeFormatter.ISO_LOCAL_TIME)); + } + + static void unchecked(@SuppressWarnings("rawtypes") Callable f) { + try { + f.call(); + } catch (Exception ignored) { } } }