Java - 解析日期/期间的优雅方式?

时间:2017-06-19 03:59:58

标签: java datetime java-8 duration period

根据ISO-8601标准,有4种表达间隔/持续时间的方法:

  1. 开始和结束,例如" 2007-03-01T13:00:00Z / 2008-05-11T15:30:00Z"

  2. 开始和持续时间,例如" 2007-03-01T13:00:00Z / P1Y2M10DT2H30M"

  3. 持续时间和结束,例如" P1Y2M10DT2H30M / 2008-05-11T15:30:00Z"

  4. 仅限持续时间,例如" P1Y2M10DT2H30M",以及其他背景信息

  5. 仅使用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...的内容。

2 个答案:

答案 0 :(得分:6)

没有真正的反模式来分裂这些东西。 Oracle已经将这些单独解析器的职责分开了,如果我们想在这种编排中一起使用它们,我们应该确保以合理的方式再次将这些部分组合在一起。

也就是说,我有一个与核心Java 8兼容的解决方案,并使用Function和一些自定义类。为了简洁,我将省略自定义bean,因为它们是相当基本的,以及主要提升在Function中完成的事实。

请注意,为了将'Z'识别为有效条目,您必须使用DateTimeFormatter.ISO_DATE_TIME进行解析。此外,为了确保正确选择您的持续时间,请在"PT"前加上适合持续时间的文本。从你现有的字符串中获取这种细节的更聪明的方法是我留给读者的练习。

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]));
};

答案 1 :(得分:2)

我很乐意解决您的问题,因为这是在Composite Design Pattern中引入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());

让我们逐步介绍上面的代码

下面的代码几乎适用于OOP中Design Patterns的大部分内容。例如:CompositeProxyAdapterFactory Method设计模式和.etc。

功能

factoryboth方法符合第一种情况,如下所示:

static Function<String, Optional<Range<LocalDateTime>>> both() {
    return parsing((first, second) -> new Range<>(
            datetime(first),
            datetime(second)
    ));
}

factorystarting方法符合第二种情况,如下所示:

static Function<String, Optional<Range<LocalDateTime>>> starting() {
        return parsing((first, second) -> {
            LocalDateTime start = datetime(first);
            return new Range<>(start, start.plus(amount(second)));
        });
    }

factoryending方法符合第三种情况,如下所示:

static Function<String, Optional<Range<LocalDateTime>>> ending() {
    return parsing((first, second) -> {
        LocalDateTime end = datetime(second);
        return new Range<>(end.minus(amount(first)), end);
    });
}

factorysince方法符合以下最后一种情况:

static Function<String,Optional<Range<LocalDateTime>>> since(LocalDateTime start) {
    return parsing((amount, __) -> new Range<>(start, start.plus(amount(amount))));
}

compositeanyOf方法的责任是尽快在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();
}

adapterparsing方法的责任是为某个输入创建一个解析器

static <R> Function<String, Optional<R>> 
parsing(BiFunction<String, String, R> parser) {
    return splitting("/", exceptionally(optional(parser), Optional::empty));
}

proxyexceptionally方法的责任在于处理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();
        }
    };
}

adaptersplitting方法的责任是将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]);
    };
}

adapteroptional方法的责任是为最终结果创建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类:

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方法从LocalDateTime

创建String
static LocalDateTime datetime(String datetime) {
    return LocalDateTime.parse(
            datetime, 
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss['Z']")
    );
}    

amount方法会创建TemporalAmount,同时从Duration获取PeriodString

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);
}