-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
298 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package liqp.filters.date; | ||
|
||
import java.time.*; | ||
import java.time.format.DateTimeFormatter; | ||
import java.time.format.DateTimeFormatterBuilder; | ||
import java.time.temporal.TemporalAccessor; | ||
import java.time.temporal.TemporalField; | ||
import java.time.temporal.TemporalQueries; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import static java.time.temporal.ChronoField.*; | ||
|
||
public abstract class BasicDateParser { | ||
|
||
private final List<String> cachedPatterns = new ArrayList<>(); | ||
|
||
protected BasicDateParser() { | ||
|
||
} | ||
|
||
protected BasicDateParser(List<String> patterns) { | ||
cachedPatterns.addAll(patterns); | ||
} | ||
|
||
protected void storePattern(String pattern) { | ||
cachedPatterns.add(pattern); | ||
} | ||
|
||
public abstract ZonedDateTime parse(String valAsString, Locale locale, ZoneId timeZone); | ||
|
||
protected ZonedDateTime parseUsingCachedPatterns(String str, Locale locale, ZoneId defaultZone) { | ||
for(String pattern : cachedPatterns) { | ||
try { | ||
TemporalAccessor temporalAccessor = parseUsingPattern(str, pattern, locale); | ||
return getZonedDateTimeFromTemporalAccessor(temporalAccessor, defaultZone); | ||
} catch (Exception e) { | ||
// ignore | ||
} | ||
} | ||
// Could not parse the string into a meaningful date, return null. | ||
return null; | ||
} | ||
|
||
protected TemporalAccessor parseUsingPattern(String normalized, String pattern, Locale locale) { | ||
DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder() | ||
.parseCaseInsensitive() | ||
.appendPattern(pattern) | ||
.toFormatter(locale); | ||
|
||
return timeFormatter.parse(normalized); | ||
} | ||
|
||
|
||
/** | ||
* Follow ruby rules: if some datetime part is missing, | ||
* the default is taken from `now` with default zone | ||
*/ | ||
public static ZonedDateTime getZonedDateTimeFromTemporalAccessor(TemporalAccessor temporal, ZoneId defaultZone) { | ||
if (temporal == null) { | ||
return ZonedDateTime.now(defaultZone); | ||
} | ||
if (temporal instanceof ZonedDateTime) { | ||
return (ZonedDateTime) temporal; | ||
} | ||
if (temporal instanceof Instant) { | ||
return ZonedDateTime.ofInstant((Instant) temporal, defaultZone); | ||
} | ||
|
||
ZoneId zoneId = temporal.query(TemporalQueries.zone()); | ||
if (zoneId == null) { | ||
LocalDate date = temporal.query(TemporalQueries.localDate()); | ||
LocalTime time = temporal.query(TemporalQueries.localTime()); | ||
|
||
if (date == null) { | ||
date = LocalDate.now(defaultZone); | ||
} | ||
if (time == null) { | ||
time = LocalTime.now(defaultZone); | ||
} | ||
return ZonedDateTime.of(date, time, defaultZone); | ||
} else { | ||
LocalDateTime now = LocalDateTime.now(zoneId); | ||
TemporalField[] copyThese = new TemporalField[]{ | ||
YEAR, | ||
MONTH_OF_YEAR, | ||
DAY_OF_MONTH, | ||
HOUR_OF_DAY, | ||
MINUTE_OF_HOUR, | ||
SECOND_OF_MINUTE, | ||
NANO_OF_SECOND | ||
}; | ||
for (TemporalField tf: copyThese) { | ||
if (temporal.isSupported(tf)) { | ||
now = now.with(tf, temporal.get(tf)); | ||
} | ||
} | ||
return now.atZone(zoneId); | ||
} | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
src/main/java/liqp/filters/date/FuzzyDateDateParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package liqp.filters.date; | ||
|
||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.time.temporal.TemporalAccessor; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
public class FuzzyDateDateParser extends BasicDateParser { | ||
|
||
@Override | ||
public ZonedDateTime parse(String valAsString, Locale locale, ZoneId defaultZone) { | ||
String normalized = valAsString.toLowerCase(); | ||
ZonedDateTime zonedDateTime = parseUsingCachedPatterns(normalized, locale, defaultZone); | ||
if (zonedDateTime != null) { | ||
return zonedDateTime; | ||
} | ||
|
||
List<Part> parts = new ArrayList<>(); | ||
// we start as one big single unparsed part | ||
DateParseContext ctx = new DateParseContext(); | ||
parts.add(new UnparsedPart(0, normalized.length(), normalized)); | ||
|
||
while (haveUnparsed(parts)) { | ||
parts = parsePart(parts, ctx); | ||
} | ||
|
||
String pattern = reconstructPattern(parts); | ||
|
||
TemporalAccessor temporalAccessor = parseUsingPattern(normalized, pattern, locale); | ||
if (temporalAccessor == null) { | ||
return null; | ||
} | ||
storePattern(pattern); | ||
return getZonedDateTimeFromTemporalAccessor(temporalAccessor, defaultZone); | ||
} | ||
|
||
|
||
private String reconstructPattern(List<Part> parts) { | ||
return null; | ||
} | ||
|
||
static class DateParseContext { | ||
|
||
} | ||
|
||
private List<Part> parsePart(List<Part> parts, DateParseContext ctx) { | ||
return new ArrayList<>(); | ||
} | ||
|
||
private boolean haveUnparsed(List<Part> parts) { | ||
return parts.stream().anyMatch(p -> p.state() == PartState.UNPARSED); | ||
} | ||
|
||
private PartItem getPart(String valAsString) { | ||
return null; | ||
} | ||
enum PartState { | ||
UNPARSED, | ||
PARSED, | ||
KNOWN_CONSTANT, | ||
UNRECOGNIZED | ||
} | ||
interface Part { | ||
int start(); // before symbol | ||
int end(); // after symbol | ||
PartState state(); | ||
} | ||
|
||
static class UnparsedPart implements Part { | ||
final int start; | ||
final int end; | ||
UnparsedPart(int start, int end, String value) { | ||
this.start = start; | ||
this.end = end; | ||
} | ||
@Override | ||
public int start() { | ||
return start; | ||
} | ||
@Override | ||
public int end() { | ||
return end; | ||
} | ||
@Override | ||
public PartState state() { | ||
return PartState.UNPARSED; | ||
} | ||
} | ||
|
||
static class PartItem { | ||
final PartKind kind; | ||
final String pattern; | ||
final int start; | ||
final int end; | ||
PartItem(PartKind kind, String pattern, int start, int end) { | ||
this.kind = kind; | ||
this.pattern = pattern; | ||
this.start = start; | ||
this.end = end; | ||
} | ||
} | ||
enum PartKind { | ||
CONSTANT, | ||
YEAR, | ||
MONTH, | ||
DAY, | ||
HOUR, | ||
MINUTE, | ||
SECOND, | ||
MILLISECOND, | ||
MICROSECOND, | ||
NANOSECOND | ||
} | ||
|
||
} |
Oops, something went wrong.