Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more date patterns and changed to a more lenient day and month parsing #312

Merged
merged 2 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 118 additions & 65 deletions src/main/java/liqp/filters/date/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -29,79 +28,133 @@ public class Parser {
*/
public static List<String> 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 {

Expand All @@ -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
Expand Down
53 changes: 53 additions & 0 deletions src/test/java/liqp/filters/DateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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"));

Expand Down Expand Up @@ -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+"));
}
}
}
Loading