Skip to content

Commit

Permalink
[WIP]
Browse files Browse the repository at this point in the history
  • Loading branch information
vkhrystiuk-ks committed Dec 18, 2024
1 parent 428f1f3 commit d29ce11
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 103 deletions.
2 changes: 1 addition & 1 deletion src/main/java/liqp/filters/date/BasicDateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

public abstract class BasicDateParser {

private final List<String> cachedPatterns = new CopyOnWriteArrayList<>();
protected final List<String> cachedPatterns = new CopyOnWriteArrayList<>();

protected BasicDateParser() {

Expand Down
86 changes: 70 additions & 16 deletions src/main/java/liqp/filters/date/fuzzy/FuzzyDateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import liqp.filters.date.BasicDateParser;
import liqp.filters.date.fuzzy.Part.NewPart;
import liqp.filters.date.fuzzy.Part.RecognizedPart;
Expand Down Expand Up @@ -51,17 +53,37 @@ public ZonedDateTime parse(String normalized, Locale locale, ZoneId defaultZone)
return date;
}

String pattern = guessPattern(normalized, locale);

TemporalAccessor temporalAccessor = parseUsingPattern(normalized, pattern, locale);
if (temporalAccessor == null) {
GuessingResult guessingResult = guessPattern(normalized, locale, defaultZone);
if (guessingResult == null) {
return null;
}
storePattern(pattern);
return getFullDateIfPossible(temporalAccessor, defaultZone);
storePattern(guessingResult.pattern);
return getFullDateIfPossible(guessingResult.temporalAccessor, defaultZone);
}

GuessingResult guessPattern(String normalized, Locale locale, ZoneId defaultZone) {
Stream<String> guessingStream = getGuessingStream(cachedPatterns, normalized, locale, defaultZone);
return getGuessingResult(guessingStream, normalized, locale, defaultZone);
}

private Stream<String> getGuessingStream(List<String> cachedPatterns, String normalized,
Locale locale, ZoneId defaultZone) {
// [1, 2][1][1][1] => ["1111"], ["2111"]
List<List<String>> fullPattern = guessVariants(normalized, locale);

return fullPattern.stream()
.reduce(
Stream.of(""), // Initial
(stream, list) -> stream.flatMap(
combination -> list.stream().map(element -> combination + element)
),
Stream::concat
)
.filter(p -> !cachedPatterns.contains(p));
}

String guessPattern(String normalized, Locale locale) {

protected List<List<String>> guessVariants(String normalized, Locale locale) {
if (locale == null) {
locale = Locale.ENGLISH;
}
Expand All @@ -76,19 +98,51 @@ String guessPattern(String normalized, Locale locale) {
return reconstructPattern(parts);
}

private boolean haveUnrecognized(List<Part> parts) {
return parts.stream().anyMatch(p -> p.state() == Part.PartState.NEW);
}

private String reconstructPattern(List<Part> parts) {
private List<List<String>> reconstructPattern(List<Part> parts) {
return parts.stream().map(p -> {
if (p.state() == Part.PartState.RECOGNIZED) {
return ((RecognizedPart) p).getPattern();
return ((RecognizedPart) p).getPatterns();
} else if (p.state() == Part.PartState.PUNCTUATION) {
return p.source();
return newList(p.source());
} else {
return "'" + p.source() + "'";
return newList("'" + p.source() + "'");
}
}).collect(Collectors.joining());
}).collect(Collectors.toList());
}

private List<String> newList(String pattern) {
List<String> res = new ArrayList<>();
res.add(pattern);
return res;
}

private GuessingResult getGuessingResult(Stream<String> guessingStream, String normalized, Locale locale, ZoneId defaultZone) {
return guessingStream
.map(pattern -> {
TemporalAccessor temporalAccessor = parseUsingPattern(normalized, pattern, locale);
if (temporalAccessor != null) {
GuessingResult result = new GuessingResult();
result.pattern = pattern;
result.temporalAccessor = temporalAccessor;
return result;
}
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

static class GuessingResult {
String pattern;
TemporalAccessor temporalAccessor;
}
private TemporalAccessor getTemporalAccessor(String normalized, Locale locale,
ZoneId defaultZone, Stream<String> guessingStream) {
return null;
}

private boolean haveUnrecognized(List<Part> parts) {
return parts.stream().anyMatch(p -> p.state() == Part.PartState.NEW);
}
}
8 changes: 8 additions & 0 deletions src/main/java/liqp/filters/date/fuzzy/Part.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package liqp.filters.date.fuzzy;

import java.util.ArrayList;
import java.util.List;

public interface Part {
enum PartState {
NEW,
Expand Down Expand Up @@ -130,6 +133,11 @@ public String source() {
public String getPattern() {
return pattern;
}
public List<String> getPatterns() {
ArrayList<String> res = new ArrayList<>();
res.add(pattern);
return res;
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,92 +23,92 @@ public class FuzzyDateParserParametrizedTest {
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
// {null, "1995", "yyyy"},
// {null, " 1995 ", " yyyy "},
// {null, " 1995", " yyyy"},
// {null, "1995 ", "yyyy "},
// {null, "January 1995", "MMMM yyyy"},
// {null, "January 1995 ", "MMMM yyyy "},
// {null, " January 1995", " MMMM yyyy"},
// {null, " 1995 January", " yyyy MMMM"},
// {null, "Jan 1995", "MMM yyyy"},
// {null, "1995 Jan ", "yyyy MMM "},
// {Locale.GERMAN, "1995 Mai", "yyyy MMMM"},
// FuzzyDateParser.CLDR_LOADED ?
// new Object[]{
// Locale.GERMAN, "??1995-----Dez.!", "'??'yyyy-----MMM'!'"}
// : new Object[]{
// Locale.GERMAN, "??1995-----Dez!", "'??'yyyy-----MMM'!'"}
// ,
// {null, "1:23", "H:mm"},
// {null, "01:23", "HH:mm"},
// {null, "1:23:45", "H:mm:ss"},
// {null, "01:23:45", "HH:mm:ss"},
// {null, "1:23:45.6", "H:mm:ss.S"},
// {null, "01:23:45.6", "HH:mm:ss.S"},
// {null, "1:23:45.67", "H:mm:ss.SS"},
// {null, "1:23:45.678", "H:mm:ss.SSS"},
// {null, "1:23:45.6789", "H:mm:ss.SSSS"},
// {null, "1:23:45.67890", "H:mm:ss.SSSSS"},
// {null, "1:23:45.678901", "H:mm:ss.SSSSSS"},
// {null, "1:23:45.6789012", "H:mm:ss.SSSSSSS"},
// {null, "1:23:45.67890123", "H:mm:ss.SSSSSSSS"},
// {null, "1:23:45.678901234", "H:mm:ss.SSSSSSSSS"},
// {null, "1:23:45.678901234am", "h:mm:ss.SSSSSSSSSa"}, // correct
// {null, "1:23:45.678901234a", "H:mm:ss.SSSSSSSSS'a'"}, // incorrect
// {null, "1:23:45.678901234p", "H:mm:ss.SSSSSSSSS'p'"}, // incorrect
// {null, "1:23:45.678901234pm", "h:mm:ss.SSSSSSSSSa"}, // correct
// {null, "1:23:45.678901234 pm", "h:mm:ss.SSSSSSSSS a"}, // correct
// {null, " 1:23:45.678", " H:mm:ss.SSS"},
// {null, " 1:23:45.678 ", " H:mm:ss.SSS "},
// {null, " 01:23:45.678 ", " HH:mm:ss.SSS "},
// {null, " 1:23:45.678 am ", " h:mm:ss.SSS a "},
// {null, " 1:23:45.678 PM ", " h:mm:ss.SSS a "},
// {null, "12 Jan 1995T01:23:45.678", "'12' MMM yyyy'T'HH:mm:ss.SSS"},
// {null, "12 AD", "yy GG"},
// {null, " 12 AD ", " yy GG "},
// {null, " 12 Anno Domini ", " yy GGGG "},
// {null, " 12345 Before Christ ", " yyyyy GGGG "},
// {null, " 1 BC ", " y 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 ", " yy GG '12' MMM HH:mm:ss.SSS "},
// {null, "12 Jan 01:23:45.678 12 Anno Domini", "'12' MMM HH:mm:ss.SSS yy 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, "1995", "yyyy"},
{null, " 1995 ", " yyyy "},
{null, " 1995", " yyyy"},
{null, "1995 ", "yyyy "},
{null, "January 1995", "MMMM yyyy"},
{null, "January 1995 ", "MMMM yyyy "},
{null, " January 1995", " MMMM yyyy"},
{null, " 1995 January", " yyyy MMMM"},
{null, "Jan 1995", "MMM yyyy"},
{null, "1995 Jan ", "yyyy MMM "},
{Locale.GERMAN, "1995 Mai", "yyyy MMMM"},
FuzzyDateParser.CLDR_LOADED ?
new Object[]{
Locale.GERMAN, "??1995-----Dez.!", "'??'yyyy-----MMM'!'"}
: new Object[]{
Locale.GERMAN, "??1995-----Dez!", "'??'yyyy-----MMM'!'"}
,
{null, "1:23", "H:mm"},
{null, "01:23", "HH:mm"},
{null, "1:23:45", "H:mm:ss"},
{null, "01:23:45", "HH:mm:ss"},
{null, "1:23:45.6", "H:mm:ss.S"},
{null, "01:23:45.6", "HH:mm:ss.S"},
{null, "1:23:45.67", "H:mm:ss.SS"},
{null, "1:23:45.678", "H:mm:ss.SSS"},
{null, "1:23:45.6789", "H:mm:ss.SSSS"},
{null, "1:23:45.67890", "H:mm:ss.SSSSS"},
{null, "1:23:45.678901", "H:mm:ss.SSSSSS"},
{null, "1:23:45.6789012", "H:mm:ss.SSSSSSS"},
{null, "1:23:45.67890123", "H:mm:ss.SSSSSSSS"},
{null, "1:23:45.678901234", "H:mm:ss.SSSSSSSSS"},
{null, "1:23:45.678901234am", "h:mm:ss.SSSSSSSSSa"}, // correct
{null, "1:23:45.678901234a", "H:mm:ss.SSSSSSSSS'a'"}, // incorrect
{null, "1:23:45.678901234p", "H:mm:ss.SSSSSSSSS'p'"}, // incorrect
{null, "1:23:45.678901234pm", "h:mm:ss.SSSSSSSSSa"}, // correct
{null, "1:23:45.678901234 pm", "h:mm:ss.SSSSSSSSS a"}, // correct
{null, " 1:23:45.678", " H:mm:ss.SSS"},
{null, " 1:23:45.678 ", " H:mm:ss.SSS "},
{null, " 01:23:45.678 ", " HH:mm:ss.SSS "},
{null, " 1:23:45.678 am ", " h:mm:ss.SSS a "},
{null, " 1:23:45.678 PM ", " h:mm:ss.SSS a "},
{null, "12 Jan 1995T01:23:45.678", "'12' MMM yyyy'T'HH:mm:ss.SSS"},
{null, "12 AD", "yy GG"},
{null, " 12 AD ", " yy GG "},
{null, " 12 Anno Domini ", " yy GGGG "},
{null, " 12345 Before Christ ", " yyyyy GGGG "},
{null, " 1 BC ", " y 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 ", " yy GG '12' MMM HH:mm:ss.SSS "},
{null, "12 Jan 01:23:45.678 12 Anno Domini", "'12' MMM HH:mm:ss.SSS yy 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"},
// {null, "2021-1-2", "yyyy-M-d"},
// {null, "2021-01-2", "yyyy-MM-d"},
// {null, "2021-1-02", "yyyy-M-dd"},
// {null, "2024-1-5 08:15 ", "yyyy-M-d HH:mm "},
// {null, "2024-12-25 14:45 ", "yyyy-MM-dd HH:mm "},
// {null, "2024-12-25 14:45:30 ", "yyyy-MM-dd HH:mm:ss "},
// {null, "1/1/23 ", "d/M/yy "},
// {null, "1/1/2023 ", "d/M/yyyy "},
// {null, "01/01/23 ", "dd/MM/yy "},
// {null, "01/01/2023 ", "dd/MM/yyyy "},
// {null, "1/1/23 12:34 ", "d/M/yy HH:mm "},
// {null, "1/1/2023 12:34 ", "d/M/yyyy HH:mm "},
// {null, "01/01/23 12:34 ", "dd/MM/yy HH:mm "},
{null, "2021-1-2", "yyyy-M-d"},
{null, "2021-01-2", "yyyy-MM-d"},
{null, "2021-1-02", "yyyy-M-dd"},
{null, "2024-1-5 08:15 ", "yyyy-M-d HH:mm "},
{null, "2024-12-25 14:45 ", "yyyy-MM-dd HH:mm "},
{null, "2024-12-25 14:45:30 ", "yyyy-MM-dd HH:mm:ss "},
{null, "1/1/23 ", "d/M/yy "},
{null, "1/1/2023 ", "d/M/yyyy "},
{null, "01/01/23 ", "dd/MM/yy "},
{null, "01/01/2023 ", "dd/MM/yyyy "},
{null, "1/1/23 12:34 ", "d/M/yy HH:mm "},
{null, "1/1/2023 12:34 ", "d/M/yyyy HH:mm "},
{null, "01/01/23 12:34 ", "dd/MM/yy HH:mm "},

});
}
Expand All @@ -125,7 +125,7 @@ public void shouldParse() {
String pattern = null;
try {
final FuzzyDateParser parser = new FuzzyDateParser();
pattern = parser.guessPattern(input, locale);
pattern = parser.guessPattern(input, locale, null).pattern;
assertEquals(String.format("input is: [%s], expected pattern: [%s], real pattern: [%s]", input, expectedPattern, pattern), expectedPattern, pattern);
parsed = parser.parse(input, locale, null);
String formatted = parsed.format(DateTimeFormatter.ofPattern(pattern, locale)).toLowerCase(locale);
Expand Down

0 comments on commit d29ce11

Please sign in to comment.