Hot questions for Using Joda-Time in intervals

Question:

I need to schedule a periodic job for lots of users. This job will run at a fixed rate, the interval. I want to distribute the execution of the job for each user uniformly over that interval. For example, if the interval is 4 days, I'd use a consistent hashing function with an identifier for each user to schedule the job at the same time, eg. every 4 days, on the 3rd day.

The interval is relative to an origin instant that is the same for all users. Given such an origin instant, like Instant#EPOCH or some other constant value, how do I find the start date of the current interval?

I can do

Instant now = Instant.now();
Instant origin = Instant.EPOCH;
Duration interval = Duration.ofDays(4);

Duration duration = Duration.between(origin, now);
long sinceOrigin = duration.toMillis();
long millisPerInterval = interval.toMillis();

long intervalsSince = sinceOrigin / millisPerInterval;
Instant startNext = origin.plus(interval.multipliedBy(intervalsSince));

int cursor = distributionStrategy.distribute(hashCode, millisPerInterval);

I can then use the cursor to schedule the job at an Instant relative to the start of the current interval.

There's a lot of math here and I'm not sure the transformation to milliseconds everywhere will uphold actual dates. Is there a more precise way of dividing the time between two instants and finding the one (the subdivision) we are in currently?


Answer:

If you only want to reduce the math here, you can use remainder instead of a division and multiplication.

long millisSinceIntervalStart = sinceOrigin % millisPerInterval;
Instant startNext = now.minusMillis(millisSinceIntervalStart);

Here you don't have to calculate the number of intervals passed since origin. Just get the time passed since intervalStart and subtract it from current time.

Also, your startNext seems to indicate the start of current interval, not the next interval. Correct?

Question:

I am working on a project that confuses me really bad right now.

Given is a List<TimeInterval> list that contains elements of the class TimeInterval, which looks like this:

public class TimeInterval {
    private static final Instant CONSTANT = new Instant(0);
    private final LocalDate validFrom;
    private final LocalDate validTo;


    public TimeInterval(LocalDate validFrom, LocalDate validTo) {
        this.validFrom = validFrom;
        this.validTo = validTo;
    }


    public boolean isValid() {
        try {
            return toInterval() != null;
        }
        catch (IllegalArgumentException e) {
            return false;
        }
    }


    public boolean overlapsWith(TimeInterval timeInterval) {
        return this.toInterval().overlaps(timeInterval.toInterval());
    }


