6

I'm looking for a library or helper class in Java that would allow me to perform date interval sum and subtractions.

For example, lets's say I have the following date intervals:

A = ["2015-01-01 00:00", "2015-01-20 00:00"]
B = ["2015-01-05 00:00", "2015-01-10 00:00"]
C = ["2015-01-11 00:00", "2015-01-14 00:00"]
D = ["2015-01-19 00:00", "2015-01-25 00:00"]

1                  A               20
|----------------------------------|
    |---------|   |----------|   |------------|
    5    B    10  11    C    14  19    D      25

And let's say I'd like to calculate the following:

A - B - C + D = { ["2015-01-01 00:00", "2015-01-05 00:00"[,
                  ]"2015-01-10 00:00", "2015-01-11 00:00"[,
                  ]"2015-01-14 00:00", "2015-01-25 00:00"] }

1   5         10  11         14               25
|---|         |---|          |----------------|

I know I can build my own logic using pure Java, but I'd rather not reinvent the wheel...

I was looking into Joda-Time, but I couldn't figure out how to perform such operations using it.

Thanks a lot!

tiagobt
  • 141
  • 7
  • Incidentally, Joda Time has new been integrated into Java 8 as the new date/time API. – Boris the Spider Jan 25 '15 at 21:40
  • Interesting! But is it capable of performing the operations I described? – tiagobt Jan 25 '15 at 21:45
  • I think you mean "union" and "intersection" rather than sum and subtraction... – Adam Jan 25 '15 at 22:06
  • Look at http://stackoverflow.com/questions/3802893/number-of-days-between-two-dates-in-joda-time – Michal Jan 25 '15 at 22:10
  • @Michal, I'm not sure I follow you. The method `Days#daysBetween` calculates a period, but I need an interval (or a list of intervals). – tiagobt Jan 25 '15 at 22:23
  • @Adam, you're right. "Union" and "intersection" would be more precise. However, what I meant by "subtraction" is different from an intersection. – tiagobt Jan 25 '15 at 22:28
  • @BoristheSpider Incorrect, Joda-Time has *not* been incorporated into Java 8. The new java.time package in Java 8 was inspired by Joda-Time but was re-architected. Both Joda-Time and java.time have features the other lacks. Joda-Time continues to be an ongoing active useful project. You can use both date-time libraries in a project, drawing on each one’s strengths. – Basil Bourque Jan 26 '15 at 08:25
  • @tiagobt - sorry, I misunderstood your question, my fault. – Michal Jan 26 '15 at 16:22

2 Answers2

8

I found exactly what I needed: Ranges, from the guava-libraries.

Works like this:

Range<Date> a = Range.closed(
    new GregorianCalendar(2015, 0, 1).getTime(),
    new GregorianCalendar(2015, 0, 20).getTime());
Range<Date> b = Range.closed(
    new GregorianCalendar(2015, 0, 5).getTime(),
    new GregorianCalendar(2015, 0, 10).getTime());
Range<Date> c = Range.closed(
    new GregorianCalendar(2015, 0, 11).getTime(),
    new GregorianCalendar(2015, 0, 14).getTime());
Range<Date> d = Range.closed(
    new GregorianCalendar(2015, 0, 19).getTime(),
    new GregorianCalendar(2015, 0, 25).getTime());

RangeSet<Date> result = TreeRangeSet.create();
result.add(a);
result.remove(b);
result.remove(c);
result.add(d);

System.out.println(result);

The code above prints:

[
    [Thu Jan 01 00:00:00 BRST 2015‥Mon Jan 05 00:00:00 BRST 2015),
    (Sat Jan 10 00:00:00 BRST 2015‥Sun Jan 11 00:00:00 BRST 2015),
    (Wed Jan 14 00:00:00 BRST 2015‥Sun Jan 25 00:00:00 BRST 2015]
]
tiagobt
  • 141
  • 7
  • Yes, this is a good solution if you still want to continue with `GregorianCalendar` etc. However, these classes also carry timezone infos (not contained in your original question). Another library which might be interesting for you is my library Time4J, see especially the range-package with the class [IntervalCollection](http://time4j.net/javadoc-en/net/time4j/range/IntervalCollection.html) – Meno Hochschild Jan 26 '15 at 05:50
  • Interesting, @MenoHochschild. I'll have a look at your library. However, both `Range` and `RangeSet` are generic, so I could have chosen a date class that would not carry timezone information (as long as it implements the `Comparable` interface). – tiagobt Jan 27 '15 at 02:38
  • True, Guava can also be combined with Joda-Time-classes or Java-8-classes like `LocalDate` etc.This is much better than trying to use Joda-Time-Interval directly (see the other Joda-related answer). About my library, I am going to implement the missing minus()-operation for intervals in the next release (although a workaround is possible). – Meno Hochschild Jan 27 '15 at 03:59
0

I think it can be done basically using Joda-Time with some custom code. It is assumed that A is the Interval which all other intervals should relate to.

While this code should give the expected results (and should work for different values accordingly) I highly suggest testing it with very different data, especially for the three cases a) an interval not intersecting A at all, b) intersecting A at the beginning and c) an interval which itself intersects B or C or D.

So despite this, it might help for further tests.

Interval a = new Interval(Instant.parse("2015-01-01T00:00Z"), Instant.parse("2015-01-20T00:00Z"));
List<Interval> l = Arrays.asList(
        /* b */ new Interval(Instant.parse("2015-01-05T00:00Z"), Instant.parse("2015-01-10T00:00Z")),
        /* c */ new Interval(Instant.parse("2015-01-11T00:00Z"), Instant.parse("2015-01-14T00:00Z")),
        /* d */ new Interval(Instant.parse("2015-01-19T00:00Z"), Instant.parse("2015-01-25T00:00Z"))
);
List<Interval> results = new ArrayList<Interval>();

for (Interval i : l) {
    if (a.contains(i)) {
        // if i is completely inside a, then calculate the first part and the remaining part
        //   whereas the first part will be added to the result
        Interval firstPart = new Interval(a.getStart(), i.getStart());
        results.add(firstPart);
        // followed by i itself (skipped)
        // part after i, inside a
        Interval remainingPart = new Interval(i.getEnd(), a.getEnd());
        a = remainingPart;
    } else if (i.overlaps(a)) {
        // if the intervals only overlap, then we take the earliest beginning and the latest ending as a result part
        DateTime overlapMin = (a.getStart().isBefore(i.getStart())) ? a.getStart() : i.getStart();
        DateTime overlapMax = (a.getEnd().isAfter(i.getEnd())) ? a.getEnd() : i.getEnd();
        Interval overlapAndBothParts = new Interval(overlapMin, overlapMax);
        results.add(overlapAndBothParts);
        // if the checked interval i is at the beginning, then a will become the part after this "overlap"
        if (i.getStartMillis() < a.getStartMillis()) {
            Interval whatsLeft = new Interval(i.getEndMillis(), a.getEndMillis());
            a = whatsLeft;
        }
    }
}

// print result
for (Interval i : results) {
    System.out.println("result part: " + i);
}
jCoder
  • 2,289
  • 23
  • 22
  • Thanks a lot for the help, @jCoder. The only problem with this solution in that it doesn't allow me to specify which operation I want (sum/union or subtraction). If I understand correctly, it performs a subtraction in case A contains the other interval and it performs a sum/union in case the interval overlaps A. Anyway, I'll try to write a code that solves my problem based on yours. – tiagobt Jan 25 '15 at 23:52