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

Java—解析日期/期间的优雅方式?,java,datetime,java-8,duration,period,Java,Datetime,Java 8,Duration,Period,根据ISO-8601标准,有4种表示间隔/持续时间的方法: 开始和结束,如“2007-03-01T13:00:00Z/2008-05-11T15:30:00Z” 开始和持续时间,如“2007-03-01T13:00:00Z/P1Y2M10DT2H30M” 持续时间和结束时间,如“P1Y2M10DT2H30M/2008-05-11T15:30:00Z” 仅限持续时间,如“P1Y2M10DT2H30M”,并带有其他上下文信息 仅使用Java8(无Joda、扩展等),是否有处理案例1-3的优雅方式 我

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

  • 开始和结束,如“2007-03-01T13:00:00Z/2008-05-11T15:30:00Z”

  • 开始和持续时间,如“2007-03-01T13:00:00Z/P1Y2M10DT2H30M”

  • 持续时间和结束时间,如“P1Y2M10DT2H30M/2008-05-11T15:30:00Z”

  • 仅限持续时间,如“P1Y2M10DT2H30M”,并带有其他上下文信息

  • 仅使用Java8(无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
    
    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());
    
    我目前的思维过程相当草率,我100%确信有更好的方法。类似于使用嵌套的try/catch块分别处理4个案例,这似乎有点像反模式。(在
    /
    上拆分,解析第一个数据块作为日期,检查错误,解析第一个数据块作为句点,解析第二个数据块作为日期,检查错误……你明白了)

    任何提示都将不胜感激

    --


    另外,上的答案对我没有任何帮助,因为最上面的答案只关心
    PT…
    东西。

    没有真正的反模式来分解这些东西。Oracle已经将这些单独的解析器的职责分开,如果我们想在这种编排中同时使用它们,就要由我们来确保以合理的方式将这些部分重新剥离在一起

    也就是说,我有一个与核心Java8一起工作的解决方案,它使用了
    函数
    和一些自定义类。为了简洁起见,我将省略定制bean,因为它们相当基本,而且主要提升是在
    函数中完成的

    请注意,为了将“Z”识别为有效条目,必须使用
    DateTimeFormatter.ISO\u DATE\u 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]));
    };
    
    函数转换器范围=(日期字符串)->{
    String[]dateStringParts=dateString.split(“/”);
    返回新范围(LocalDateTime.parse(dateStringParts[0],DateTimeFormatter.ISO_DATE_TIME),
    parse(dateStringParts[1],DateTimeFormatter.ISO_DATE_TIME));
    };
    函数convertToDurationAndDateTime=(日期字符串)->{
    String[]dateStringParts=dateString.split(“/”);
    字符串[]durationAndPeriodParts=dateStringParts[1]。拆分(“T”);
    返回新的DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
    parse(“PT”+durationAndPeriodParts[1]),
    parse(dateStringParts[0],DateTimeFormatter.ISO_DATE_TIME));
    };
    函数convertToDateTimeAndDuration=(日期字符串)->{
    String[]dateStringParts=dateString.split(“/”);
    字符串[]durationAndPeriodParts=dateStringParts[0]。拆分(“T”);
    返回新的DurationAndDateTime(Period.parse(durationAndPeriodParts[0]),
    parse(“PT”+durationAndPeriodParts[1]),
    parse(dateStringParts[1],DateTimeFormatter.ISO_DATE_TIME));
    };
    函数convertToDurationOnlyRelativeToCurrentTime=(日期字符串)->{
    String[]durationAndPeriodParts=dateString.split(“T”);
    仅返回新的DurationOnly(Period.parse(durationAndPeriodParts[0]),
    parse(“PT”+durationAndPeriodParts[1]);
    };
    
    我很高兴能解决您的问题,因为这是一个引入in的好例子。您可以将函数组合成更大、更强大的单个函数。例如:

    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
    
    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());
    
    :启动
    方法满足第二种情况,如下所示:

    static Function<String, Optional<Range<LocalDateTime>>> both() {
        return parsing((first, second) -> new Range<>(
                datetime(first),
                datetime(second)
        ));
    }
    
    static Function<String, Optional<Range<LocalDateTime>>> starting() {
            return parsing((first, second) -> {
                LocalDateTime start = datetime(first);
                return new Range<>(start, start.plus(amount(second)));
            });
        }
    
    static Function<String, Optional<Range<LocalDateTime>>> ending() {
        return parsing((first, second) -> {
            LocalDateTime end = datetime(second);
            return new Range<>(end.minus(amount(first)), end);
        });
    }
    
    static Function<String,Optional<Range<LocalDateTime>>> since(LocalDateTime start) {
        return parsing((amount, __) -> new Range<>(start, start.plus(amount(amount))));
    }
    
    :自
    以来的
    方法满足最后一种情况,如下所示:

    static Function<String, Optional<Range<LocalDateTime>>> both() {
        return parsing((first, second) -> new Range<>(
                datetime(first),
                datetime(second)
        ));
    }
    
    static Function<String, Optional<Range<LocalDateTime>>> starting() {
            return parsing((first, second) -> {
                LocalDateTime start = datetime(first);
                return new Range<>(start, start.plus(amount(second)));
            });
        }
    
    static Function<String, Optional<Range<LocalDateTime>>> ending() {
        return parsing((first, second) -> {
            LocalDateTime end = datetime(second);
            return new Range<>(end.minus(amount(first)), end);
        });
    }
    
    static Function<String,Optional<Range<LocalDateTime>>> since(LocalDateTime start) {
        return parsing((amount, __) -> new Range<>(start, start.plus(amount(amount))));
    }
    
    :解析方法的职责是为特定输入创建解析器:

    static <R> Function<String, Optional<R>> 
    parsing(BiFunction<String, String, R> parser) {
        return splitting("/", exceptionally(optional(parser), Optional::empty));
    }
    
    :拆分
    方法的职责是将
    双功能
    转换为
    功能

    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]);
        };
    }
    
    : 用于保存范围内物品的
    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);
        }
    }
    
    amount
    方法创建一个
    TemporalAmount
    ,它从
    字符串中获取
    持续时间
    期间

    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);
    }
    
    static TemporalAmount amount(字符串文本){
    返回拆分(“T”,(第一,第二)->新的TemporalAmount(){
    private Period Period=first.isEmpty()?Period.ZERO:Period.parse(first);
    private Duration Duration=second.isEmpty()?Duration.ZERO
    :Duration.parse(String.format(“PT%s”,秒));
    @凌驾
    公共长廊(临时单元){
    返回(period.getUnits()包含(单位)?period.get(单位):0+
    (duration.getUnits()包含(单位)?duration.get(单位):0;
    }
    @凌驾
    公共列表getUnits(){
    返回流.of(期间,持续时间).map(临时计数::getUnits)
    .flatMap(列表::流)
    .collect(toList());
    }
    @凌驾
    公共时态addTo(时态){
    重现期.addTo(持续时间.addTo(时间));
    }
    @凌驾
    公共时态减去(时态){
    重现期.减去(持续时间.减去(时间));
    }
    }).应用(文本);
    }
    
    Off-topic fun fact:这是第10000个queston标记的java-8。对此没有直接的支持。首先,您必须在斜杠处拆分字符串,
    /
    ,并分别解析每一半。第二,Java将
    期间
    、年、月和日与
    持续时间
    小时和分钟等分开。我想我会考虑使用