Skip to content

Commit

Permalink
Address review comments. More explicitly handle open vs closed ranges.
Browse files Browse the repository at this point in the history
  • Loading branch information
chipkent committed Dec 6, 2023
1 parent 0f08c74 commit 282e6ea
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private static TimeRange<LocalTime>[] parseBusinessRanges(final List<Element> bu
DateTimeUtils.parseLocalTime(getText(getRequiredChild(businessRanges.get(i), "open")));
final LocalTime close =
DateTimeUtils.parseLocalTime(getText(getRequiredChild(businessRanges.get(i), "close")));
rst[i] = new TimeRange<>(open, close);
rst[i] = new TimeRange<>(open, close, true);
}

return rst;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ public class CalendarDay<T extends Comparable<T> & Temporal> {
for (int i = 1; i < ranges.length; i++) {
final TimeRange<T> p0 = ranges[i - 1];
final TimeRange<T> p1 = ranges[i];
final int cmp = p1.start().compareTo(p0.end());

if (p1.start().compareTo(p0.end()) < 0) {
if (cmp < 0 || (cmp == 0 && p0.isInclusiveEnd())) {
throw new IllegalArgumentException("Business time ranges overlap.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@
public class TimeRange<T extends Comparable<T> & Temporal> {
private final T start;
private final T end;
private final boolean inclusiveEnd;

/**
* Create a new time range.
*
* @param startTime start of the time range.
* @param endTime end of the time range.
* @param inclusiveEnd is the end time inclusive?
*/
TimeRange(final T startTime, final T endTime) {
TimeRange(final T startTime, final T endTime, final boolean inclusiveEnd) {
this.start = startTime;
this.end = endTime;
this.inclusiveEnd = inclusiveEnd;

if (startTime == null || endTime == null) {
throw new IllegalArgumentException("Null argument: startTime=" + startTime + " endTime=" + endTime);
Expand Down Expand Up @@ -63,13 +66,22 @@ public T end() {
return end;
}

/**
* Is the end time inclusive?
*
* @return is the end time inclusive?
*/
public boolean isInclusiveEnd() {
return inclusiveEnd;
}

/**
* Length of the range in nanoseconds.
*
* @return length of the range in nanoseconds
*/
public long nanos() {
return start.until(end, ChronoUnit.NANOS);
return start.until(end, ChronoUnit.NANOS) - (inclusiveEnd ? 0 : 1);
}

/**
Expand All @@ -88,9 +100,15 @@ public Duration duration() {
* @return true if the time is in this range; otherwise, false.
*/
public boolean contains(final T time) {
return time != null
&& start.compareTo(time) <= 0
&& time.compareTo(end) <= 0;
if(inclusiveEnd) {
return time != null
&& start.compareTo(time) <= 0
&& time.compareTo(end) <= 0;
}else {
return time != null
&& start.compareTo(time) <= 0
&& time.compareTo(end) < 0;
}
}

@Override
Expand All @@ -100,19 +118,20 @@ public boolean equals(Object o) {
if (!(o instanceof TimeRange))
return false;
TimeRange<?> that = (TimeRange<?>) o;
return start.equals(that.start) && end.equals(that.end);
return start.equals(that.start) && end.equals(that.end) && inclusiveEnd == that.inclusiveEnd;
}

@Override
public int hashCode() {
return Objects.hash(start, end);
return Objects.hash(start, end, inclusiveEnd);
}

@Override
public String toString() {
return "TimeRange{" +
"start=" + start +
", end=" + end +
", inclusiveEnd=" + inclusiveEnd +
'}';
}

Expand All @@ -127,7 +146,7 @@ public String toString() {
public static TimeRange<Instant> toInstant(final TimeRange<LocalTime> p, final LocalDate date,
final ZoneId timeZone) {
return new TimeRange<>(DateTimeUtils.toInstant(date, p.start, timeZone),
DateTimeUtils.toInstant(date, p.end, timeZone));
DateTimeUtils.toInstant(date, p.end, timeZone), p.inclusiveEnd);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
public class TestBusinessCalendar extends TestCalendar {
private final LocalDate firstValidDate = LocalDate.of(2000, 1, 1);
private final LocalDate lastValidDate = LocalDate.of(2050, 12, 31);
private final TimeRange<LocalTime> period = new TimeRange<>(LocalTime.of(9, 0), LocalTime.of(12, 15));
private final TimeRange<LocalTime> periodHalf = new TimeRange<>(LocalTime.of(9, 0), LocalTime.of(11, 7));
private final TimeRange<LocalTime> period = new TimeRange<>(LocalTime.of(9, 0), LocalTime.of(12, 15), true);
private final TimeRange<LocalTime> periodHalf = new TimeRange<>(LocalTime.of(9, 0), LocalTime.of(11, 7), true);
private final CalendarDay<LocalTime> schedule = new CalendarDay<>(new TimeRange[] {period});
private final Set<DayOfWeek> weekendDays = Set.of(DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY);
private final LocalDate holidayDate1 = LocalDate.of(2023, 7, 4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
public class TestCalendarDay extends BaseArrayTestCase {
private final Instant open1 = DateTimeUtils.parseInstant("2017-03-11T10:00:00.000000000 NY");
private final Instant close1 = DateTimeUtils.parseInstant("2017-03-11T11:00:00.000000000 NY");
private final TimeRange<Instant> period1 = new TimeRange<>(open1, close1);
private final TimeRange<Instant> period1 = new TimeRange<>(open1, close1, true);
private final Instant open2 = DateTimeUtils.parseInstant("2017-03-11T12:00:00.000000000 NY");
private final Instant close2 = DateTimeUtils.parseInstant("2017-03-11T17:00:00.000000000 NY");
private final TimeRange<Instant> period2 = new TimeRange<>(open2, close2);
private final TimeRange<Instant> period2 = new TimeRange<>(open2, close2, true);

public void testEmpty() {
final CalendarDay<Instant> empty = new CalendarDay<>();
Expand Down Expand Up @@ -130,8 +130,8 @@ public void testPeriodsOverlap() {
}

public void testToInstant() {
final TimeRange<LocalTime> p1 = new TimeRange<>(LocalTime.of(1, 2), LocalTime.of(3, 4));
final TimeRange<LocalTime> p2 = new TimeRange<>(LocalTime.of(5, 6), LocalTime.of(7, 8));
final TimeRange<LocalTime> p1 = new TimeRange<>(LocalTime.of(1, 2), LocalTime.of(3, 4), true);
final TimeRange<LocalTime> p2 = new TimeRange<>(LocalTime.of(5, 6), LocalTime.of(7, 8), true);

final CalendarDay<LocalTime> local = new CalendarDay<>(new TimeRange[] {p1, p2});
final LocalDate date = LocalDate.of(2017, 3, 11);
Expand All @@ -152,7 +152,7 @@ public void testEqualsHash() {

final CalendarDay<Instant> multi2 = new CalendarDay<>(new TimeRange[] {period1, period2});
final CalendarDay<Instant> multi3 = new CalendarDay<>(new TimeRange[] {period1,
new TimeRange<>(open2, DateTimeUtils.parseInstant("2017-03-11T17:01:00.000000000 NY"))});
new TimeRange<>(open2, DateTimeUtils.parseInstant("2017-03-11T17:01:00.000000000 NY"), true)});
assertEquals(multi, multi);
assertEquals(multi, multi2);
assertNotEquals(multi, multi3);
Expand All @@ -162,7 +162,7 @@ public void testEqualsHash() {
public void testToString() {
final CalendarDay<Instant> multi = new CalendarDay<>(new TimeRange[] {period1, period2});
assertEquals(
"CalendarDay{businessTimeRanges=[TimeRange{start=2017-03-11T15:00:00Z, end=2017-03-11T16:00:00Z}, TimeRange{start=2017-03-11T17:00:00Z, end=2017-03-11T22:00:00Z}]}",
"CalendarDay{businessTimeRanges=[TimeRange{start=2017-03-11T15:00:00Z, end=2017-03-11T16:00:00Z, inclusiveEnd=true}, TimeRange{start=2017-03-11T17:00:00Z, end=2017-03-11T22:00:00Z, inclusiveEnd=true}]}",
multi.toString());
}
}
127 changes: 109 additions & 18 deletions engine/time/src/test/java/io/deephaven/time/calendar/TestTimeRange.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,42 @@

public class TestTimeRange extends BaseArrayTestCase {

public void testTimeRange() {
public void testTimeRangeInclusive() {
final Instant open1 = DateTimeUtils.parseInstant("2017-03-11T10:00:00.000000000 NY");
final Instant close1 = DateTimeUtils.parseInstant("2017-03-11T11:00:00.000000000 NY");

try {
new TimeRange<>(null, close1);
new TimeRange<>(null, close1, true);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("null"));
}

try {
new TimeRange<>(close1, null);
new TimeRange<>(close1, null, true);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("null"));
}

try {
new TimeRange<>(close1, open1);
new TimeRange<>(close1, open1, true);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("after"));
}

try {
new TimeRange<>(open1, open1);
new TimeRange<>(open1, open1, true);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("same"));
}

TimeRange<Instant> period = new TimeRange<>(open1, close1);
TimeRange<Instant> period = new TimeRange<>(open1, close1, true);
assertEquals(open1, period.start());
assertEquals(close1, period.end());
assertTrue(period.isInclusiveEnd());
assertEquals(DateTimeUtils.HOUR, period.nanos());
assertEquals(Duration.ofNanos(DateTimeUtils.HOUR), period.duration());

Expand All @@ -64,41 +65,131 @@ public void testTimeRange() {
.contains(DateTimeUtils.epochNanosToInstant(DateTimeUtils.epochNanos(close1) + DateTimeUtils.MINUTE)));
}

public void testToInstant() {
public void testTimeRangeExclusive() {
final Instant open1 = DateTimeUtils.parseInstant("2017-03-11T10:00:00.000000000 NY");
final Instant close1 = DateTimeUtils.parseInstant("2017-03-11T11:00:00.000000000 NY");

try {
new TimeRange<>(null, close1, false);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("null"));
}

try {
new TimeRange<>(close1, null, false);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("null"));
}

try {
new TimeRange<>(close1, open1, false);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("after"));
}

try {
new TimeRange<>(open1, open1, false);
TestCase.fail("Expected an exception");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("same"));
}

TimeRange<Instant> period = new TimeRange<>(open1, close1, false);
assertEquals(open1, period.start());
assertEquals(close1, period.end());
assertFalse(period.isInclusiveEnd());
assertEquals(DateTimeUtils.HOUR-1, period.nanos());
assertEquals(Duration.ofNanos(DateTimeUtils.HOUR-1), period.duration());

assertTrue(period.contains(open1));
assertTrue(period
.contains(DateTimeUtils.epochNanosToInstant(DateTimeUtils.epochNanos(open1) + DateTimeUtils.MINUTE)));
assertFalse(period
.contains(DateTimeUtils.epochNanosToInstant(DateTimeUtils.epochNanos(open1) - DateTimeUtils.MINUTE)));
assertTrue(period.contains(close1.minusNanos(1)));
assertFalse(period.contains(close1));
assertTrue(period
.contains(DateTimeUtils.epochNanosToInstant(DateTimeUtils.epochNanos(close1) - DateTimeUtils.MINUTE)));
assertFalse(period
.contains(DateTimeUtils.epochNanosToInstant(DateTimeUtils.epochNanos(close1) + DateTimeUtils.MINUTE)));
}

public void testToInstantInclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);

final TimeRange<LocalTime> local = new TimeRange<>(start, end);
final TimeRange<LocalTime> local = new TimeRange<>(start, end, true);

final LocalDate date = LocalDate.of(2017, 3, 11);
final ZoneId timeZone = ZoneId.of("America/Los_Angeles");
final Instant targetStart = date.atTime(start).atZone(timeZone).toInstant();
final Instant targetEnd = date.atTime(end).atZone(timeZone).toInstant();

final TimeRange<Instant> target = new TimeRange<>(targetStart, targetEnd);
final TimeRange<Instant> target = new TimeRange<>(targetStart, targetEnd, true);
final TimeRange<Instant> rst = TimeRange.toInstant(local, date, timeZone);
assertEquals(target, rst);
}

public void testEqualsHash() {
public void testToInstantExclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end);
final TimeRange<LocalTime> p2 = new TimeRange<>(start, end);
final TimeRange<LocalTime> p3 = new TimeRange<>(LocalTime.of(0, 1), end);
final TimeRange<LocalTime> p4 = new TimeRange<>(start, LocalTime.of(8, 9));

assertEquals(p1.hashCode(), Objects.hash(start, end, p1.nanos()));
final TimeRange<LocalTime> local = new TimeRange<>(start, end, false);

final LocalDate date = LocalDate.of(2017, 3, 11);
final ZoneId timeZone = ZoneId.of("America/Los_Angeles");
final Instant targetStart = date.atTime(start).atZone(timeZone).toInstant();
final Instant targetEnd = date.atTime(end).atZone(timeZone).toInstant();

final TimeRange<Instant> target = new TimeRange<>(targetStart, targetEnd, false);
final TimeRange<Instant> rst = TimeRange.toInstant(local, date, timeZone);
assertEquals(target, rst);
}

public void testEqualsHashInclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end, true);
final TimeRange<LocalTime> p2 = new TimeRange<>(start, end, true);
final TimeRange<LocalTime> p3 = new TimeRange<>(LocalTime.of(0, 1), end, true);
final TimeRange<LocalTime> p4 = new TimeRange<>(start, LocalTime.of(8, 9), true);

assertEquals(p1.hashCode(), Objects.hash(start, end, true));
assertEquals(p1, p1);
assertEquals(p1, p2);
assertNotEquals(p1, p3);
assertNotEquals(p1, p4);
}

public void testEqualsHashExclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end, false);
final TimeRange<LocalTime> p2 = new TimeRange<>(start, end, false);
final TimeRange<LocalTime> p3 = new TimeRange<>(LocalTime.of(0, 1), end, false);
final TimeRange<LocalTime> p4 = new TimeRange<>(start, LocalTime.of(8, 9), false);

assertEquals(p1.hashCode(), Objects.hash(start, end, false));
assertEquals(p1, p1);
assertEquals(p1, p2);
assertNotEquals(p1, p3);
assertNotEquals(p1, p4);
}

public void testToString() {
public void testToStringInclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end, true);
assertEquals("TimeRange{start=01:02:03, end=07:08:09, inclusiveEnd=true}", p1.toString());
}

public void testToStringExclusive() {
final LocalTime start = LocalTime.of(1, 2, 3);
final LocalTime end = LocalTime.of(7, 8, 9);
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end);
assertEquals("TimeRange{start=01:02:03, end=07:08:09}", p1.toString());
final TimeRange<LocalTime> p1 = new TimeRange<>(start, end, false);
assertEquals("TimeRange{start=01:02:03, end=07:08:09, inclusiveEnd=false}", p1.toString());
}
}

0 comments on commit 282e6ea

Please sign in to comment.