Search code examples
javascriptnode.jsdate-fns

Get overlapping months in two ranges using date-fns


I'm trying to get the overlapping months in two date intervals using date-fns:

const range1 = { start: new Date('2018-01-01'), end: new Date('2019-01-01') }
const range2 = { start: new Date('2018-07-03'), end: new Date('2019-12-01') }
getOverlappingMonthsInIntervals(a, b)) // 6

Looking at date-fns docs, I see the method getOverlappingDaysInIntervals but no similar method for months. Is there a way to achieve this with date-fns or a workaround (other than getOverlappingDaysInIntervals/30)


Solution

  • You can create your own overlapping months function using some of the other date-fns functions. See below for an example.

    In my answer I make the assumption that two intervals have an overlapping month if both intervals span the entirety of that calendar month. Given your example ranges there are 5 overlapping months: August, September, October, November and December of 2018.

      JFMAMJJASONDJFMAMJJASOND
    1 |------+++++|
    2       |+++++-----------|
    
    1. First we need to determine if the two intervals overlap.
    2. If the two intervals overlap then determine the start and end date of the where the intervals overlap.
      • If the two intervals overlap, the overlap start date will be the maximum of the two intervals start dates. And, the overlap end date will be the minimum of the two intervals end dates.
      • We can use the max and min functions for this.
    3. Once we find the start and end of the overlap we need to determine how many months are in this interval.
    import {
      areIntervalsOverlapping,
      max,
      min,
      // differenceInMonths,
      differenceInCalendarMonths,
    } from 'date-fns'
    
    const getOverlappingMonthsInterval = (r1, r2) => {
      if (areIntervalsOverlapping(r1, r2)) {
        const start = max([r1.start, r2.start])
        const end = min([r1.end, r2.end])
        return differenceInCalendarMonths(end, start) // or use `differenceInMonths`
      } else return 0
    }
    
    const range1 = { start: new Date('2018-01-01'), end: new Date('2019-01-01') }
    const range2 = { start: new Date('2018-07-01'), end: new Date('2019-12-01') }
    
    console.log(getOverlappingMonthsInterval(range1, range2)) // 5
    

    I like to use the conditional operator for functions like this:

    // one-liner
    const getOverlappingMonthsInterval = (r1, r2) =>
      areIntervalsOverlapping(r1, r2)
        ? differenceInCalendarMonths(
            min([r1.end, r2.end]),
            max([r1.start, r2.start])
          )
        : 0