    private Interval toInterval() throws IllegalArgumentException {
        return new Interval(validFrom.toDateTime(CONSTANT), validTo.toDateTime(CONSTANT));
    }

The intervals are generated using the following:

TimeInterval tI = new TimeInterval(ld_dateValidFrom, ld_dateValidTo);

The intervals within the list may overlap:

|--------------------|
         |-------------------|

This should result in:

|-------||-----------||------|

It should NOT result in:

|--------|-----------|-------|

Generally speaking in numbers:

I1: 2014-01-01 - 2014-01-30
I2: 2014-01-07 - 2014-01-15

That should result in:

I1: 2014-01-01 - 2014-01-06
I2: 2014-01-07 - 2014-01-15
I3: 2014-01-16 - 2014-01-30

I'm using JODA Time API but since I'm using for the first time, I actually don't really have a clue how to solve my problem. I already had a look at the method overlap() / overlapWith() but I still don't get it.

Your help is much appreciated!

UPDATE I found something similar to my problem >here< but that doesn't help me for now.


I tried it over and over again, and even though it worked for the first intervals I tested, it doesn't actually work the way I wanted it to.

Here are the intervals I have been given:

2014-10-20 ---> 2014-10-26
2014-10-27 ---> 2014-11-02
2014-11-03 ---> 2014-11-09
2014-11-10 ---> 2014-11-16
2014-11-17 ---> 9999-12-31

This is the function I am using to generate the new intervals:

private List<Interval> cleanIntervalList(List<Interval> sourceList) {
    TreeMap<DateTime, Integer> endPoints = new TreeMap<DateTime, Integer>();

    // Fill the treeMap from the TimeInterval list. For each start point,
    // increment the value in the map, and for each end point, decrement it.
    for (Interval interval : sourceList) {
        DateTime start = interval.getStart();
        if (endPoints.containsKey(start)) {
            endPoints.put(start, endPoints.get(start)+1);
        }
        else {
            endPoints.put(start, 1);
        }
        DateTime end = interval.getEnd();
        if (endPoints.containsKey(end)) {
            endPoints.put(end, endPoints.get(start)-1);
        }
        else {
            endPoints.put(end, 1);
        }
    }
    System.out.println(endPoints);

    int curr = 0;
    DateTime currStart = null;

    // Iterate over the (sorted) map. Note that the first iteration is used
    // merely to initialize curr and currStart to meaningful values, as no
    // interval precedes the first point.

    List<Interval> targetList = new LinkedList<Interval>();

    for (Entry<DateTime, Integer> e : endPoints.entrySet()) {
        if (curr > 0) {
            if (e.getKey().equals(endPoints.lastEntry().getKey())){
                targetList.add(new Interval(currStart, e.getKey()));
            }
            else {
                targetList.add(new Interval(currStart, e.getKey().minusDays(1)));
            }
        }
        curr += e.getValue();
        currStart = e.getKey();
    }
    System.out.println(targetList);
    return targetList;
}

This is what the output actually looks like:

2014-10-20 ---> 2014-10-25
2014-10-26 ---> 2014-10-26
2014-10-27 ---> 2014-11-01
2014-11-02 ---> 2014-11-02
2014-11-03 ---> 2014-11-08
2014-11-09 ---> 2014-11-09
2014-11-10 ---> 2014-11-15
2014-11-16 ---> 2014-11-16
2014-11-17 ---> 9999-12-31

And this is what the output SHOULD look like:

2014-10-20 ---> 2014-10-26
2014-10-27 ---> 2014-11-02
2014-11-03 ---> 2014-11-09
2014-11-10 ---> 2014-11-16
2014-11-17 ---> 9999-12-31

Since there is no overlap in the original intervals, I don't get why it produces stuff like

2014-10-26 ---> 2014-10-26
2014-11-02 ---> 2014-11-02
2014-11-09 ---> 2014-11-09
etc

I've been trying to fix this all day long and I'm still not getting there :( Any more help is much appreciated!


Answer:

Half-Open

I suggest you reconsider the terms of your goal. Joda-Time wisely uses the "Half-Open" approach to defining a span of time. The beginning is inclusive while the ending is exclusive. For example, a week starts an the beginning of the first day and runs up to, but not including, the first moment of the next week. Half-open proves to be quite helpful and natural way to handle spans of time, as discussed in other answers.

Using this Half-Open approach for your example, you do indeed want this result:

|--------|-----------|-------|

I1: 2014-01-01 - 2014-01-07
I2: 2014-01-07 - 2014-01-16
I3: 2014-01-16 - 2014-01-30

Search StackOverflow for "half-open" to find discussion and examples, such as this answer of mine.

Joda-Time Interval

Joda-Time has an excellent Interval class to represent a span of time defined by a pair of endpoints on the timeline. That Interval class offers overlap, overlaps (sic), abuts, and gap methods. Note in particular the overlap method that generates a new Interval when comparing two others; that may be key to your solution.

But unfortunately, that class only works with DateTime objects and not LocalDate (date-only, no time-of-day or time zone). Perhaps that lack of support for LocalDate is why you or your team invented that TimeInterval class. But I suggest rather that using that custom class, consider using DateTime objects with Joda-Time's classes. I'm not 100% certain that is better than rolling your own date-only interval class (I've been tempted to do that), but my gut tells me so.

To focus on days rather than day+time, on your DateTime objects call the withTimeAtStartOfDay method to adjust the time portion to the first moment of the day. That first moment is usually 00:00:00.000 but not necessarily due to Daylight Saving Time (DST) and possibly other anomalies. Just be careful and consistent with the time zone; perhaps use UTC throughout.

Here is some example code in Joda-Time 2.5 using the values suggested in the Question. In these particular lines, the call to withTimeAtStartOfDay may be unnecessary as Joda-Time defaults to first moment of day when no day-of-time is provided. But I suggest using those calls to withTimeAtStartOfDay as it makes your code self-documenting as to your intent. And it makes all your day-focused use of DateTime code consistent.

Interval i1 = new Interval( new DateTime( "2014-01-01", DateTimeZone.UTC ).withTimeAtStartOfDay(), new DateTime( "2014-01-30", DateTimeZone.UTC ).withTimeAtStartOfDay() );
Interval i2 = new Interval( new DateTime( "2014-01-07", DateTimeZone.UTC ).withTimeAtStartOfDay(), new DateTime( "2014-01-15", DateTimeZone.UTC ).withTimeAtStartOfDay() );

From there, apply the logic suggested in the other answers.

Question:

I'd like to have a JodaTime Interval which represents a range of days within a year. For example, January 21 - February 23 or the like, or even December 7 - January 13. Now I'd like to figure out if a given DateTime falls within that range of the year, regardless of the particular year.

Unfortunately, Interval#contains doesn't seem to work this way. For example, January 7, 2013 might match, but January 7, 1863 will not. Is there any workaround or another bit of the API I can use?


Answer:

I don't believe there's any such type within Joda Time - and Interval deals with instants, where it sounds like you're interested in day/month values anyway.

You should probably construct your own type that is composed of two MonthDay fields.

Then to determine whether a particular value is within that range, extra the MonthDay for that value, and compare the three values to each other.

For example:

// Note: this assumes you always want end to be exclusive, and start to be inclusive.
// You may well want to make end inclusive instead; it depends on your use case.
public final class MonthDayInterval {
    private final MonthDay start;
    private final MonthDay end;

    public MonthDayInterval(MonthDay start, MonthDay end) {
        this.start = start;
        this.end = end;
    }

    public boolean contains(DateTime dateTime) {
        MonthDay monthDay = 
        return contains(new MonthDay(
            dateTime.getMonthOfYear(), dateTime.getDayOfMonth());
    }

    public boolean contains(MonthDay monthDay) {
        boolean natural = start.compareTo(monthDay) <= 0 && monthDay.compareTo(end) < 0;
        // We need to invert the result if end is after or equal to start.
        return start.compareTo(end) < 0 ? natural : !natural;
    }
}

Question:

I have a custom java sync that fetch data by date range thoght SOAP service running on tomcat. Ex:

getDataByDateRange(startDate,endDate)
getDataByDateRange('2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000')

I want to write a control program to check if any range has been missed by any kind of runtime or server error.

How can I find the missing date ranges?

Thanks.

Visually Example:

TimeLine       : [------------------------------------------------------------------]
Processed Dates: [----1---][---2----]---[-3-][--4---]---[----5---][---6--]-----------
Missing Dates  : -------------------[-1-]-----------[-2-]----------------[-----3----]

TimeLine: 
1: '2016-01-01 10:00:00.00000','2016-02-01 09:00:00.00000'

Processed Dates: 
1: '2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000'
2: '2016-01-01 11:00:00.00000','2016-01-01 12:00:00.00000'
3: '2016-01-01 13:00:00.00000','2016-01-01 13:30:00.00000'
4: '2016-01-01 13:30:00.00000','2016-01-01 14:30:00.00000'
5: '2016-01-01 15:30:00.00000','2016-01-01 16:30:00.00000'
6: '2016-01-01 16:30:00.00000','2016-01-01 17:00:00.00000'

Missing Dates:
1: '2016-01-01 12:00:00.00000','2016-01-01 13:00:00.00000'
2: '2016-01-01 14:30:00.00000','2016-01-01 15:30:00.00000'
3: '2016-01-01 17:00:00.00000','2016-01-02 09:00:00.00000'

Answer:

According to your comment I post my previous comment as answer. This solution uses my library Time4J (including the range-module):

// prepare parser
ChronoFormatter<PlainTimestamp> f = 
  ChronoFormatter.ofTimestampPattern( // five decimal digits
    "uuuu-MM-dd HH:mm:ss.SSSSS", PatternType.CLDR, Locale.ROOT);

// parse input to intervals - here the overall time window
TimestampInterval timeline = 
  TimestampInterval.between(
    f.parse("2016-01-01 10:00:00.00000"), 
    f.parse("2016-02-01 09:00:00.00000"));

// for more flexibility - consider a for-each-loop
TimestampInterval i1 =
    TimestampInterval.between(f.parse("2016-01-01 10:00:00.00000"), f.parse("2016-01-01 11:00:00.00000"));
TimestampInterval i2 =
    TimestampInterval.between(f.parse("2016-01-01 11:00:00.00000"), f.parse("2016-01-01 12:00:00.00000"));
TimestampInterval i3 =
    TimestampInterval.between(f.parse("2016-01-01 13:00:00.00000"), f.parse("2016-01-01 13:30:00.00000"));
TimestampInterval i4 =
    TimestampInterval.between(f.parse("2016-01-01 13:30:00.00000"), f.parse("2016-01-01 14:30:00.00000"));
TimestampInterval i5 =
    TimestampInterval.between(f.parse("2016-01-01 15:30:00.00000"), f.parse("2016-01-01 16:30:00.00000"));
TimestampInterval i6 =
    TimestampInterval.between(f.parse("2016-01-01 16:30:00.00000"), f.parse("2016-01-01 17:00:00.00000"));

// apply interval arithmetic
IntervalCollection<PlainTimestamp> icoll =
    IntervalCollection.onTimestampAxis().plus(Arrays.asList(i1, i2, i3, i4, i5, i6));
List<ChronoInterval<PlainTimestamp>> missed = icoll.withComplement(timeline).getIntervals();

// result
System.out.println(missed); 
// [[2016-01-01T12/2016-01-01T13), [2016-01-01T14:30/2016-01-01T15:30), [2016-01-01T17/2016-02-01T09)]

The core of the whole interval arithmetic is just done by the code fragment icoll.withComplement(timeline). The rest is only about creation of intervals. By applying a for-each-loop you can surely minimize again the count of lines in presented code.

The output is based on the canonical description of the intervals implicitly using toString(), for example: [2016-01-01T12/2016-01-01T13) The square bracket denotes a closed boundary while the round bracket to the right end denotes an open boundary. So we have here the standard case of half-open timestamp intervals (without timezone). While other interval types are possible I have chosen that type because it corresponds to the type of your input strings.

If you plan to combine this solution with Joda-Time in other parts of your app then keep in mind that a) there is not yet any special bridge between both libraries available and b) the conversion looses microsecond precision (Joda-Time only supports milliseconds) and c) Time4J has much more power than Joda-Time (for almost everything). Anyway, you can do this as conversion (important if you don't want to do the effort of bigger rewriting of your app):

ChronoInterval<PlainTimestamp> missed0 = missed.get(0);
PlainTimestamp tsp = missed0.getStart().getTemporal();
LocalDateTime ldt = // joda-equivalent
    new LocalDateTime(
        tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(), 
        tsp.getHour(), tsp.getMinute(), tsp.getSecond(), tsp.get(PlainTime.MILLI_OF_SECOND));
System.out.println(ldt); // 2016-01-01T10:00:00.000

About a Joda-only solution:

Joda-Time does only support instant intervals, not timestamp intervals without timezone. However, you could simulate that missing interval type by hardwiring the timezone to UTC (using fixed offset).

Another problem is missing support for five decimal digits. You can circumvent it by this hack:

DateTime start = 
  DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
    .withZoneUTC()
    .parseDateTime("2016-01-01 16:30:00.00000".substring(0, 23));
System.out.println(start); // 2016-01-01T16:30:00.000Z
DateTime end = ...;
Interval interval = new Interval(start, end);

The other more critical element of a solution is almost missing - interval arithmetic. You have to sort the intervals first by start instant (and then by end instant). After sorting, you can iterate over all intervals such that you find the gaps. The best thing Joda-Time can do for you here is giving you methods like isBefore(anotherInstant) etc. which you can use in your own solution. But it gets pretty much bloated.

Question:

I have a method as follows:

public void storeAppointment(int year,
    int monthOfYear,
    int dayOfMonth,
    int hourOfDayFrom,
    int minuteFrom,
    int hourOfDayUntil, int minuteUntil) {

    Calendar appointmentCalendar = Calendar.getInstance();
    appointmentCalendar.set(year, monthOfYear, dayOfMonth);
    TimeZone tz = appointmentCalendar.getTimeZone();
    DateTimeZone jodaTz = DateTimeZone.forID(tz.getID());
    DateTime appointmentDateTime = new DateTime(appointmentCalendar.getTimeInMillis(), jodaTz);
    LocalDate localDate = appointmentDateTime.toLocalDate();

    // At this point I have the appointment date.  

    // Should e.g. throw an exception for invalid time interval
    validate(hourOfDayFrom, minuteFrom, hourOfDayUntil, minuteUntil);

    // set proper times for calendar
    appointmentCalendar.set(Calendar.HOUR, hourOfDay);
    appointmentCalendar.set(Calendar.MINUTE, minute);
    // store date and times  
    // Should I update the localDate instead of the appointmentCalendar?
}    

Questions:

  1. How should I validate the hours/minutes? Should the actual date be included or is that not relevant?

  2. Should I update the localDate instead of the appointmentCalendar?


Answer:

You are working much too hard here.

Avoid legacy date-time classes

Avoid using the troublesome old date-time classes, such as Date & Calendar. Now legacy, supplanted by the java.time classes.

Do not mix date-time libraries

Do not mix the different date-time libraries. If using Joda-Time, no need for java.util.Date and no need for java.util.Calendar. And if using java.time classes, no need for Joda-Time and no need for java.util.Date/.Calendar.

The Joda-Time project, now in maintenance mode, advises migration to java.time.

Business rules

Should the actual date be included or is that not relevant?

We cannot tell you whether to consider the date or not. That depends on your business rules.

For example, if your business always takes a lunch break from noon to 13:00, then any business record marked with a time-of-day in that hour must be invalid. Date is irrelevant here if you always take the same lunch break every single day.

But if your scenario is something like recording a worker’s periods worked, then no two periods should overlap on the same day. In this case you must consider the date.

ZonedDateTime

Should I update the localDate instead of the appointmentCalendar?

a) You should not be mixing these classes, as discussed above.

b) In both Joda-Time and java.time, the LocalDate class represents a date-only value without time-of-day. And like its sibling Local… classes, it purposely has no concept of time zone. So not at all a fit for your purpose.

