bridged the gap between device mapping events where "to" is inclusive, and TimeRange, where "to" is exclusive; TimePoint.plus|minus no longer wraps around but bumps

Change-Id: I97f30deb38d83872879c33971616bfcf18a5469a
This commit is contained in:
Axel Uhl committed 2016-12-23 23:10:05 +01:00
1 parent da9e5d20b3
commit d4479a3bc1
17 files changed
+121 -46

No files matched your search

@@ -970,8 +970,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
if (event.getFrom() != null) { if (event.getFrom() != null) {
storeTimePoint(event.getFrom(), result, fromField); storeTimePoint(event.getFrom(), result, fromField);
} }
if (event.getTo() != null) { if (event.getToInclusive() != null) {
storeTimePoint(event.getTo(), result, toField); storeTimePoint(event.getToInclusive(), result, toField);
} }
} }
@@ -1440,7 +1440,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject result = createBasicRegattaLogEventDBObject(event); DBObject result = createBasicRegattaLogEventDBObject(event);
result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT); storeTimePoint(event.getClosingTimePointInclusive(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
storeRegattaLogEvent(regattaLikeId, result); storeRegattaLogEvent(regattaLikeId, result);
} }
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
/** /**
* Closes the time range of a {@link RegattaLogDeviceMappingEvent}. This means that the {@link #getClosingTimePoint() timepoint} provided * Closes the time range of a {@link RegattaLogDeviceMappingEvent}. This means that the {@link #getClosingTimePointInclusive() timepoint} provided
* by this event is substituted for the missing timepoint in the {@code DeviceMappingEvent} it refers to. * by this event is substituted for the missing timepoint in the {@code DeviceMappingEvent} it refers to.
* @author Fredrik Teschke * @author Fredrik Teschke
* *
@@ -15,8 +15,10 @@ import com.sap.sse.common.TimePoint;
public interface RegattaLogCloseOpenEndedDeviceMappingEvent extends RegattaLogEvent, Revokable { public interface RegattaLogCloseOpenEndedDeviceMappingEvent extends RegattaLogEvent, Revokable {
/** /**
* Returns the timepoint that shall substitute the missing one in the corresponding {@link RegattaLogDeviceMappingEvent}. * Returns the timepoint that shall substitute the missing one in the corresponding {@link RegattaLogDeviceMappingEvent}.
* As with {@link RegattaLogDeviceMappingEvent#getToInclusive()}, this time point marks the <em>inclusive</em> end
* of the interval.
*/ */
TimePoint getClosingTimePoint(); TimePoint getClosingTimePointInclusive();
/** /**
* Returns the {@code id} of the event for which this event provides the closing timepoint. * Returns the {@code id} of the event for which this event provides the closing timepoint.
@@ -9,15 +9,16 @@ import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.WithID; import com.sap.sse.common.WithID;
/** /**
* Event type for mapping {@link DeviceIdentifier devices} to {@code items} ({@link Competitor competitors} or {@link Mark marks}). * Event type for mapping {@link DeviceIdentifier devices} to {@code items} ({@link Competitor competitors} or {@link Mark marks}).
* <p> * <p>
* Not both {@link #getFrom()} and {@link #getTo()} may be {@code null} at the same time (which would be a mapping for "all times", * Not both {@link #getFrom()} and {@link #getToInclusive()} may be {@code null} at the same time (which would be a mapping for "all times",
* as the semantics of somehow later limiting this range would become unclear * as the semantics of somehow later limiting this range would become unclear
* <p> * <p>
* If one end of the time range enclosed by {@link #getFrom()} and {@link #getTo()} is open, this can be closed * If one end of the time range enclosed by {@link #getFrom()} and {@link #getToInclusive()} is open, this can be closed
* by a {@link RegattaLogCloseOpenEndedDeviceMappingEvent}. * by a {@link RegattaLogCloseOpenEndedDeviceMappingEvent}.
* *
* @see RegattaLogDefinedMarkAnalyzer for rules by which {@link DeviceMapping}s are derived from these events. * @see RegattaLogDefinedMarkAnalyzer for rules by which {@link DeviceMapping}s are derived from these events.
@@ -29,15 +30,19 @@ public interface RegattaLogDeviceMappingEvent<ItemType extends WithID> extends R
/** /**
* The {@code return} value may be {@code null}, symbolizing an open range. * The {@code return} value may be {@code null}, symbolizing an open range.
* In that case the device should be mapped to the item for any timepoint before that specified by {@link #getTo()}. * In that case the device should be mapped to the item for any timepoint before that specified by {@link #getToInclusive()}.
*/ */
TimePoint getFrom(); TimePoint getFrom();
/** /**
* The {@code return} value may be {@code null}, symbolizing an open range. * The {@code return} value may be {@code null}, symbolizing an open range.
* In that case the device should be mapped to the item for any timepoint after that specified by {@link #getFrom()}. * In that case the device should be mapped to the item for any timepoint after that specified by {@link #getFrom()}.
* Otherwise, this marks the <em>inclusive</em> end of the interval, other than the {@link TimeRange#to()} semantics which
* uses an <em>exclusive</em> interval end. To bridge this gap, whenever mapping this event's from/to interval to a
* {@link TimeRange}, at least the resolution of the {@link TimePoint} representation must be added to the result of
* this method to make it into a valid end of a {@link TimeRange} that includes the time interval covered by this mapping.
*/ */
TimePoint getTo(); TimePoint getToInclusive();
/** /**
* Visitor patter implementation for event mapping handling differentiation. * Visitor patter implementation for event mapping handling differentiation.
@@ -15,23 +15,35 @@ RegattaLogDeviceMappingEvent<ItemType> {
private final ItemType mappedTo; private final ItemType mappedTo;
private final DeviceIdentifier device; private final DeviceIdentifier device;
/**
* inclusive start time point
*/
private final TimePoint from; private final TimePoint from;
private final TimePoint to;
/**
* inclusive end time point
*/
private final TimePoint toInclusive;
/**
* @param from inclusive
* @param toInclusive inclusive
*/
public RegattaLogDeviceMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint, public RegattaLogDeviceMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable id, ItemType mappedTo, AbstractLogEventAuthor author, Serializable id, ItemType mappedTo,
DeviceIdentifier device, TimePoint from, TimePoint to) { DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
super(createdAt, logicalTimePoint, author, id); super(createdAt, logicalTimePoint, author, id);
this.mappedTo = mappedTo; this.mappedTo = mappedTo;
this.device = device; this.device = device;
this.from = from; this.from = from;
this.to = to; this.toInclusive = toInclusive;
} }
public RegattaLogDeviceMappingEventImpl(TimePoint logicalTimePoint, public RegattaLogDeviceMappingEventImpl(TimePoint logicalTimePoint,
AbstractLogEventAuthor author, ItemType mappedTo, AbstractLogEventAuthor author, ItemType mappedTo,
DeviceIdentifier device, TimePoint from, TimePoint to) { DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
this(now(), logicalTimePoint, author, randId(), mappedTo, device, from, to); this(now(), logicalTimePoint, author, randId(), mappedTo, device, from, toInclusive);
} }
public ItemType getMappedTo() { public ItemType getMappedTo() {
@@ -43,12 +55,12 @@ RegattaLogDeviceMappingEvent<ItemType> {
public TimePoint getFrom() { public TimePoint getFrom() {
return from; return from;
} }
public TimePoint getTo() { public TimePoint getToInclusive() {
return to; return toInclusive;
} }
@Override @Override
public String getShortInfo() { public String getShortInfo() {
return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + to; return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + toInclusive;
} }
} }
@@ -33,7 +33,7 @@ public class RegattaLogCloseOpenEndedDeviceMappingEventImpl extends AbstractLogE
} }
@Override @Override
public TimePoint getClosingTimePoint() { public TimePoint getClosingTimePointInclusive() {
return closingTimePoint; return closingTimePoint;
} }
@@ -15,13 +15,13 @@ public class RegattaLogDeviceCompetitorMappingEventImpl extends RegattaLogDevice
RegattaLogDeviceCompetitorMappingEvent { RegattaLogDeviceCompetitorMappingEvent {
public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint, public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device, AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device,
TimePoint from, TimePoint to) { TimePoint from, TimePoint toInclusive) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to); super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, toInclusive);
} }
public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author, public RegattaLogDeviceCompetitorMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint toInclusive) {
super(logicalTimePoint, author, mappedTo, device, from, to); super(logicalTimePoint, author, mappedTo, device, from, toInclusive);
} }
private static final long serialVersionUID = -1494030544804758753L; private static final long serialVersionUID = -1494030544804758753L;
@@ -51,8 +51,8 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
for (final RegattaLogDeviceMappingEvent<ItemT> event : events.get(item)) { for (final RegattaLogDeviceMappingEvent<ItemT> event : events.get(item)) {
final TimePoint from = event.getFrom(); final TimePoint from = event.getFrom();
final RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = closingEvents.get(event.getId()); final RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = closingEvents.get(event.getId());
final TimePoint to = closingEvent != null ? closingEvent.getClosingTimePoint() : event.getTo(); final TimePoint toInclusive = closingEvent != null ? closingEvent.getClosingTimePointInclusive() : event.getToInclusive();
final TimeRange mappingTimeRange = new TimeRangeImpl(from, to); final TimeRange mappingTimeRange = new TimeRangeImpl(from, toInclusive.plus(1) /* convert from inclusive to exclusive end */);
if (mappingTimeRange.includes(fixTimePoint)) { if (mappingTimeRange.includes(fixTimePoint)) {
if (closingEvent != null) { if (closingEvent != null) {
log.revokeEvent(closingEvent.getAuthor(), closingEvent, log.revokeEvent(closingEvent.getAuthor(), closingEvent,
@@ -65,8 +65,8 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
if (!endOfFirstHalf.before(from)) { if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice())); log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
} }
if (to == null || !to.before(startOfSecondHalf)) { if (toInclusive == null || !toInclusive.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to, log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, toInclusive,
event.getDevice())); event.getDevice()));
} }
} }
@@ -74,6 +74,6 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
} }
protected abstract RegattaLogDeviceMappingEvent<ItemT> createDeviceMappingEvent(ItemT item, protected abstract RegattaLogDeviceMappingEvent<ItemT> createDeviceMappingEvent(ItemT item,
AbstractLogEventAuthor author, TimePoint plus, TimePoint to, DeviceIdentifier deviceId); AbstractLogEventAuthor author, TimePoint plus, TimePoint toInclusive, DeviceIdentifier deviceId);
} }
@@ -25,8 +25,8 @@ public class RegattaLogDeviceCompetitorMappingFinder extends BaseRegattaLogDevic
@Override @Override
protected RegattaLogDeviceCompetitorMappingEvent createDeviceMappingEvent(Competitor item, AbstractLogEventAuthor author, protected RegattaLogDeviceCompetitorMappingEvent createDeviceMappingEvent(Competitor item, AbstractLogEventAuthor author,
TimePoint from, TimePoint to, DeviceIdentifier deviceId) { TimePoint from, TimePoint toInclusive, DeviceIdentifier deviceId) {
final TimePoint now = MillisecondsTimePoint.now(); final TimePoint now = MillisecondsTimePoint.now();
return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to); return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, toInclusive);
} }
} }
@@ -48,8 +48,8 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID>
} }
protected DeviceMappingWithRegattaLogEvent<ItemT> createMapping(DeviceIdentifier device, ItemT item, protected DeviceMappingWithRegattaLogEvent<ItemT> createMapping(DeviceIdentifier device, ItemT item,
TimePoint from, TimePoint to, Serializable originalEventId, RegattaLogDeviceMappingEvent<ItemT> event) { TimePoint from, TimePoint toInclusive, Serializable originalEventId, RegattaLogDeviceMappingEvent<ItemT> event) {
return new DeviceMappingWithRegattaLogEventImpl<ItemT>(item, device, new TimeRangeImpl(from, to), return new DeviceMappingWithRegattaLogEventImpl<ItemT>(item, device, new TimeRangeImpl(from, toInclusive.plus(1) /* TimeRange uses exclusive end */),
Collections.singletonList(originalEventId), event); Collections.singletonList(originalEventId), event);
} }
@@ -95,16 +95,16 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID>
List<DeviceMappingWithRegattaLogEvent<ItemT>> result = new ArrayList<>(); List<DeviceMappingWithRegattaLogEvent<ItemT>> result = new ArrayList<>();
for (final RegattaLogDeviceMappingEvent<ItemT> event : events) { for (final RegattaLogDeviceMappingEvent<ItemT> event : events) {
TimePoint from = event.getFrom(); TimePoint from = event.getFrom();
TimePoint to = event.getTo(); TimePoint toInclusive = event.getToInclusive();
TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId()) TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId())
.getClosingTimePoint() : null; .getClosingTimePointInclusive() : null;
if (from == null) { if (from == null) {
logger.severe("No start time set for DeviceMappingEvent with ID: " + event.getId()); logger.severe("No start time set for DeviceMappingEvent with ID: " + event.getId());
} }
if (to == null) { if (toInclusive == null) {
to = closingTimePoint; toInclusive = closingTimePoint;
} }
result.add(createMapping(event.getDevice(), item, from, to, event.getId(), event)); result.add(createMapping(event.getDevice(), item, from, toInclusive, event.getId(), event));
} }
return result; return result;
} }
@@ -46,7 +46,7 @@ public class RegattaLogOpenEndedDeviceMappingCloser extends
for (Serializable eventId : mapping.getOriginalRaceLogEventIds()) { for (Serializable eventId : mapping.getOriginalRaceLogEventIds()) {
RegattaLogDeviceMappingEvent<?> event = (RegattaLogDeviceMappingEvent<?>) getLog().getEventById(eventId); RegattaLogDeviceMappingEvent<?> event = (RegattaLogDeviceMappingEvent<?>) getLog().getEventById(eventId);
if (event.getFrom() == null || event.getTo() == null) { if (event.getFrom() == null || event.getToInclusive() == null) {
result.add(createCloseEvent(MillisecondsTimePoint.now(), event.getId())); result.add(createCloseEvent(MillisecondsTimePoint.now(), event.getId()));
} }
} }
@@ -66,7 +66,8 @@ public class DeviceMappingImpl<ItemType extends WithID> implements DeviceMapping
} }
public static <T extends WithID> DeviceMapping<T> convertToDeviceMapping(RegattaLogDeviceMappingEvent<T> event) { public static <T extends WithID> DeviceMapping<T> convertToDeviceMapping(RegattaLogDeviceMappingEvent<T> event) {
return new DeviceMappingImpl<T>(event.getMappedTo(), event.getDevice(), new TimeRangeImpl(event.getFrom(), event.getTo()), return new DeviceMappingImpl<T>(event.getMappedTo(), event.getDevice(),
new TimeRangeImpl(event.getFrom(), event.getToInclusive().plus(1) /* TimeRange uses exclusive end */),
event.getId(), event.getClass()); event.getId(), event.getClass());
} }
@@ -11,6 +11,7 @@ import java.util.GregorianCalendar;
import org.junit.Test; import org.junit.Test;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -39,12 +40,34 @@ public class TimePointTest {
} }
@Test @Test
public void testEqualForEuqal() { public void testEqualForEqual() {
TimePoint t1 = new MillisecondsTimePoint(Long.MIN_VALUE); TimePoint t1 = new MillisecondsTimePoint(Long.MIN_VALUE);
TimePoint t2 = new MillisecondsTimePoint(Long.MIN_VALUE); TimePoint t2 = new MillisecondsTimePoint(Long.MIN_VALUE);
assertThat(t1, is(t2)); assertThat(t1, is(t2));
assertThat(t2, is(t1)); assertThat(t2, is(t1));
} }
@Test
public void testAvoidOverflowBeyondEndOfTime() {
TimePoint t1 = TimePoint.EndOfTime;
assertEquals(TimePoint.EndOfTime, t1.plus(1));
assertEquals(TimePoint.EndOfTime, t1.plus(100));
assertEquals(TimePoint.EndOfTime, t1.plus(Duration.ONE_MINUTE));
TimePoint t2 = TimePoint.EndOfTime.minus(100);
assertEquals(TimePoint.EndOfTime, t2.plus(200));
assertEquals(TimePoint.EndOfTime, t2.plus(Duration.ONE_HOUR));
}
@Test
public void testAvoidUnderflowBeyondBeginningOfTime() {
TimePoint t1 = TimePoint.BeginningOfTime;
assertEquals(TimePoint.BeginningOfTime, t1.minus(1));
assertEquals(TimePoint.BeginningOfTime, t1.minus(100));
assertEquals(TimePoint.BeginningOfTime, t1.minus(Duration.ONE_MINUTE));
TimePoint t2 = TimePoint.BeginningOfTime.plus(100);
assertEquals(TimePoint.BeginningOfTime, t2.minus(200));
assertEquals(TimePoint.BeginningOfTime, t2.minus(Duration.ONE_HOUR));
}
@Test @Test
public void testEqualForNull() { public void testEqualForNull() {
@@ -134,7 +134,7 @@ public class TopLevelMasterData implements Serializable {
relevantFixes.put(device, new HashSet<>()); relevantFixes.put(device, new HashSet<>());
} }
Set<Timed> fixes = relevantFixes.get(device); Set<Timed> fixes = relevantFixes.get(device);
sensorFixStore.loadFixes(fixes::add, mappingEvent.getDevice(), mappingEvent.getFrom(), mappingEvent.getTo(), sensorFixStore.loadFixes(fixes::add, mappingEvent.getDevice(), mappingEvent.getFrom(), mappingEvent.getToInclusive(),
true); true);
} }
@@ -738,7 +738,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTra
} }
if (intervalStart == null) { if (intervalStart == null) {
if (beforeFixIter.hasNext()) { if (beforeFixIter.hasNext()) {
// No before fix within half averaging interval, but there is one further away; is its next neighbour even further away? // No before fix within half averaging interval, but there is one further away; is its next neighbor even further away?
// If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix' // If so, or no more neighbours are found, it's affected and marks the invalidation interval start; otherwise, fix'
// time point is the invalidation interval start // time point is the invalidation interval start
TimePoint intervalStartCandidate = beforeFixIter.next().getTimePoint(); TimePoint intervalStartCandidate = beforeFixIter.next().getTimePoint();
@@ -783,7 +783,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTra
} finally { } finally {
unlockAfterRead(); unlockAfterRead();
} }
return new TimeRangeImpl(intervalStart, intervalEnd); return new TimeRangeImpl(intervalStart, intervalEnd.plus(1) /* need to add one millisecond to make intervalEnd included in time range */);
} }
protected FixType createDummyGPSFix(TimePoint at) { protected FixType createDummyGPSFix(TimePoint at) {
@@ -16,12 +16,32 @@ public interface TimePoint extends Comparable<TimePoint>, Serializable {
Date asDate(); Date asDate();
/**
* @return a time point that is {@code milliseconds} later than {@code this} time point, except in case the
* resulting time point would be after the {@link #EndOfTime} in which case {@link #EndOfTime} is returned
* instead of provoking an overflow with wrap-around.
*/
TimePoint plus(long milliseconds); TimePoint plus(long milliseconds);
/**
* @return a time point that is {@code duration} later than {@code this} time point, except in case the
* resulting time point would be after the {@link #EndOfTime} in which case {@link #EndOfTime} is returned
* instead of provoking an overflow with wrap-around.
*/
TimePoint plus(Duration duration); TimePoint plus(Duration duration);
/**
* @return a time point that is {@code milliseconds} earlier than {@code this} time point, except in case the
* resulting time point would be before the {@link #BeginningOfTime} in which case {@link #BeginningOfTime} is returned
* instead of provoking an underflow with wrap-around.
*/
TimePoint minus(long milliseconds); TimePoint minus(long milliseconds);
/**
* @return a time point that is {@code duration} earlier than {@code this} time point, except in case the
* resulting time point would be before the {@link #BeginningOfTime} in which case {@link #BeginningOfTime} is returned
* instead of provoking an underflow with wrap-around.
*/
TimePoint minus(Duration duration); TimePoint minus(Duration duration);
/** /**
@@ -64,7 +64,19 @@ public abstract class AbstractTimePoint implements TimePoint {
@Override @Override
public TimePoint plus(long milliseconds) { public TimePoint plus(long milliseconds) {
return new MillisecondsTimePoint(asMillis()+milliseconds); final TimePoint result;
if (milliseconds > 0) {
if (EndOfTime.asMillis()-milliseconds >= asMillis()) {
result = new MillisecondsTimePoint(asMillis()+milliseconds);
} else {
result = EndOfTime;
}
} else if (asMillis() < milliseconds) {
result = BeginningOfTime;
} else {
result = new MillisecondsTimePoint(asMillis()+milliseconds);
}
return result;
} }
@Override @Override
@@ -21,13 +21,13 @@ public class TimeRangeImpl extends Util.Pair<TimePoint, TimePoint> implements Ti
* if {@code null}, the time range is considered open on its "left" end, and all {@link TimePoint}s at or * if {@code null}, the time range is considered open on its "left" end, and all {@link TimePoint}s at or
* after {@link TimePoint#BeginningOfTime} and at or before {@code to} are considered {@link #includes * after {@link TimePoint#BeginningOfTime} and at or before {@code to} are considered {@link #includes
* included} in this time range. * included} in this time range.
* @param to * @param toExclusive
* if {@code null}, the time range is considered open on its "right" end, and all {@link TimePoint}s at * if {@code null}, the time range is considered open on its "right" end, and all {@link TimePoint}s at
* or before {@link TimePoint#EndOfTime} and at or after {@code from} are considered {@link #includes * or before {@link TimePoint#EndOfTime} and at or after {@code from} are considered {@link #includes
* included} in this time range. * included} in this time range.
*/ */
public TimeRangeImpl(TimePoint from, TimePoint to) { public TimeRangeImpl(TimePoint from, TimePoint toExclusive) {
super(from == null ? TimePoint.BeginningOfTime : from, to == null ? TimePoint.EndOfTime : to); super(from == null ? TimePoint.BeginningOfTime : from, toExclusive == null ? TimePoint.EndOfTime : toExclusive);
if (from().after(to())) { if (from().after(to())) {
throw new IllegalArgumentException("from " + from() + " must lie before to " + to() + " in a TimeRange"); throw new IllegalArgumentException("from " + from() + " must lie before to " + to() + " in a TimeRange");
} }