Java - 优雅的解析方式 date/period?
Java - Elegant way of parsing date/period?
从ISO-8601标准来看,有4种表达方式intervals/duration:
开始和结束,如“2007-03-01T13:00:00Z/2008-05-11T15:30:00Z”
开始和持续时间,例如“2007-03-01T13:00:00Z/P1Y2M10DT2H30M”
时长和结束,如"P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
仅持续时间,例如 "P1Y2M10DT2H30M",带有额外的上下文信息
仅使用 Java 8(没有 Joda、扩展等),是否有任何优雅的方式来处理案例 1-3?
我知道 Duration.Parse()
和 Period.Parse()
,但我想知道是否有一种优雅的方式来处理这 4 种情况。例如:
String datePeriod = "2016-07-21/P6D";
String twoDates = "2016-07-21/2016-07-25";
Duration d = Duration.parse(datePeriod); // DateTimeParseException
Duration p = Duration.parse(twoDates); // same
我目前的思考过程非常草率,我 100% 确定有更好的方法。就像用每个案例的嵌套 try/catch 块分别处理 4 个案例,这看起来有点像反模式(如果有的话)。 (在 /
上拆分,为日期解析第一个块,检查错误,为句点解析第一个块,为日期解析第二个块,检查错误...你明白了)
如有任何提示,我们将不胜感激!
--
此外,ISO 8601 Time Interval Parsing in Java 的答案对我没有任何帮助,因为最重要的答案只关心 PT...
的东西。
拆分这些东西没有真正的反模式。 Oracle 已将这些单独的解析器的职责分开,如果我们想在这种编排中一起使用它们,我们有责任确保以合理的方式再次将各个部分剥离在一起。
就是说,我有一个与核心 Java 8 一起使用并利用 Function
和一些自定义 类 的解决方案。为简洁起见,我将省略自定义 bean,因为它们相当基本,而且主要提升是在 Function
s 中完成的。
请注意,为了让 'Z' 被识别为有效条目,您必须使用 DateTimeFormatter.ISO_DATE_TIME
进行解析。此外,为确保正确选择持续时间,请将 "PT"
添加到适合持续时间的文本。从现有字符串中获取此类详细信息的一种更智能的方法是我留给 reader.
的练习
Function<String, Range> convertToRange = (dateString) -> {
String[] dateStringParts = dateString.split("/");
return new Range(LocalDateTime.parse(dateStringParts[0], DateTimeFormatter.ISO_DATE_TIME),
LocalDateTime.parse(dateStringParts[1], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationAndDateTime> convertToDurationAndDateTime = (dateString) -> {
String[] dateStringParts = dateString.split("/");
String[] durationAndPeriodParts = dateStringParts[1].split("T");
return new DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]),
LocalDateTime.parse(dateStringParts[0], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationAndDateTime> convertToDateTimeAndDuration = (dateString) -> {
String[] dateStringParts = dateString.split("/");
String[] durationAndPeriodParts = dateStringParts[0].split("T");
return new DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]),
LocalDateTime.parse(dateStringParts[1], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationOnly> convertToDurationOnlyRelativeToCurrentTime = (dateString) -> {
String[] durationAndPeriodParts = dateString.split("T");
return new DurationOnly(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]));
};
很高兴解决您的问题,因为它是介绍 Composite Design Pattern in Functional Programming 的一个很好的例子。您可以将函数组合成 更大 和 强大 的单个函数。例如:
Function<String, Optional<Range<LocalDateTime>>> parser = anyOf(
both(), //case 1
starting(), //case 2
ending(), //case 3
since(LocalDateTime.now()) //case 4
);
Range<LocalDateTime> range = parser.apply("<INPUT>").orElse(null);
//OR using in stream as below
List<Range<LocalDateTime>> result = Stream.of(
"<Case 1>", "<Case 2>", "<Case 3>", "<Case 4>"
).map(parser).filter(Optional::isPresent).map(Optional::get).collect(toList());
下面一步步介绍上面的代码
下面的代码几乎应用了大部分 Design Patterns in OOP. e.g: Composite, Proxy, Adapter, Factory Method 设计模式和.etc.
函数
factory:both
方法满足第一种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> both() {
return parsing((first, second) -> new Range<>(
datetime(first),
datetime(second)
));
}
factory:starting
方法满足第二种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> starting() {
return parsing((first, second) -> {
LocalDateTime start = datetime(first);
return new Range<>(start, start.plus(amount(second)));
});
}
factory:ending
方法满足第三种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> ending() {
return parsing((first, second) -> {
LocalDateTime end = datetime(second);
return new Range<>(end.minus(amount(first)), end);
});
}
factory:since
方法满足最后一种情况如下:
static Function<String,Optional<Range<LocalDateTime>>> since(LocalDateTime start) {
return parsing((amount, __) -> new Range<>(start, start.plus(amount(amount))));
}
composite : anyOf
方法的职责是尽快在 Function
中找到满意的结果:
@SuppressWarnings("ConstantConditions")
static <T, R> Function<T, Optional<R>>
anyOf(Function<T, Optional<R>>... functions) {
return it -> Stream.of(functions).map(current -> current.apply(it))
.filter(Optional::isPresent)
.findFirst().get();
}
adapter: parsing
方法的职责是为特定输入创建一个 parser:
static <R> Function<String, Optional<R>>
parsing(BiFunction<String, String, R> parser) {
return splitting("/", exceptionally(optional(parser), Optional::empty));
}
proxy: exceptionally
方法的职责是处理 Exception
s:
static <T, U, R> BiFunction<T, U, R>
exceptionally(BiFunction<T, U, R> source, Supplier<R> exceptional) {
return (first, second) -> {
try {
return source.apply(first, second);
} catch (Exception ex) {
return exceptional.get();
}
};
}
adapter:splitting
方法的职责是将 BiFunction
转换为 Function
:
static <R> Function<String, R>
splitting(String regex, BiFunction<String, String, R> source) {
return value -> {
String[] parts = value.split(regex);
return source.apply(parts[0], parts.length == 1 ? "" : parts[1]);
};
}
adapter:optional
方法的职责是为最终结果创建一个 Optional
:
static <R> BiFunction<String, String, Optional<R>>
optional(BiFunction<String, String, R> source) {
return (first, last) -> Optional.of(source.apply(first, last));
}
Value Object:
Range
class 用于保存远程对象:
final class Range<T> {
public final T start;
public final T end;
public Range(T start, T end) {
this.start = start;
this.end = end;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Range)) {
return false;
}
Range<?> that = (Range<?>) o;
return Objects.equals(start, that.start) && Objects.equals(end, that.end);
}
@Override
public int hashCode() {
return Objects.hash(start) * 31 + Objects.hash(end);
}
@Override
public String toString() {
return String.format("[%s, %s]", start, end);
}
}
实用工具
datetime
方法从 String
:
创建一个 LocalDateTime
static LocalDateTime datetime(String datetime) {
return LocalDateTime.parse(
datetime,
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss['Z']")
);
}
amount
方法创建一个 TemporalAmount
,它从 String
中获取 Duration
和 Period
:
static TemporalAmount amount(String text) {
return splitting("T", (first, second) -> new TemporalAmount() {
private Period period= first.isEmpty() ? Period.ZERO : Period.parse(first);
private Duration duration = second.isEmpty() ? Duration.ZERO
: Duration.parse(String.format("PT%s", second));
@Override
public long get(TemporalUnit unit) {
return (period.getUnits().contains(unit) ? period.get(unit) : 0) +
(duration.getUnits().contains(unit) ? duration.get(unit) : 0);
}
@Override
public List<TemporalUnit> getUnits() {
return Stream.of(period, duration).map(TemporalAmount::getUnits)
.flatMap(List::stream)
.collect(toList());
}
@Override
public Temporal addTo(Temporal temporal) {
return period.addTo(duration.addTo(temporal));
}
@Override
public Temporal subtractFrom(Temporal temporal) {
return period.subtractFrom(duration.subtractFrom(temporal));
}
}).apply(text);
}
从ISO-8601标准来看,有4种表达方式intervals/duration:
开始和结束,如“2007-03-01T13:00:00Z/2008-05-11T15:30:00Z”
开始和持续时间,例如“2007-03-01T13:00:00Z/P1Y2M10DT2H30M”
时长和结束,如"P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
仅持续时间,例如 "P1Y2M10DT2H30M",带有额外的上下文信息
仅使用 Java 8(没有 Joda、扩展等),是否有任何优雅的方式来处理案例 1-3?
我知道 Duration.Parse()
和 Period.Parse()
,但我想知道是否有一种优雅的方式来处理这 4 种情况。例如:
String datePeriod = "2016-07-21/P6D";
String twoDates = "2016-07-21/2016-07-25";
Duration d = Duration.parse(datePeriod); // DateTimeParseException
Duration p = Duration.parse(twoDates); // same
我目前的思考过程非常草率,我 100% 确定有更好的方法。就像用每个案例的嵌套 try/catch 块分别处理 4 个案例,这看起来有点像反模式(如果有的话)。 (在 /
上拆分,为日期解析第一个块,检查错误,为句点解析第一个块,为日期解析第二个块,检查错误...你明白了)
如有任何提示,我们将不胜感激!
--
此外,ISO 8601 Time Interval Parsing in Java 的答案对我没有任何帮助,因为最重要的答案只关心 PT...
的东西。
拆分这些东西没有真正的反模式。 Oracle 已将这些单独的解析器的职责分开,如果我们想在这种编排中一起使用它们,我们有责任确保以合理的方式再次将各个部分剥离在一起。
就是说,我有一个与核心 Java 8 一起使用并利用 Function
和一些自定义 类 的解决方案。为简洁起见,我将省略自定义 bean,因为它们相当基本,而且主要提升是在 Function
s 中完成的。
请注意,为了让 'Z' 被识别为有效条目,您必须使用 DateTimeFormatter.ISO_DATE_TIME
进行解析。此外,为确保正确选择持续时间,请将 "PT"
添加到适合持续时间的文本。从现有字符串中获取此类详细信息的一种更智能的方法是我留给 reader.
Function<String, Range> convertToRange = (dateString) -> {
String[] dateStringParts = dateString.split("/");
return new Range(LocalDateTime.parse(dateStringParts[0], DateTimeFormatter.ISO_DATE_TIME),
LocalDateTime.parse(dateStringParts[1], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationAndDateTime> convertToDurationAndDateTime = (dateString) -> {
String[] dateStringParts = dateString.split("/");
String[] durationAndPeriodParts = dateStringParts[1].split("T");
return new DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]),
LocalDateTime.parse(dateStringParts[0], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationAndDateTime> convertToDateTimeAndDuration = (dateString) -> {
String[] dateStringParts = dateString.split("/");
String[] durationAndPeriodParts = dateStringParts[0].split("T");
return new DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]),
LocalDateTime.parse(dateStringParts[1], DateTimeFormatter.ISO_DATE_TIME));
};
Function<String, DurationOnly> convertToDurationOnlyRelativeToCurrentTime = (dateString) -> {
String[] durationAndPeriodParts = dateString.split("T");
return new DurationOnly(Period.parse(durationAndPeriodParts[0]),
Duration.parse("PT" + durationAndPeriodParts[1]));
};
很高兴解决您的问题,因为它是介绍 Composite Design Pattern in Functional Programming 的一个很好的例子。您可以将函数组合成 更大 和 强大 的单个函数。例如:
Function<String, Optional<Range<LocalDateTime>>> parser = anyOf(
both(), //case 1
starting(), //case 2
ending(), //case 3
since(LocalDateTime.now()) //case 4
);
Range<LocalDateTime> range = parser.apply("<INPUT>").orElse(null);
//OR using in stream as below
List<Range<LocalDateTime>> result = Stream.of(
"<Case 1>", "<Case 2>", "<Case 3>", "<Case 4>"
).map(parser).filter(Optional::isPresent).map(Optional::get).collect(toList());
下面一步步介绍上面的代码
下面的代码几乎应用了大部分 Design Patterns in OOP. e.g: Composite, Proxy, Adapter, Factory Method 设计模式和.etc.
函数
factory:both
方法满足第一种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> both() {
return parsing((first, second) -> new Range<>(
datetime(first),
datetime(second)
));
}
factory:starting
方法满足第二种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> starting() {
return parsing((first, second) -> {
LocalDateTime start = datetime(first);
return new Range<>(start, start.plus(amount(second)));
});
}
factory:ending
方法满足第三种情况如下:
static Function<String, Optional<Range<LocalDateTime>>> ending() {
return parsing((first, second) -> {
LocalDateTime end = datetime(second);
return new Range<>(end.minus(amount(first)), end);
});
}
factory:since
方法满足最后一种情况如下:
static Function<String,Optional<Range<LocalDateTime>>> since(LocalDateTime start) {
return parsing((amount, __) -> new Range<>(start, start.plus(amount(amount))));
}
composite : anyOf
方法的职责是尽快在 Function
中找到满意的结果:
@SuppressWarnings("ConstantConditions")
static <T, R> Function<T, Optional<R>>
anyOf(Function<T, Optional<R>>... functions) {
return it -> Stream.of(functions).map(current -> current.apply(it))
.filter(Optional::isPresent)
.findFirst().get();
}
adapter: parsing
方法的职责是为特定输入创建一个 parser:
static <R> Function<String, Optional<R>>
parsing(BiFunction<String, String, R> parser) {
return splitting("/", exceptionally(optional(parser), Optional::empty));
}
proxy: exceptionally
方法的职责是处理 Exception
s:
static <T, U, R> BiFunction<T, U, R>
exceptionally(BiFunction<T, U, R> source, Supplier<R> exceptional) {
return (first, second) -> {
try {
return source.apply(first, second);
} catch (Exception ex) {
return exceptional.get();
}
};
}
adapter:splitting
方法的职责是将 BiFunction
转换为 Function
:
static <R> Function<String, R>
splitting(String regex, BiFunction<String, String, R> source) {
return value -> {
String[] parts = value.split(regex);
return source.apply(parts[0], parts.length == 1 ? "" : parts[1]);
};
}
adapter:optional
方法的职责是为最终结果创建一个 Optional
:
static <R> BiFunction<String, String, Optional<R>>
optional(BiFunction<String, String, R> source) {
return (first, last) -> Optional.of(source.apply(first, last));
}
Value Object:
Range
class 用于保存远程对象:
final class Range<T> {
public final T start;
public final T end;
public Range(T start, T end) {
this.start = start;
this.end = end;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Range)) {
return false;
}
Range<?> that = (Range<?>) o;
return Objects.equals(start, that.start) && Objects.equals(end, that.end);
}
@Override
public int hashCode() {
return Objects.hash(start) * 31 + Objects.hash(end);
}
@Override
public String toString() {
return String.format("[%s, %s]", start, end);
}
}
实用工具
datetime
方法从 String
:
LocalDateTime
static LocalDateTime datetime(String datetime) {
return LocalDateTime.parse(
datetime,
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss['Z']")
);
}
amount
方法创建一个 TemporalAmount
,它从 String
中获取 Duration
和 Period
:
static TemporalAmount amount(String text) {
return splitting("T", (first, second) -> new TemporalAmount() {
private Period period= first.isEmpty() ? Period.ZERO : Period.parse(first);
private Duration duration = second.isEmpty() ? Duration.ZERO
: Duration.parse(String.format("PT%s", second));
@Override
public long get(TemporalUnit unit) {
return (period.getUnits().contains(unit) ? period.get(unit) : 0) +
(duration.getUnits().contains(unit) ? duration.get(unit) : 0);
}
@Override
public List<TemporalUnit> getUnits() {
return Stream.of(period, duration).map(TemporalAmount::getUnits)
.flatMap(List::stream)
.collect(toList());
}
@Override
public Temporal addTo(Temporal temporal) {
return period.addTo(duration.addTo(temporal));
}
@Override
public Temporal subtractFrom(Temporal temporal) {
return period.subtractFrom(duration.subtractFrom(temporal));
}
}).apply(text);
}