diff --git a/src/main/java/org/threeten/extra/Interval.java b/src/main/java/org/threeten/extra/Interval.java index e7fc5358..3350f36e 100644 --- a/src/main/java/org/threeten/extra/Interval.java +++ b/src/main/java/org/threeten/extra/Interval.java @@ -183,6 +183,9 @@ private static Interval parseSplit(CharSequence startStr, CharSequence endStr) { } } // instant followed by instant or duration + if (isUnbounded(startStr)) { + return parseEndDateTime(Instant.MIN, ZoneOffset.UTC, endStr); + } OffsetDateTime start; try { start = OffsetDateTime.parse(startStr); @@ -199,6 +202,10 @@ private static Interval parseSplit(CharSequence startStr, CharSequence endStr) { return parseEndDateTime(start.toInstant(), start.getOffset(), endStr); } + private static boolean isUnbounded(CharSequence sequence) { + return sequence.length() == 2 && sequence.charAt(0) == '.' && sequence.charAt(1) == '.'; + } + // handle case where Instant is outside the bounds of OffsetDateTime private static Interval parseStartExtended(CharSequence startStr, CharSequence endStr) { Instant start = Instant.parse(startStr); @@ -219,6 +226,10 @@ private static Interval parseStartExtended(CharSequence startStr, CharSequence e // parse when there are two date-times private static Interval parseEndDateTime(Instant start, ZoneOffset offset, CharSequence endStr) { + if (isUnbounded(endStr)) { + return Interval.of(start, Instant.MAX); + } + try { TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(endStr, OffsetDateTime::from, LocalDateTime::from); if (temporal instanceof OffsetDateTime) { @@ -708,13 +719,15 @@ public int hashCode() { *

* The output will be the ISO-8601 format formed by combining the * {@code toString()} methods of the two instants, separated by a forward slash. + * Unbounded intervals will have its open end represented by two periods, as in + * {@code 2007-12-03T10:15:30/..}. * - * @return a string representation of this instant, not null + * @return a string representation of this interval, not null */ @Override @ToString public String toString() { - return start.toString() + '/' + end.toString(); + return (isUnboundedStart() ? ".." : getStart()) + "/" + (isUnboundedEnd() ? ".." : getEnd()); } } diff --git a/src/test/java/org/threeten/extra/TestInterval.java b/src/test/java/org/threeten/extra/TestInterval.java index 5f3c4d5f..6acdaa37 100644 --- a/src/test/java/org/threeten/extra/TestInterval.java +++ b/src/test/java/org/threeten/extra/TestInterval.java @@ -196,8 +196,11 @@ public static Object[][] data_parseValid() { {NOW1.atOffset(ZoneOffset.ofHours(2)) + "/" + NOW2.atOffset(ZoneOffset.ofHours(2)).toLocalDateTime(), NOW1, NOW2}, {MIN_OFFSET_DATE_TIME.toString() + "/" + MAX_OFFSET_DATE_TIME, MIN_OFFSET_DATE_TIME, MAX_OFFSET_DATE_TIME}, {NOW1 + "/" + Instant.MAX, NOW1, Instant.MAX}, - {Instant.MIN.toString() + "/" + NOW2, Instant.MIN, NOW2}, - {Instant.MIN.toString() + "/" + Instant.MAX, Instant.MIN, Instant.MAX} + {Instant.MIN + "/" + NOW2, Instant.MIN, NOW2}, + {Instant.MIN + "/" + Instant.MAX, Instant.MIN, Instant.MAX}, + {"../" + NOW2, Instant.MIN, NOW2}, + {NOW1 + "/..", NOW1, Instant.MAX}, + {"../..", Instant.MIN, Instant.MAX} }; } @@ -214,6 +217,16 @@ public void test_parse_CharSequence_badOrder() { assertThrows(DateTimeException.class, () -> Interval.parse(NOW2 + "/" + NOW1)); } + @Test + public void test_parse_CharSequence_illegalOpenStartDuration() { + assertThrows(DateTimeException.class, () -> Interval.parse("../PT1H")); + } + + @Test + public void test_parse_CharSequence_illegalDurationOpenEnd() { + assertThrows(DateTimeException.class, () -> Interval.parse("PT1H/..")); + } + @Test public void test_parse_CharSequence_badFormat() { assertThrows(DateTimeParseException.class, () -> Interval.parse(NOW2 + "-" + NOW1)); @@ -843,4 +856,20 @@ public void test_toString() { assertEquals(NOW1 + "/" + NOW2, test.toString()); } + @Test + public void test_toString_open_end() { + Interval test = Interval.ALL.withStart(NOW1); + assertEquals(NOW1 + "/..", test.toString()); + } + @Test + public void test_toString_open_start() { + Interval test = Interval.ALL.withEnd(NOW2); + assertEquals("../" + NOW2, test.toString()); + } + @Test + public void test_toString_open_all() { + Interval test = Interval.ALL; + assertEquals("../..", test.toString()); + } + }