You need to use ZonedDateTime to represent a date and a time-of-day that has meaning within your intended time zone.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate ld = LocalDate.of( 2016 , 1 , 23 );
LocalTime lt = LocalTime.of( 12 , 30 );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z );

To get the current moment:

Instant instant = Instant.now();  // UTC.
ZonedDateTime zdt = instant.atZone( z );

…or, as a shortcut…

ZonedDateTime zdt = ZonedDateTime.now( z );

Also, the java.time classes are immutable objects. So you do not change ("mutate") their values. Instead you instantiate a new object based on the original’s values.

Interval

You may find the Interval class in ThreeTen-Extras project to be helpful here.

Interval a = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );

You can compare intervals with methods such as contains, overlaps, encloses, isBefore, and isAfter.

Boolean overlaps = a.overlaps( b );
Pass around objects

Rather than pass around mere primitives of piecemeal data, pass objects.

So instead of passing primitives such as integers for month, date, and hour, pass java.time objects such as Instant, OffsetDateTime, ZonedDateTime. When you have only the date or only the time-of-day, pass LocalDate or LocalTime.

Default time zone

To get the JVM’s current default time zone, call ZoneId.systemDefault.

But if important you should be asking the user for their desired/expected time zone. That default can change at any moment by any code in any thread of any app running in that JVM.

