3

I am trying to get a date range using Guava's new Range functionality, via

Range<Date> dateRange = Ranges.range(start, BoundType.CLOSED, end, BoundType.CLOSED);

My goal is to get the hours in this date range. So I have created a DiscreteDomain like such:

private static final DiscreteDomain<Date> HOURS = new DiscreteDomain<Date>() {

    public Date next(Date value) {
        return addHours(value, 1);
    }

    private Date addHours(Date value, int i) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(value);
        cal.add(Calendar.HOUR_OF_DAY, i);
        return cal.getTime();
    }

    public Date previous(Date value) {
        return addHours(value, -1);
    }

    public long distance(Date start, Date end) {
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(start);

        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(end);

        return cal2.getTimeInMillis() - cal1.getTimeInMillis();
    }

    public Date minValue() {
        return new Date(Long.MIN_VALUE);
    }

    public Date maxValue() {
        return new Date(Long.MAX_VALUE);
    }
};

If I merely sysout the output, I get the closed set

[Thu Feb 24 00:00:00 EST 2011..Thu Feb 24 00:02:00 EST 2011]

I really want to see each hour in the range, however, so I try a for loop:

for (Date hour : hours) {
    System.out.println(hour);
}

When running this block, I seem to get an infinite set, beginning at the left side of the range, but not stopping at the right side, making me kill the IDE. What am I doing wrong?

ColinD
  • 108,630
  • 30
  • 201
  • 202
Ray
  • 4,829
  • 4
  • 28
  • 55

2 Answers2

6

I think this might be due to the behavior of the Iterator returned by the ContiguousSet (returned by Range.asSet()):

  @Override public UnmodifiableIterator<C> iterator() {
    return new AbstractLinkedIterator<C>(first()) {
      final C last = last();

      @Override
      protected C computeNext(C previous) {
        return equalsOrThrow(previous, last) ? null : domain.next(previous);
      }
    };
  }

  private static boolean equalsOrThrow(Comparable<?> left,
      @Nullable Comparable<?> right) {
    return right != null && compareOrThrow(left, right) == 0;
  }

  private static int compareOrThrow(Comparable left, Comparable right) {
    return left.compareTo(right);
  }

It only stops when the next computed value is equal to the right bound of the range.

In your case, have you tried calling it using Thu Feb 24 02:00:00 instead of Thu Feb 24 00:02:00 for the right bound of your range?

I think this behavior is problematic, and it might be worth asking if equalsOrThrow() could be changed to check for left <= right instead of left == right


Also, your distance() method is incorrect. It should return the distance in hours, not in milliseconds, according to the method contract.


EDIT

All this being said, I believe the real problem is that, according to the DiscreteDomain's javadoc:

A discrete domain always represents the entire set of values of its type; it cannot represent partial domains such as "prime integers" or "strings of length 5."

In your case, you are attempting to create a discrete domain over hourly dates, which is a partial domain of all dates. This is, I think, the root cause of the problem. When you have a partial domain, the equalsOrThrow method becomes unreliable, and it can "miss" the right bound of your range.

Etienne Neveu
  • 12,604
  • 9
  • 36
  • 59
  • 3
    Spent too much time editing my answer and I see you had already edited yours to mention the partial domain problem! – ColinD Jun 13 '11 at 23:04
  • Ahah, yeah, I thought about this just after posting, and re-checked the DiscreteDomain's javadoc... I'm a little trigger happy on the edit button. Sorry about this. Though I guess our answers complement each other, covering the whole domain ;) I'm wondering if it would be worth it for Guava to throw an `IllegalStateException` when it sees that the iterator's next element is strictly greater than the upper bound (which would indicate a problem with the discrete domain). – Etienne Neveu Jun 13 '11 at 23:21
3

I just tried this and it worked fine for me. @eneveu already pointed out the issue with your distance method as well. I'm also guessing that there's some minor difference at the millisecond level between start and end which means that you'll never actually get a Date equal to end by adding hours to start.

However, that's all just symptoms of using the classes in a way they aren't designed to work. The Javadoc for DiscreteDomain states:

A discrete domain always represents the entire set of values of its type; it cannot represent partial domains such as "prime integers" or "strings of length 5."

A DiscreteDomain of "hours" does not represent the domain of all possible Date objects and as such breaks its contract.

ColinD
  • 108,630
  • 30
  • 201
  • 202