3

I have two objects calendar

Calendar startCalendar = new GregorianCalendar(2013,0,31);

Calendar endCalendar = new GregorianCalendar();

I want to know if the interval between the two dates listed above is covered by n of other objects pair calendars without holes between intervals

Example1:

Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2013,5,31);
Calendar endCalendar2();

Is GOOD

Example2:

Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2014,2,31);
Calendar endCalendar2();

NOT GOOD

I use Java 6 Thanks

Plijen
  • 81
  • 1
  • 7

3 Answers3

3

1 Rude but simple method

Use a Set< Long>

Set<Long> all_times_in_milli=new HashSet<Long>();
// Put every interval

// interval 1
for (long time_in_millis=startCalendar1.getTimeInMillis(); 
        time_in_millis<= endCalendar1.getTimeInMillis(); 
        time_in_millis+=86400000)
        all_times_in_milli.add(time_in_millis);

// interval 2
for (long time_in_millis=startCalendar2.getTimeInMillis(); 
        time_in_millis<= endCalendar2.getTimeInMillis(); 
        time_in_millis+=86400000)
        all_times_in_milli.add(time_in_millis);

// ETC
// AND TEST !
boolean failed=false;
for (long time_in_millis=startCalendar.getTimeInMillis(); 
        time_in_millis<= endCalendar.getTimeInMillis(); 
        time_in_millis+=86400000)
        {

        if (all_times_in_milli.contains(time_in_millis))
            {
            failed=true; break;
            }
        }

if (failed) System.out.println("Your are done !");

2 SMARTER METHOD As every interval is an [long - long] interval

  • assemble your intervals to get continuous intervals (sets of overlaping intervals) => then you get B1-E1, B2-E2, B3-E3 distincts intervals
  • check if you first interval is inside of of them: B1 <= start <= end <=E1, or B2 <= start <= end <=E2, ...

interesting only if you have a lot of datas

3

First approach: Only using Java 6

When I see your date examples like 2015-01-31 then I get the strong suspicion that you speak about closed date intervals otherwise choosing the end of a month might appear a little bit strange. This is a wide spread and reasonable approach. Unfortunately choosing a data type like java.util.Calendar representing an instant (also a date-time-zone-combo, too) is not in harmony with closed intervals. Such instant-like types work better with half-open intervals. The consequence is:

If you decide to use only Java-6-types then you can try to convert all Calendar-objects to Long-values representing the elapsed millisecs since Unix epoch as suggested by @guillaume girod-vitouchkina (has got my upvote as an example how to do this without any external library) . But you have to add an extra day to every single Calendar-object (if representing an end boundary) in advance to achieve the effect of closed date intervals.

And of course, you have still to do some home grown interval arithmetic yourself as shown in that answer in a sketchy way. If you carefully study the other proposals and your own requirements you will find that the final solution even requires more than just a new interval class or basic comparisons of intervals. You will also need a higher abstraction layer, namely defined operations between several intervals. Doing this all yourself might cause some headache. On the other hand: Implementing a Long-based interval arithmetic might save some performance overhead as typical for an extra interval library if you have good programming skills.

Second approach: Using a dedicated interval library

I only know four libraries which promise to handle intervals. Threeten-Extra as mentioned by @Basil Bourque cannot be used because it requires Java-8. Its interval class has the disadvantage to handle instants only, but not calendar dates. There is also almost no support for handling collections of intervals. The same can be said for Joda-Time (which is at least working on Java-6 and also offers a dedicated calendar date type, namely LocalDate but no date intervals).

An interesting option is using Guava and its class RangeSet, especially if you decide to continue using Calendar-objects and Longs. This class has some support for handling operations between intervals - for me much more appealing than using the simple interval class of Joda-Time.

Finally you also have the option to use my library Time4J which has the range-package. I will show now a complete solution for your problem:

// our test interval
PlainDate start = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate end = SystemClock.inLocalView().today();
DateInterval test = DateInterval.between(start, end);
IntervalCollection<PlainDate> icTest = IntervalCollection.onDateAxis().plus(test);

// two intervals for your GOOD case
PlainDate s1 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e1 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i1 = DateInterval.between(s1, e1);

PlainDate s2 = PlainDate.of(2013, Month.MAY, 31);
PlainDate e2 = end; // today
DateInterval i2 = DateInterval.between(s2, e2);

IntervalCollection<PlainDate> goodCase = 
    IntervalCollection.onDateAxis().plus(i1).plus(i2);

boolean covered = icTest.minus(goodCase).isEmpty();
System.out.println("Good case: " + covered); // true

// two intervals for your BAD case
PlainDate s3 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e3 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i3 = DateInterval.between(s3, e3);

PlainDate s4 = PlainDate.of(2014, Month.MARCH, 31);
PlainDate e4 = end; // today
DateInterval i4 = DateInterval.between(s4, e4);

IntervalCollection<PlainDate> badCase = 
    IntervalCollection.onDateAxis().plus(i3).plus(i4);

covered = icTest.minus(badCase).isEmpty();
System.out.println("Bad case: " + covered); // false

The biggest part of code is just interval construction. The real interval arithmetic itself is done by this surprisingly small code fragment:

boolean covered = 
  IntervalCollection.onDateAxis().plus(test).minus(
    IntervalCollection.onDateAxis().plus(i1).plus(i2)
  ).isEmpty();

Explanation: The test interval is covered by intervals i1 and i2 if the remainder of the subtraction of i1 and i2 from test is empty.