Question:

I have to model intervals of time that a commerce is open, for example: monday to friday 10hs-18hs, and with a function check if the commerce is open or not. I think the most rational is using the class "Interval" that Joda provides, but I can't instantiate an Interval only with days or hours. I must use Joda.


Answer:

Quoting javadoc of Interval:

A time interval represents a period of time between two instants.

Since "Monday at 10:00" is not an instant, you cannot use Interval.

The Joda-Time library doesn't have a class for what you want, so you'll have to write it yourself.

The requirement of "must use JODA" simple means that your class should take a LocalDateTime as input and use Joda-Time for querying the weekday and time-of-day.


A good reusable class (e.g. called OpenCalendar) might be used like this:

OpenCalendar openCal = new OpenCalendar();
openCal.add(DateTimeConstants.MONDAY   , new LocalTime(10, 0), new LocalTime(18, 0));
openCal.add(DateTimeConstants.TUESDAY  , new LocalTime(10, 0), new LocalTime(18, 0));
openCal.add(DateTimeConstants.WEDNESDAY, new LocalTime(10, 0), new LocalTime(18, 0));
openCal.add(DateTimeConstants.THURSDAY , new LocalTime(10, 0), new LocalTime(18, 0));
openCal.add(DateTimeConstants.FRIDAY   , new LocalTime(10, 0), new LocalTime(18, 0));

