1

Based on this answer, I decided to implement my own MutableClock for unit testing purposes that works slightly different:

class MutableClock extends Clock {
  private final List<Instant> instants = new ArrayList<>();

  public MutableClock(Instant now, TemporalAmount... amounts) {
    Objects.requireNonNull(now, "now must not be null");
    Objects.requireNonNull(amounts, "amounts must not be null");
    instants.add(now);
    for (TemporalAmount amount : amounts) {
        Instant latest = instants.get(instants.size() - 1);
        instants.add(latest.plus(amount));
    }
  }

  @Override
  public Instant instant() {
    Instant latest = instants.get(0);
    if (instants.size() > 1) {
        instants.remove(0);
    }
    return latest;
  }

  ... 

But then I noticed I was doing this here:

instants.add(latest.plus(amount));

So, basically, I can only tick the clock "forward". Sure, that makes sense most of the time, but as all of this is for unit testing, I can imagine that I want to use such a MutableClock instance and have it return instants that aren't always "increasing".

But when looking at the TemporalAmount interface: there is no way to express a negative amount of time?! In other words: it seems that instances of TemporalAmount aren't "signed".

So, how could one go about this?

GhostCat
  • 137,827
  • 25
  • 176
  • 248

2 Answers2

1

This problem can be solved rather straight forward: simply by looking at concrete implementations of that interface, namely Duration. That class actually offers negated() to well, negate a Duration instance.

Therefore the above implementation already works, when passing a negative Duration:

@Test
public void testNegativeAdjustments() {
    Instant now = Instant.now();
    Duration amount = Duration.ofSeconds(5);
    TemporalAmount negativeAmount = amount.negated();
    MutableClock underTest = new MutableClock(now, negativeAmount);
    assertThat(underTest.instant(), is(now));
    assertThat(underTest.instant(), is(amount.subtractFrom(now)));
}
GhostCat
  • 137,827
  • 25
  • 176
  • 248
1

TemporalAmount is implemented by two classes in java.time: Duration and Period. A user may also write his or her own implementation of the interface, of course. I didn’t check, but I would assume that the PeriodDuration class of the ThreeTen Extra project implements the interface too. You may think that you cannot add a Period to an Instant because Instant doesn’t know days, months and years, which is what a Period consists of. In general it’s true that you cannot, but there are cases where you can. Adding Period.ZERO or Period.ofWeeks(3) goes nicely (the latter will define a week as 168 hours, and we know that when summer time begins and ends, this is not true, but yet it’s possible to do). So in short: we cannot safely assume that the TemporalAmount is a Duration.

If you want to program to the interface, a fairly simple trick will be to check whether the clock actually went backward when adding the amount:

        Instant latest = instants.get(instants.size() - 1);
        Instant newInstant = latest.plus(amount);
        if (newInstant.isBefore(latest)) {
            // The amount was negative; do whatever handling of the situation you need
        } else {
            instants.add(newInstant);
        }

Of course, if you want to allow your clock to go backward, no special treatment of that situation is needed (you can leave out the if-else construct). As you noted in your own answer, creating a negative amount poses no problem, for example Duration.ofSeconds(-5) or Period.ofWeeks(-3).

Why doesn’t the interface offer a test for a negative amount and/or a negating method?

While a Duration is always unambiguously either negative, zero or positive, this does not hold true for a Period. Period.ofMonths(1).minusDays(30) may be negative, zero or positive depending on the choice of month. Curiously Period has got an isNegative method, but it just tests whether any one of the three units (years, months, days) is negative, so the semantics are not the ones that you need. So Period.ofMonths(1).minusDays(3).isNegative() returns true.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    Interesting point. Maybe the real answer is also to change my code from not using that interface, but to simply rely on Duration itself. That clearly expresses the intent, and also avoids people pushing in Period instances and then strange things happening. – GhostCat Aug 07 '19 at 10:44
  • 1
    That sounds right. The interfaces of java.time aren’t all for use by the everyday application (or test) programmer, so if relying directly on `Duration` makes things simpler or easier, by all means do (I hurried to upvote your own answer going in the same direction). – Ole V.V. Aug 07 '19 at 10:49