diff --git a/java/google/registry/model/common/TimeOfYear.java b/java/google/registry/model/common/TimeOfYear.java
index 86bd59d59..c49709cd2 100644
--- a/java/google/registry/model/common/TimeOfYear.java
+++ b/java/google/registry/model/common/TimeOfYear.java
@@ -14,14 +14,17 @@
package google.registry.model.common;
+import static com.google.common.collect.DiscreteDomain.integers;
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 static org.joda.time.DateTimeZone.UTC;
+import com.google.common.base.Function;
import com.google.common.base.Splitter;
-import com.google.common.collect.BoundType;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.Range;
import com.googlecode.objectify.annotation.Embed;
@@ -72,40 +75,37 @@ public class TimeOfYear extends ImmutableObject {
}
/**
- * Returns an {@link ImmutableSet} of {@link DateTime}s of every recurrence of this particular
+ * Returns an {@link Iterable} of {@link DateTime}s of every recurrence of this particular
* time of year within a given {@link Range} (usually one spanning many years).
+ *
+ *
WARNING: This can return a potentially very large {@link Iterable} if {@code END_OF_TIME}
+ * is used as the upper endpoint of the range.
*/
- public ImmutableSet getInstancesInRange(Range range) {
+ public Iterable 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();
- // 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++) {
- DateTime candidate = getDateTimeWithSameYear(normalizedRange.lowerEndpoint()).withYear(year);
- if (normalizedRange.contains(candidate)) {
- instances.add(candidate);
- }
- }
- return instances.build();
+ Range normalizedRange = range.intersection(Range.closed(START_OF_TIME, END_OF_TIME));
+ Range yearRange = Range.closed(
+ normalizedRange.lowerEndpoint().getYear(),
+ normalizedRange.upperEndpoint().getYear());
+ return FluentIterable.from(ContiguousSet.create(yearRange, integers()))
+ .transform(new Function() {
+ @Override
+ public DateTime apply(Integer year) {
+ return getDateTimeWithYear(year);
+ }})
+ .filter(normalizedRange);
}
/** Get the first {@link DateTime} with this month/day/millis that is at or after the start. */
public DateTime getNextInstanceAtOrAfter(DateTime start) {
- DateTime withSameYear = getDateTimeWithSameYear(start);
+ DateTime withSameYear = getDateTimeWithYear(start.getYear());
return isAtOrAfter(withSameYear, start) ? withSameYear : withSameYear.plusYears(1);
}
/** Get the first {@link DateTime} with this month/day/millis that is at or before the end. */
public DateTime getLastInstanceBeforeOrAt(DateTime end) {
- DateTime withSameYear = getDateTimeWithSameYear(end);
+ DateTime withSameYear = getDateTimeWithYear(end.getYear());
return isBeforeOrAt(withSameYear, end) ? withSameYear : withSameYear.minusYears(1);
}
@@ -113,11 +113,12 @@ public class TimeOfYear extends ImmutableObject {
* Return a new datetime with the same year as the parameter but projected to the month, day, and
* time of day of this object.
*/
- private DateTime getDateTimeWithSameYear(DateTime date) {
+ private DateTime getDateTimeWithYear(int year) {
List monthDayMillis = Splitter.on(' ').splitToList(timeString);
// Do not be clever and use Ints.stringConverter here. That does radix guessing, and bad things
// will happen because of the leading zeroes.
- return date
+ return new DateTime(0, UTC)
+ .withYear(year)
.withMonthOfYear(Integer.parseInt(monthDayMillis.get(0)))
.withDayOfMonth(Integer.parseInt(monthDayMillis.get(1)))
.withMillisOfDay(Integer.parseInt(monthDayMillis.get(2)));
diff --git a/javatests/google/registry/model/common/TimeOfYearTest.java b/javatests/google/registry/model/common/TimeOfYearTest.java
index 1b03d6b98..0f8439906 100644
--- a/javatests/google/registry/model/common/TimeOfYearTest.java
+++ b/javatests/google/registry/model/common/TimeOfYearTest.java
@@ -70,15 +70,14 @@ public class TimeOfYearTest {
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.closed(startDate, endDate));
ImmutableSet expected = ImmutableSet.of(
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);
+ assertThat(timeOfYear.getInstancesInRange(Range.closed(startDate, endDate)))
+ .containsExactlyElementsIn(expected);
}
@Test
@@ -86,14 +85,13 @@ public class TimeOfYearTest {
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.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);
+ assertThat(timeOfYear.getInstancesInRange(Range.openClosed(startDate, endDate)))
+ .containsExactlyElementsIn(expected);
}
@Test
@@ -101,14 +99,13 @@ public class TimeOfYearTest {
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.closedOpen(startDate, endDate));
ImmutableSet expected = ImmutableSet.of(
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"));
- assertThat(actual).containsExactlyElementsIn(expected);
+ assertThat(timeOfYear.getInstancesInRange(Range.closedOpen(startDate, endDate)))
+ .containsExactlyElementsIn(expected);
}
@Test
@@ -116,37 +113,34 @@ public class TimeOfYearTest {
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);
+ assertThat(timeOfYear.getInstancesInRange(Range.open(startDate, endDate)))
+ .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);
+ assertThat(timeOfYear.getInstancesInRange(Range.atMost(START_OF_TIME.plusYears(2))))
+ .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);
+ assertThat(timeOfYear.getInstancesInRange(Range.atLeast(END_OF_TIME.minusYears(2))))
+ .containsExactlyElementsIn(expected);
}
@Test
@@ -154,8 +148,6 @@ public class TimeOfYearTest {
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();
+ assertThat(timeOfYear.getInstancesInRange(Range.closed(startDate, endDate))).isEmpty();
}
}