LocalDateTime now = LocalDateTime.now();
if (openCal.isOpen(now)) {
    // code here
}

Question:

I am having issues coming up with the right way to calculate an accurate value. Here is the problem. I have a resource that can be priced weekly, daily and hourly.

Lets assume it cost £5 per hour, £20 per day, and £150 a week.

What I want to do is calculate the cost to book the resource so if someone made a booking for 1 week, 3 days and 2 hours, they should end up paying only 150 + 60 + 10 = 220.

I am using JodaTime for reference. Here is what I have so far:

hour_price = resource.getPricing().getHourly() * ((mDuration.toStandardSeconds().getSeconds() % 86400) % 24);
day_price = resource.getPricing().getDaily() * (mDuration.toStandardDays().getDays() % 7);
week_price = resource.getPricing().getWeekly() * (Math.floor(mDuration.toStandardDays().getDays() / 7));
double cost = week_price + day_price + hour_price;

I think the week logic and the day logic is sound. Now I just need the remainder hour logic. Any pointers?


Answer:

You should start from biggest interval, take out it from duration, then go with smaller, take remaining seconds and use it for smallest interval:

week_price = resource.getPricing().getWeekly() * (mDuration.toStandardDays().getDays() / 7);
day_price =  resource.getPricing().getDaily() * (mDuration.toStandardDays().getDays() % 7);
hour_price = resource.getPricing().getHourly() * ((mDuration.toStandardSeconds().getSeconds() % 86400) / 3600);

