Merge pull request #1646 from jamesagnew/1645-implement-daterangeparam-isdatewithinrange

Resolve "Implement DateRangeParam.isDateWithinRange(Date theDate)."
This commit is contained in:
Diederik Muylwyk 2019-12-30 15:38:42 -05:00 committed by GitHub
commit eb294f92c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 331 additions and 16 deletions

View File

@ -215,6 +215,15 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
* This lower bound is assumed to have a <code>ge</code> * This lower bound is assumed to have a <code>ge</code>
* (greater than or equals) modifier. * (greater than or equals) modifier.
* </p> * </p>
* <p>
* Note: An operation can take a DateRangeParam. If only a single date is provided,
* it will still result in a DateRangeParam where the lower and upper bounds
* are the same value. As such, even though the prefixes for the lower and
* upper bounds default to <code>ge</code> and <code>le</code> respectively,
* the resulting prefix is effectively <code>eq</code> where only a single
* date is provided - as required by the FHIR specificiation (i.e. "If no
* prefix is present, the prefix <code>eq</code> is assumed").
* </p>
*/ */
public DateRangeParam setLowerBound(String theLowerBound) { public DateRangeParam setLowerBound(String theLowerBound) {
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)); setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
@ -281,7 +290,7 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
case LESSTHAN_OR_EQUALS: case LESSTHAN_OR_EQUALS:
case ENDS_BEFORE: case ENDS_BEFORE:
case NOT_EQUAL: case NOT_EQUAL:
throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix()); throw new IllegalStateException("Invalid lower bound comparator: " + myLowerBound.getPrefix());
} }
} }
return retVal; return retVal;
@ -298,6 +307,15 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
* This upper bound is assumed to have a <code>le</code> * This upper bound is assumed to have a <code>le</code>
* (less than or equals) modifier. * (less than or equals) modifier.
* </p> * </p>
* <p>
* Note: An operation can take a DateRangeParam. If only a single date is provided,
* it will still result in a DateRangeParam where the lower and upper bounds
* are the same value. As such, even though the prefixes for the lower and
* upper bounds default to <code>ge</code> and <code>le</code> respectively,
* the resulting prefix is effectively <code>eq</code> where only a single
* date is provided - as required by the FHIR specificiation (i.e. "If no
* prefix is present, the prefix <code>eq</code> is assumed").
* </p>
*/ */
public DateRangeParam setUpperBound(String theUpperBound) { public DateRangeParam setUpperBound(String theUpperBound) {
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)); setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
@ -339,7 +357,7 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
case APPROXIMATE: case APPROXIMATE:
case NOT_EQUAL: case NOT_EQUAL:
case STARTS_AFTER: case STARTS_AFTER:
throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix()); throw new IllegalStateException("Invalid upper bound comparator: " + myUpperBound.getPrefix());
} }
} }
return retVal; return retVal;
@ -370,6 +388,104 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return Objects.hash(myLowerBound, myUpperBound); return Objects.hash(myLowerBound, myUpperBound);
} }
public boolean isDateWithinRange(Date theDate) {
boolean retVal = false;
if (theDate == null) {
throw new NullPointerException("theDate can not be null");
}
boolean hasLowerBound = hasBound(myLowerBound);
boolean hasUpperBound = hasBound(myUpperBound);
boolean hasLowerAndUpperBounds = hasLowerBound && hasUpperBound && (myLowerBound.getValue().getTime() != myUpperBound.getValue().getTime());
if (hasLowerAndUpperBounds) {
retVal = isDateWithinLowerAndUpperBounds(theDate);
} else if (hasLowerBound) {
retVal = isDateWithinLowerBound(theDate);
} else if (hasUpperBound) {
retVal = isDateWithinUpperBound(theDate);
}
return retVal;
}
private boolean isDateWithinLowerAndUpperBounds(Date theDate) {
return isDateWithinLowerBound(theDate, true) && isDateWithinUpperBound(theDate, true);
}
public boolean isDateWithinLowerBound(Date theDate) {
return isDateWithinLowerBound(theDate, false);
}
private boolean isDateWithinLowerBound(Date theDate, boolean theIsRange) {
boolean retVal = false;
if (theDate == null) {
throw new NullPointerException("theDate can not be null");
}
if (hasBound(myLowerBound)) {
long lowerBound = myLowerBound.getValue().getTime();
switch (myLowerBound.getPrefix()) {
case GREATERTHAN:
case STARTS_AFTER:
retVal = theDate.getTime() > lowerBound;
break;
case EQUAL:
if (theIsRange) {
retVal = theDate.getTime() >= lowerBound;
} else {
retVal = theDate.getTime() == lowerBound;
}
break;
case GREATERTHAN_OR_EQUALS:
retVal = theDate.getTime() >= lowerBound;
break;
default:
throw new IllegalStateException("Invalid lower bound comparator: " + myLowerBound.getPrefix());
}
}
return retVal;
}
public boolean isDateWithinUpperBound(Date theDate) {
return isDateWithinUpperBound(theDate, false);
}
private boolean isDateWithinUpperBound(Date theDate, boolean theIsRange) {
boolean retVal = false;
if (theDate == null) {
throw new NullPointerException("theDate can not be null");
}
if (hasBound(myUpperBound)) {
long upperBound = myUpperBound.getValue().getTime();
switch (myUpperBound.getPrefix()) {
case LESSTHAN:
case ENDS_BEFORE:
retVal = theDate.getTime() < upperBound;
break;
case EQUAL:
if (theIsRange) {
retVal = theDate.getTime() <= upperBound;
} else {
retVal = theDate.getTime() == upperBound;
}
break;
case LESSTHAN_OR_EQUALS:
retVal = theDate.getTime() <= upperBound;
break;
default:
throw new IllegalStateException("Invalid upper bound comparator: " + myUpperBound.getPrefix());
}
}
return retVal;
}
public boolean isEmpty() { public boolean isEmpty() {
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null); return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
} }
@ -520,6 +636,15 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return b.toString(); return b.toString();
} }
/**
* Note: An operation can take a DateRangeParam. If only a single date is provided,
* it will still result in a DateRangeParam where the lower and upper bounds
* are the same value. As such, even though the prefixes for the lower and
* upper bounds default to <code>ge</code> and <code>le</code> respectively,
* the resulting prefix is effectively <code>eq</code> where only a single
* date is provided - as required by the FHIR specificiation (i.e. "If no
* prefix is present, the prefix <code>eq</code> is assumed").
*/
private void validateAndSet(DateParam lowerBound, DateParam upperBound) { private void validateAndSet(DateParam lowerBound, DateParam upperBound) {
if (hasBound(lowerBound) && hasBound(upperBound)) { if (hasBound(lowerBound) && hasBound(upperBound)) {
if (lowerBound.getValue().getTime() > upperBound.getValue().getTime()) { if (lowerBound.getValue().getTime() > upperBound.getValue().getTime()) {

View File

@ -1,28 +1,209 @@
package ca.uhn.fhir.rest.param; package ca.uhn.fhir.rest.param;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.api.QualifiedParamList;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
import org.mockito.Mockito; import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class DateRangeParamTest { public class DateRangeParamTest {
private FhirContext fhirContext; private FhirContext fhirContext;
private Date myBefore;
private Date myLower;
private Date myBetween;
private Date myUpper;
private Date myAfter;
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Before @Before
public void initMockContext() { public void initMockContext() {
fhirContext = Mockito.mock(FhirContext.class); fhirContext = Mockito.mock(FhirContext.class);
} }
@Before
public void initDates() throws InterruptedException {
myBefore = new Date();
Thread.sleep(1L);
myLower = new Date();
Thread.sleep(1L);
myBetween = new Date();
Thread.sleep(1L);
myUpper = new Date();
Thread.sleep(1L);
myAfter = new Date();
}
@Test
public void testIsDateWithinRangeExclusive() {
DateParam lowerBound = new DateParam(GREATERTHAN, myLower);
DateParam upperBound = new DateParam(LESSTHAN, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam(lowerBound, upperBound);
assertFalse(dateRangeParam.isDateWithinRange(myBefore));
assertFalse(dateRangeParam.isDateWithinRange(myLower));
assertTrue(dateRangeParam.isDateWithinRange(myBetween));
assertFalse(dateRangeParam.isDateWithinRange(myUpper));
assertFalse(dateRangeParam.isDateWithinRange(myAfter));
}
@Test
public void testIsDateWithinRangeInclusive() {
DateParam lowerBound = new DateParam(EQUAL, myLower);
DateParam upperBound = new DateParam(EQUAL, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam(lowerBound, upperBound);
assertFalse(dateRangeParam.isDateWithinRange(myBefore));
assertTrue(dateRangeParam.isDateWithinRange(myLower));
assertTrue(dateRangeParam.isDateWithinRange(myBetween));
assertTrue(dateRangeParam.isDateWithinRange(myUpper));
assertFalse(dateRangeParam.isDateWithinRange(myAfter));
}
@Test
public void testIsDateWithinRangeOnlyLower() {
DateParam lowerBound = new DateParam(EQUAL, myLower);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setLowerBound(lowerBound);
assertFalse(dateRangeParam.isDateWithinRange(myBefore));
assertTrue(dateRangeParam.isDateWithinRange(myLower));
assertFalse(dateRangeParam.isDateWithinRange(myBetween));
assertFalse(dateRangeParam.isDateWithinRange(myUpper));
assertFalse(dateRangeParam.isDateWithinRange(myAfter));
}
@Test
public void testIsDateWithinRangeOnlyUpper() {
DateParam upperBound = new DateParam(EQUAL, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setUpperBound(upperBound);
assertFalse(dateRangeParam.isDateWithinRange(myBefore));
assertFalse(dateRangeParam.isDateWithinRange(myLower));
assertFalse(dateRangeParam.isDateWithinRange(myBetween));
assertTrue(dateRangeParam.isDateWithinRange(myUpper));
assertFalse(dateRangeParam.isDateWithinRange(myAfter));
}
@Test
public void testIsDateWithinLowerBoundGreaterThan() {
DateParam lowerBound = new DateParam(GREATERTHAN, myLower);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setLowerBound(lowerBound);
assertFalse(dateRangeParam.isDateWithinLowerBound(myBefore));
assertFalse(dateRangeParam.isDateWithinLowerBound(myLower));
assertTrue(dateRangeParam.isDateWithinLowerBound(myBetween));
assertTrue(dateRangeParam.isDateWithinLowerBound(myUpper));
assertTrue(dateRangeParam.isDateWithinLowerBound(myAfter));
}
@Test
public void testIsDateWithinLowerBoundStartsAfter() {
DateParam lowerBound = new DateParam(STARTS_AFTER, myLower);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setLowerBound(lowerBound);
assertFalse(dateRangeParam.isDateWithinLowerBound(myBefore));
assertFalse(dateRangeParam.isDateWithinLowerBound(myLower));
assertTrue(dateRangeParam.isDateWithinLowerBound(myBetween));
assertTrue(dateRangeParam.isDateWithinLowerBound(myUpper));
assertTrue(dateRangeParam.isDateWithinLowerBound(myAfter));
}
@Test
public void testIsDateWithinLowerBoundEqual() {
DateParam lowerBound = new DateParam(EQUAL, myLower);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setLowerBound(lowerBound);
assertFalse(dateRangeParam.isDateWithinLowerBound(myBefore));
assertTrue(dateRangeParam.isDateWithinLowerBound(myLower));
assertFalse(dateRangeParam.isDateWithinLowerBound(myBetween));
assertFalse(dateRangeParam.isDateWithinLowerBound(myUpper));
assertFalse(dateRangeParam.isDateWithinLowerBound(myAfter));
}
@Test
public void testIsDateWithinLowerBoundGreaterThanOrEquals() {
DateParam lowerBound = new DateParam(GREATERTHAN_OR_EQUALS, myLower);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setLowerBound(lowerBound);
assertFalse(dateRangeParam.isDateWithinLowerBound(myBefore));
assertTrue(dateRangeParam.isDateWithinLowerBound(myLower));
assertTrue(dateRangeParam.isDateWithinLowerBound(myBetween));
assertTrue(dateRangeParam.isDateWithinLowerBound(myUpper));
assertTrue(dateRangeParam.isDateWithinLowerBound(myAfter));
}
@Test
public void testIsDateWithinUpperBoundLessThan() {
DateParam upperBound = new DateParam(LESSTHAN, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setUpperBound(upperBound);
assertTrue(dateRangeParam.isDateWithinUpperBound(myBefore));
assertTrue(dateRangeParam.isDateWithinUpperBound(myLower));
assertTrue(dateRangeParam.isDateWithinUpperBound(myBetween));
assertFalse(dateRangeParam.isDateWithinUpperBound(myUpper));
assertFalse(dateRangeParam.isDateWithinUpperBound(myAfter));
}
@Test
public void testIsDateWithinUpperBoundEndsBefore() {
DateParam upperBound = new DateParam(ENDS_BEFORE, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setUpperBound(upperBound);
assertTrue(dateRangeParam.isDateWithinUpperBound(myBefore));
assertTrue(dateRangeParam.isDateWithinUpperBound(myLower));
assertTrue(dateRangeParam.isDateWithinUpperBound(myBetween));
assertFalse(dateRangeParam.isDateWithinUpperBound(myUpper));
assertFalse(dateRangeParam.isDateWithinUpperBound(myAfter));
}
@Test
public void testIsDateWithinUpperBoundEqual() {
DateParam upperBound = new DateParam(EQUAL, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setUpperBound(upperBound);
assertFalse(dateRangeParam.isDateWithinUpperBound(myBefore));
assertFalse(dateRangeParam.isDateWithinUpperBound(myLower));
assertFalse(dateRangeParam.isDateWithinUpperBound(myBetween));
assertTrue(dateRangeParam.isDateWithinUpperBound(myUpper));
assertFalse(dateRangeParam.isDateWithinUpperBound(myAfter));
}
@Test
public void testIsDateWithinUpperBoundLessThanOrEquals() {
DateParam upperBound = new DateParam(LESSTHAN_OR_EQUALS, myUpper);
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setUpperBound(upperBound);
assertTrue(dateRangeParam.isDateWithinUpperBound(myBefore));
assertTrue(dateRangeParam.isDateWithinUpperBound(myLower));
assertTrue(dateRangeParam.isDateWithinUpperBound(myBetween));
assertTrue(dateRangeParam.isDateWithinUpperBound(myUpper));
assertFalse(dateRangeParam.isDateWithinUpperBound(myAfter));
}
/** Can happen e.g. when the query parameter for {@code _lastUpdated} is left empty. */ /** Can happen e.g. when the query parameter for {@code _lastUpdated} is left empty. */
@Test @Test
public void testParamWithoutPrefixAndWithoutValue() { public void testParamWithoutPrefixAndWithoutValue() {

View File

@ -41,18 +41,27 @@
type: "fix" type: "fix"
title: "A missing mandatory was added to the SNOMED CT CodeSystem that is uploaded when SCT is uploaded to the JPA server. Thanks to Anders Havn for the pull request!" title: "A missing mandatory was added to the SNOMED CT CodeSystem that is uploaded when SCT is uploaded to the JPA server. Thanks to Anders Havn for the pull request!"
- item: - item:
issue: 1643 issue: "1643"
type: fix type: "fix"
title: "When validating resources containing custom valuesets defined in PrePopulatedValidationSupport title: "When validating resources containing custom valuesets defined in PrePopulatedValidationSupport
outside of the JPA server, sometimes code systems could not be found resulting in false negative outside of the JPA server, sometimes code systems could not be found resulting in false negative
errors." errors."
- item: - item:
issue: 1588 issue: "1588"
type: add type: "add"
title: "Support for several new operators has been added to the `_filter` support in the JPA server. Thanks title: "Support for several new operators has been added to the `_filter` support in the JPA server. Thanks
to Anthony Sute for the Pull Request!" to Anthony Sute for the Pull Request!"
- item: - item:
issue: 1650 issue: "1650"
type: fix type: "fix"
title: Several misleading comments in documentation code snippets were fixed. Thanks to title: "Several misleading comments in documentation code snippets were fixed. Thanks to
Jafer Khan for the pull request! Jafer Khan for the pull request!"
- item:
issue: "1645"
type: "add"
title: "The following utility methods have been added:
<ul>
<li><code>DateRangeParam#isDateWithinRange(Date theDate)</code></li>
<li><code>DateRangeParam#isDateWithinLowerBound(Date theDate)</code></li>
<li><code>DateRangeParam#isDateWithinUpperBound(Date theDate)</code></li>
</ul>"