diff --git a/java/google/registry/model/common/TimeOfYear.java b/java/google/registry/model/common/TimeOfYear.java index e4fe12abd..86bd59d59 100644 --- a/java/google/registry/model/common/TimeOfYear.java +++ b/java/google/registry/model/common/TimeOfYear.java @@ -14,12 +14,15 @@ package google.registry.model.common; -import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.isAtOrAfter; import static google.registry.util.DateTimeUtils.isBeforeOrAt; import com.google.common.base.Splitter; +import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; import com.googlecode.objectify.annotation.Embed; import com.googlecode.objectify.annotation.Index; @@ -70,16 +73,26 @@ public class TimeOfYear extends ImmutableObject { /** * Returns an {@link ImmutableSet} of {@link DateTime}s of every recurrence of this particular - * time of year within a given range (usually a range spanning many years). + * time of year within a given {@link Range} (usually one spanning many years). */ - public ImmutableSet getInstancesInRange(DateTime lower, DateTime upper) { - checkArgument(isBeforeOrAt(lower, upper), "Lower bound is not before or at upper bound."); + public ImmutableSet getInstancesInRange(Range range) { + // In registry world, all dates are within START_OF_TIME and END_OF_TIME, so restrict any + // ranges without bounds to our notion of zero-to-infinity. + Range normalizedRange = Range.range( + range.hasLowerBound() ? range.lowerEndpoint() : START_OF_TIME, + range.hasLowerBound() ? range.lowerBoundType() : BoundType.CLOSED, + range.hasUpperBound() ? range.upperEndpoint() : END_OF_TIME, + range.hasUpperBound() ? range.upperBoundType() : BoundType.CLOSED); ImmutableSet.Builder instances = ImmutableSet.builder(); - DateTime firstInstance = getNextInstanceAtOrAfter(lower); - for (int year = firstInstance.getYear(); - year <= getLastInstanceBeforeOrAt(upper).getYear(); + // This produces a greedy year range, but the edge cases will be handled appropriately via + // Range.contains(). + for (int year = normalizedRange.lowerEndpoint().getYear(); + year <= normalizedRange.upperEndpoint().getYear(); year++) { - instances.add(firstInstance.withYear(year)); + DateTime candidate = getDateTimeWithSameYear(normalizedRange.lowerEndpoint()).withYear(year); + if (normalizedRange.contains(candidate)) { + instances.add(candidate); + } } return instances.build(); } diff --git a/javatests/google/registry/model/common/TimeOfYearTest.java b/javatests/google/registry/model/common/TimeOfYearTest.java index 019a8fa54..1b03d6b98 100644 --- a/javatests/google/registry/model/common/TimeOfYearTest.java +++ b/javatests/google/registry/model/common/TimeOfYearTest.java @@ -15,8 +15,11 @@ package google.registry.model.common; import static com.google.common.truth.Truth.assertThat; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; import google.registry.testing.ExceptionRule; @@ -63,34 +66,43 @@ public class TimeOfYearTest { } @Test - public void testSuccess_getInstancesOfTimeOfYearInRange() { + public void testSuccess_getInstancesInRange_closed() { DateTime startDate = DateTime.parse("2012-05-01T00:00:00Z"); - DateTime endDate = DateTime.parse("2016-02-01T00:00:00Z"); - TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-10-01T00:00:00Z")); - ImmutableSet actual = timeOfYear.getInstancesInRange(startDate, endDate); + DateTime endDate = DateTime.parse("2016-05-01T00:00:00Z"); + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-05-01T00:00:00Z")); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.closed(startDate, endDate)); ImmutableSet expected = ImmutableSet.of( - DateTime.parse("2012-10-01T00:00:00Z"), - DateTime.parse("2013-10-01T00:00:00Z"), - DateTime.parse("2014-10-01T00:00:00Z"), - DateTime.parse("2015-10-01T00:00:00Z")); + DateTime.parse("2012-05-01T00:00:00Z"), + DateTime.parse("2013-05-01T00:00:00Z"), + DateTime.parse("2014-05-01T00:00:00Z"), + DateTime.parse("2015-05-01T00:00:00Z"), + DateTime.parse("2016-05-01T00:00:00Z")); assertThat(actual).containsExactlyElementsIn(expected); } @Test - public void testSuccess_getInstancesOfTimeOfYearInRange_empty() { + public void testSuccess_getInstancesInRange_openClosed() { DateTime startDate = DateTime.parse("2012-05-01T00:00:00Z"); - DateTime endDate = DateTime.parse("2013-02-01T00:00:00Z"); - TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-03-01T00:00:00Z")); - ImmutableSet actual = timeOfYear.getInstancesInRange(startDate, endDate); - assertThat(actual).isEmpty(); + DateTime endDate = DateTime.parse("2016-05-01T00:00:00Z"); + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-05-01T00:00:00Z")); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.openClosed(startDate, endDate)); + ImmutableSet expected = ImmutableSet.of( + DateTime.parse("2013-05-01T00:00:00Z"), + DateTime.parse("2014-05-01T00:00:00Z"), + DateTime.parse("2015-05-01T00:00:00Z"), + DateTime.parse("2016-05-01T00:00:00Z")); + assertThat(actual).containsExactlyElementsIn(expected); } @Test - public void testSuccess_getInstancesOfTimeOfYearInRange_inclusive() { + public void testSuccess_getInstancesInRange_closedOpen() { DateTime startDate = DateTime.parse("2012-05-01T00:00:00Z"); - DateTime endDate = DateTime.parse("2015-05-01T00:00:00Z"); + DateTime endDate = DateTime.parse("2016-05-01T00:00:00Z"); TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-05-01T00:00:00Z")); - ImmutableSet actual = timeOfYear.getInstancesInRange(startDate, endDate); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.closedOpen(startDate, endDate)); ImmutableSet expected = ImmutableSet.of( DateTime.parse("2012-05-01T00:00:00Z"), DateTime.parse("2013-05-01T00:00:00Z"), @@ -100,10 +112,50 @@ public class TimeOfYearTest { } @Test - public void testFailure_getInstancesOfTimeOfYearInRange_inverted() { - thrown.expect(IllegalArgumentException.class, "Lower bound is not before or at upper bound."); - TimeOfYear.fromDateTime(DateTime.parse("2013-10-01T00:00:00Z")).getInstancesInRange( - DateTime.parse("2015-10-01T00:00:00Z"), - DateTime.parse("2012-10-01T00:00:00Z")); + public void testSuccess_getInstancesInRange_open() { + DateTime startDate = DateTime.parse("2012-05-01T00:00:00Z"); + DateTime endDate = DateTime.parse("2016-05-01T00:00:00Z"); + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-05-01T00:00:00Z")); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.open(startDate, endDate)); + ImmutableSet expected = ImmutableSet.of( + DateTime.parse("2013-05-01T00:00:00Z"), + DateTime.parse("2014-05-01T00:00:00Z"), + DateTime.parse("2015-05-01T00:00:00Z")); + assertThat(actual).containsExactlyElementsIn(expected); + } + + @Test + public void testSuccess_getInstancesInRange_normalizedLowerBound() { + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(START_OF_TIME); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.atMost(START_OF_TIME.plusYears(2))); + ImmutableSet expected = ImmutableSet.of( + START_OF_TIME, + START_OF_TIME.plusYears(1), + START_OF_TIME.plusYears(2)); + assertThat(actual).containsExactlyElementsIn(expected); + } + + @Test + public void testSuccess_getInstancesInRange_normalizedUpperBound() { + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(END_OF_TIME); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.atLeast(END_OF_TIME.minusYears(2))); + ImmutableSet expected = ImmutableSet.of( + END_OF_TIME.minusYears(2), + END_OF_TIME.minusYears(1), + END_OF_TIME); + assertThat(actual).containsExactlyElementsIn(expected); + } + + @Test + public void testSuccess_getInstancesOfTimeOfYearInRange_empty() { + DateTime startDate = DateTime.parse("2012-05-01T00:00:00Z"); + DateTime endDate = DateTime.parse("2013-02-01T00:00:00Z"); + TimeOfYear timeOfYear = TimeOfYear.fromDateTime(DateTime.parse("2012-03-01T00:00:00Z")); + ImmutableSet actual = + timeOfYear.getInstancesInRange(Range.closed(startDate, endDate)); + assertThat(actual).isEmpty(); } }