So in last step you take how much hours you have left in seconds after taking down weeks and days (remainder after dividing with number of seconds in one day - in 24 hours), and then divide it with number of seconds per hour to get how much hours is it. If you still have seconds left, then add just one more hour to hour_price, since it started new hour.

Question:

I would like to find the "interval" between a datetime A, and another latter one. ex.

  • A) 21/09/2015 12:00:00
  • B) 25/09/2015 12:00:00

interval = 4 days 0h 0m 0s

I found this post: How to find difference between two Joda-Time DateTimes in minutes

But I am wondering isn't JodaTime's Interval supposed to do this ? If so, how?


Answer:

You are not looking for an interval (which has anchors on the timeline) but for a duration which is not bound to a specific time on the timeline. Durations based on any time units are called Period in Joda-Time.

DateTimeFormatter f = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
LocalDateTime ldtA = LocalDateTime.parse("21/09/2015 12:00:00", f);
LocalDateTime ldtB = LocalDateTime.parse("25/09/2015 12:00:00", f);
Period diff = new Period(ldtA, ldtB, PeriodType.dayTime());
System.out.println(PeriodFormat.wordBased(Locale.ENGLISH).print(diff)); // 4 days

Question:

Suppose I have 2 joda-time LocalDate or 2 DateTime or an Interval. I want to find if an interval, ie. months july and august exists inside or overlap with that Interval. If so, I want to find the interval of overlapping. Is this possible with joda-time? If yes, how?

        LocalDate date = LocalDate.parse("2014-03-25");
        Period period = Period.months(6);
        DateTime start = date.toDateTimeAtStartOfDay();
        DateTime end = date.plus(period).toDateTimeAtStartOfDay();
        Interval interval = new Interval(start, end);
        // How do I find if this interval contains july and anugust?

Answer:

This is how I got the desired result.

Interval interval = new Interval(start, end);
int startMonth = 7; int endMonth = 8;
DateTime month1 = start
        .withMonthOfYear(startMonth)
        .withDayOfMonth(1);
DateTime month2 = start
        .withMonthOfYear(endMonth + 1)
        .withDayOfMonth(1)
        .minusDays(1);
if (month1.compareTo(month2) > 0)
    month2.plusYears(1);
Interval season = new Interval(month1, month2);
if (season.overlaps(window)) {
    return  window.overlap(season);
}

The overlap and overlaps methods were really useful.