By the way: Date intervals in Time4J are closed intervals by default. You can change these intervals to half open intervals however if you really want (simply by calling withOpenEnd() on a given date interval).

And if you plan to migrate to Java-8 later, you can just update the Time4J-version to version line 4.x (version v3.x is for Java-6) and get very easy conversions to Java-8 types like java.time.LocalDate (for example: PlainDate.from(localDate) or LocalDate ld = plainDate.toTemporalAccessor()) so you can continue to use Time4J for extra features not covered by standard Java even in the future.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
2

You using old date-time classes that have been supplanted by the java.time framework in Java 8 and later. Those old classes have proven to be clunky, confusing, and flawed.

java.time

The new java.time classes are inspired by the highly successful Joda‑Time library, intended as its successor, similar in concept but re-architected. Defined by JSR 310. Extended by the ThreeTen‑Extra project. See the Tutorial.

The new classes include LocalDate for a date-only value without time-of-day. For your purpose, use this instead of Calendar.

Note that month numbers sensibly start from one, unlike Calendar.

LocalDate start = LocalDate.of( 2013 , 1 , 31 );

Note that in order to determine a date the time zone is crucial. The date is not simultaneously the same around the world. A new day dawns earlier in Paris than Montréal, for example.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
LocalDate today = LocalDate.now ( zoneId );

From there you can call any combination of isAfter, isBefore, or isEqual to do your logic. Your Question is not clear exactly about that logic, so I cannot address that.

The ThreeTen-Extra project that extends java.time includes an Interval class that would help you. Unfortunately that class works only with Instant objects (date-time in UTC), not LocalDate. Specifically the methods for comparing intervals would help, abuts, encloses, and overlaps.

You would whip up your own IntervalLD class for LocalDate objects. Usually I do not recommend rolling your own date-time handling classes because date-time work is surprisingly tricky. But in this case with LocalDate the logic might simple. Here is my quick rough-draft completely untested example to get you started.

package com.example.javatimestuffmaven;

import java.time.LocalDate;

/**
 * Similar to the 'Interval'class in the ThreeTen-Extra project, but for LocalDate objects.
 *
 * @author Basil Bourque
 */
public class IntervalLD {

    private LocalDate start, end;

    // Constructor
    public IntervalLD ( LocalDate startArg , LocalDate endArg ) {
        this.start = startArg;
        this.end = endArg;
    }

    public Boolean isBefore ( IntervalLD interval ) {
        // True if this one's end is before that one's start.
        boolean before = this.getEnd ().isBefore ( interval.getStart () );
        return before;
    }

    public Boolean isAfter ( IntervalLD interval ) {
        // True if this one's start is after that one's end.
        boolean after = this.getStart ().isAfter ( interval.getStart () );
        return after;
    }

    public Boolean abuts ( IntervalLD interval ) {
        // True if the intervals are next to each other on the time line but do not share a date. (exclusive of each other, not half-open)
        // True if either one's end is a day ahead of the other's start or vice versa, either's start is day after the other's end.
        if ( this.isBefore ( interval ) ) {
            if ( this.getEnd ().plusDays ( 1 ).equals ( interval.getStart () ) ) {
                return Boolean.TRUE;
            } else {
                return Boolean.FALSE;
            }
        } else if ( this.isAfter ( interval ) ) {
            if ( this.getStart ().minusDays ( 1 ).equals ( interval.getEnd () ) ) {
                return Boolean.TRUE;
            } else {
                return Boolean.FALSE;
            }
        } else if ( this.isEqual ( interval ) ) {
            return Boolean.FALSE;
        }

        // Impossible. Should never reach this point.
        // TODO: Handle this error condition.
        return Boolean.FALSE;
    }

    public Boolean encloses ( IntervalLD interval ) {
        //This checks if the specified interval is fully enclosed by this interval.
        // The result is true if the start of the specified interval is contained in this interval, and
        // the end is contained or equal to the end of this interval.
        boolean thatOneStartsOnOrAfterThisOne =  ! interval.getStart ().isBefore ( this.getStart () );
        boolean thatOneEndsOnOrAfterThisOne =  ! interval.getEnd ().isAfter ( this.getEnd () );
        boolean doesEnclose = ( thatOneStartsOnOrAfterThisOne && thatOneEndsOnOrAfterThisOne );
        return doesEnclose;
    }

    public Boolean overlaps ( IntervalLD interval ) {
        // True if the two intervals share some part of the timeline.
        // True if this interval does NOT start after that one ends OR this interval does NOT end before that one starts.
        boolean startsTooLate = this.getStart ().isAfter ( interval.getEnd () );
        boolean endsTooEarly = this.getEnd ().isAfter ( interval.getEnd () );
        boolean doesOverlap = (  ! startsTooLate &&  ! endsTooEarly );
        return ( doesOverlap );
    }

    public Boolean isEqual ( IntervalLD interval ) {
        boolean sameStart = this.getStart ().isEqual ( interval.getStart () );
        boolean sameEnd = this.getEnd ().isEqual ( interval.getEnd () );
        return ( sameStart && sameEnd );
    }

    @Override
    public String toString () {
        String output = this.getStart () + "/" + this.getEnd ();
        return output;
    }

    // Getters. Read-only (immutable) so no Setters.
    /**
     * @return the start
     */
    public LocalDate getStart () {
        return this.start;
    }

    /**
     * @return the end
     */
    public LocalDate getEnd () {
        return this.end;
    }
}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154