diff --git a/src/main/java/liqp/filters/date/Parser.java b/src/main/java/liqp/filters/date/Parser.java index 165f2374..fb424643 100644 --- a/src/main/java/liqp/filters/date/Parser.java +++ b/src/main/java/liqp/filters/date/Parser.java @@ -11,7 +11,6 @@ import java.util.Locale; import static java.time.temporal.ChronoField.*; -import static java.time.temporal.ChronoField.INSTANT_SECONDS; public class Parser { @@ -29,79 +28,133 @@ public class Parser { */ public static List datePatterns = new ArrayList<>(); + // Since Liquid supports dates like `March 1st`, this list will + // hold strings that will be removed from the input string. + private static final String[] toBeRemoved = new String[] { "st", "nd", "rd", "th" }; + static { - datePatterns.add("EEE MMM dd hh:mm:ss yyyy"); - datePatterns.add("EEE MMM dd hh:mm yyyy"); - datePatterns.add("yyyy-MM-dd"); - datePatterns.add("dd-MM-yyyy"); + datePatterns.add("EEE MMM d hh:mm:ss yyyy"); + datePatterns.add("EEE MMM d hh:mm yyyy"); + datePatterns.add("yyyy-M-d"); + datePatterns.add("d-M-yyyy"); + datePatterns.add("d-M-yy"); + datePatterns.add("yy-M-d"); + + datePatterns.add("d/M/yyyy"); + datePatterns.add("yyyy/M/d"); + datePatterns.add("d/M/yy"); + datePatterns.add("yy/M/d"); + datePatterns.add("M/yyyy"); + datePatterns.add("yyyy/M"); + datePatterns.add("M/d"); + datePatterns.add("d/M"); // this is section without `T`, change here and do same change in section below with `T` - datePatterns.add("yyyy-MM-dd HH:mm"); - datePatterns.add("yyyy-MM-dd HH:mm X"); - datePatterns.add("yyyy-MM-dd HH:mm Z"); - datePatterns.add("yyyy-MM-dd HH:mm z"); - datePatterns.add("yyyy-MM-dd HH:mm'Z'"); - - datePatterns.add("yyyy-MM-dd HH:mm:ss"); - datePatterns.add("yyyy-MM-dd HH:mm:ss X"); - datePatterns.add("yyyy-MM-dd HH:mm:ss Z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss'Z'"); - - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSS"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSS X"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSS Z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSS z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSS'Z'"); - - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSS"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSS X"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSS Z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSS z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSS'Z'"); - - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSSSSS X"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSSSSS Z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSSSSS z"); - datePatterns.add("yyyy-MM-dd HH:mm:ss.SSSSSSSSS'Z'"); + datePatterns.add("yyyy-M-d HH:mm"); + datePatterns.add("yyyy-M-d HH:mm X"); + datePatterns.add("yyyy-M-d HH:mm Z"); + datePatterns.add("yyyy-M-d HH:mm z"); + datePatterns.add("yyyy-M-d HH:mm'Z'"); + + datePatterns.add("yyyy-M-d HH:mm:ss"); + datePatterns.add("yyyy-M-d HH:mm:ss X"); + datePatterns.add("yyyy-M-d HH:mm:ss Z"); + datePatterns.add("yyyy-M-d HH:mm:ss z"); + datePatterns.add("yyyy-M-d HH:mm:ss'Z'"); + + datePatterns.add("yyyy-M-d HH:mm:ss.SSS"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSS X"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSS Z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSS z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSS'Z'"); + + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSS"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSS X"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSS Z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSS z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSS'Z'"); + + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSSSSS"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSSSSS X"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSSSSS Z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSSSSS z"); + datePatterns.add("yyyy-M-d HH:mm:ss.SSSSSSSSS'Z'"); // this is section with `T` - datePatterns.add("yyyy-MM-dd'T'HH:mm"); - datePatterns.add("yyyy-MM-dd'T'HH:mm X"); - datePatterns.add("yyyy-MM-dd'T'HH:mm Z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm'Z'"); - - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss X"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss Z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS X"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS Z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSS X"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSS Z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSS z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"); - - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS X"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS Z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS z"); - datePatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'"); - + datePatterns.add("yyyy-M-d'T'HH:mm"); + datePatterns.add("yyyy-M-d'T'HH:mm X"); + datePatterns.add("yyyy-M-d'T'HH:mm Z"); + datePatterns.add("yyyy-M-d'T'HH:mm z"); + datePatterns.add("yyyy-M-d'T'HH:mm'Z'"); + + datePatterns.add("yyyy-M-d'T'HH:mm:ss"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss X"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss Z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss'Z'"); + + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSS"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSS X"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSS Z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSS z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSS'Z'"); + + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSS"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSS X"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSS Z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSS z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSS'Z'"); + + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSSSSS"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSSSSS X"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSSSSS Z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSSSSS z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ss.SSSSSSSSS'Z'"); + + datePatterns.add("EEE MMM d HH:mm:ss yyyy"); + datePatterns.add("EEE, d MMM yyyy HH:mm:ss Z"); + datePatterns.add("EEE, d MMM yyyy HH:mm:ss z"); + datePatterns.add("MMM d HH:mm:ss yyyy"); + datePatterns.add("d MMM yyyy HH:mm:ss Z"); + datePatterns.add("d MMM yyyy HH:mm:ss z"); + datePatterns.add("yyyy-M-d'T'HH:mm:ssXXX"); + + datePatterns.add("d MMM"); + datePatterns.add("d MMM yy"); + datePatterns.add("d MMM yyyy"); + datePatterns.add("d MMMM"); + datePatterns.add("d MMMM yy"); + datePatterns.add("d MMMM yyyy"); + + datePatterns.add("MMM d"); + datePatterns.add("MMM d, yy"); + datePatterns.add("MMM d, yyyy"); + + datePatterns.add("MMMM d"); + datePatterns.add("MMMM d, yy"); + datePatterns.add("MMMM d, yyyy"); + + datePatterns.add("MMM"); + datePatterns.add("MMM yy"); + datePatterns.add("MMM yyyy"); + + datePatterns.add("MMMM"); + datePatterns.add("MMMM yy"); + datePatterns.add("MMMM yyyy"); + + datePatterns.add("H:mm"); + datePatterns.add("H:mm:ss"); } public static ZonedDateTime parse(String str, Locale locale, ZoneId defaultZone) { + String normalized = str.toLowerCase(); + + for(String value : toBeRemoved) { + normalized = normalized.replace(value, ""); + } + for(String pattern : datePatterns) { try { @@ -110,7 +163,7 @@ public static ZonedDateTime parse(String str, Locale locale, ZoneId defaultZone) .appendPattern(pattern) .toFormatter(locale); - TemporalAccessor temporalAccessor = timeFormatter.parse(str); + TemporalAccessor temporalAccessor = timeFormatter.parse(normalized); return getZonedDateTimeFromTemporalAccessor(temporalAccessor, defaultZone); } catch (Exception e) { // ignore diff --git a/src/test/java/liqp/filters/DateTest.java b/src/test/java/liqp/filters/DateTest.java index 066319b1..f9d052d3 100644 --- a/src/test/java/liqp/filters/DateTest.java +++ b/src/test/java/liqp/filters/DateTest.java @@ -22,6 +22,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class DateTest { @@ -118,6 +119,7 @@ public void applyOriginalTest() throws Exception { final Filter filter = dateFilterSetting.filters.get("date"); assertThat(filter.apply("Fri Jul 16 01:00:00 2004", context, "%m/%d/%Y"), is((Object)"07/16/2004")); + assertThat(filter.apply("Fri Jul 9 01:00:00 2004", context, "%m/%d/%Y"), is((Object)"07/09/2004")); assertThat(filter.apply("Fri Jul 16 01:00 2004", context, "%m/%d/%Y"), is((Object)"07/16/2004")); @@ -233,4 +235,55 @@ public void test298InstantWhenEpochBeginAtUTC() { String res = parser.parse("{{ val }}").render("val", instant); assertEquals("1970-01-01 00:00:00 Z", res); } + + // https://github.com/bkiers/Liqp/issues/309 + @Test + public void testSupportedDateStrings() { + String[] tests = { + "now", + "today", + "1 March", + "MAR", + "MARCH", + "2024 MAR", + "2 mar", + "2 MAR", + "march 2nd", + "MARCH 2", + "MARCH 2nd", + "MARCH 3RD", + "MARCH 4th", + "MARCH 5th", + "MARCH 10th", + "2010-10-31", + "Aug 2000", + "Aug 31", + "Wed Nov 28 14:33:20 2001", + "Wed, 05 Oct 2011 22:26:12 -0400", + "Wed, 05 Oct 2011 02:26:12 GMT", + "Nov 29 14:33:20 2001", + "05 Oct 2011 22:26:12 -0400", + "06 Oct 2011 02:26:12 GMT", + "2011-10-05T22:26:12-04:00", + "0:00", + "1:00", + "01:00", + "12:00", + "16:30", + "3/2024", + "01/03", + "03/31", + "2001/03", + "01/2003", + "70-10-31" + }; + + for (String test : tests) { + // Parse every test date. If this fails, the rendering engine will just return the + // string. The '%s' will convert the date into a timestamp we check that what is + // returned is a sequence of digits. + String rendered = TemplateParser.DEFAULT.parse("{% assign d = '" + test + "' | date: '%s' %}{{ d }}").render(); + assertTrue("Failed to parse: '" + test + "'", rendered.matches("\\d+")); + } + } }