Skip to content

Instantly share code, notes, and snippets.

@pborissow
Last active May 14, 2024 13:21
Show Gist options
  • Save pborissow/46ad9a8ef9d55afde40053a6764adeb7 to your computer and use it in GitHub Desktop.
Save pborissow/46ad9a8ef9d55afde40053a6764adeb7 to your computer and use it in GitHub Desktop.

Months Between

Prototype code used to compute fractional month difference between two dates.

Tests

    String[][] tests = {
        {"2024-04-14", "2024-04-04"}, //<1 month (10 days or 0.333)
        {"2024-04-04", "2024-04-14"}, //<1 month (10 days or 0.333)
        {"2024-03-04", "2024-04-04"}, //1 month
        {"2024-03-04", "2025-04-04"}, //13 months
        {"2024-06-04", "2024-07-04"}, //1 month
        {"2024-11-28", "2025-02-28"}, //3 months
        {"2024-02-28", "2025-02-28"}, //12 months
        {"2024-02-29", "2025-02-28"}, //12 months (leap year)
        {"2024-11-30", "2025-02-28"}, //3 months
        {"2024-01-01", "2024-01-31"}, //<1 month
        {"2024-01-01", "2025-01-01"}, //12 months
        {"2024-01-01", "2025-12-01"}, //23 months
        {"2024-11-30", "2024-12-31"}, //1 month
        {"2024-09-30", "2024-12-31"}, //3 months
        {"2024-06-30", "2024-12-31"}, //6 months
        {"2024-03-01", "2024-04-15"}, //1+ months

        {"2024-03-31", "2024-04-01"}, //<1 month (1 day)
        {"2024-03-31", "2024-04-05"}, //<1 month (5 days)
        {"2024-03-31", "2024-04-15"}, //0.5 months (15 days)
        {"2024-03-31", "2024-04-30"}, //1 month
        {"2024-03-31", "2024-05-01"}, //1+ month

        {"2024-11-28", "2025-02-27"}, //<3 months
        {"2024-11-28", "2025-02-28"}, //3 months
        {"2024-11-29", "2025-02-28"}, //<3 months
        {"2024-11-30", "2025-02-28"}, //3 months
        {"2024-12-30", "2025-02-28"}, //<2 months

        {"2024-11-15", "2024-11-30"}, //0.5 months
        {"2024-11-30", "2024-11-15"}, //-0.5 months
        {"2024-11-15", "2025-11-30"}, //12.5 months
        {"2023-01-28", "2023-02-27"}, //<1 month

        {"2023-02-27", "2023-03-26"}, //<1 month
        {"2023-02-27", "2023-03-27"}, //1 month
        {"2023-02-27", "2023-03-28"}, //1+ month
        {"2023-02-27", "2023-03-29"}, //1+ month
        {"2023-02-27", "2023-03-30"}, //1+ month
        {"2023-02-27", "2023-03-31"}, //1+ month

        {"2023-02-28", "2023-03-30"}, //1+ month
        {"2023-02-28", "2023-03-31"}, //1 month

        {"2023-01-27", "2023-02-28"}, //>1 month
        {"2023-01-28", "2023-02-28"}, //1 month
        {"2023-01-29", "2023-02-28"}, //<1 month
    };

Code

    private static double getMonthsBetween(LocalDate start, LocalDate end) {


      //Check if the start date is after the end date. Swap dates as needed
        boolean negate = false;
        if (start.isAfter(end)){
            negate = true;
            LocalDate t = start;
            start = end;
            end = t;
        }


      //Check if start/end dates fall on the last of the month
        boolean startIsLastDayInMonth = start.getDayOfMonth() == start.lengthOfMonth();
        boolean endIsLastDayInMonth = end.getDayOfMonth() == end.lengthOfMonth();


      //Calulate months between the 2 dates using Java's built-in ChronoUnit
      //Note that the ChronoUnit "rounds down" the interval between the dates.
        long m = ChronoUnit.MONTHS.between(start, end);


      //When the 2 dates fall on the same day in the month, and the dates aren't
      //on the last day of the month, simply return the value returned by the
      //ChronoUnit class
        int startDay = start.getDayOfMonth();
        int endDay = end.getDayOfMonth();
        if (startDay==endDay){

            if (startIsLastDayInMonth && !endIsLastDayInMonth ||
                !startIsLastDayInMonth && endIsLastDayInMonth){
                //Example: 2024-11-28 2025-02-28
            }
            else{
                return m;
            }
        }


      //If we're still here, compute fractions
        double fraction = 0.0;
        if (m==0 && start.getMonthValue()==end.getMonthValue()){

          //Simply compare the days of the month
            fraction = (end.getDayOfMonth()-start.getDayOfMonth())/(double)end.lengthOfMonth();

        }
        else{


          //Create new end date using the original end date. Adjust the day
          //of the month to match the start date. The new date will be either
          //before or after the original end date.
            int maxDays = LocalDate.of(end.getYear(), end.getMonthValue(), 1).lengthOfMonth();
            LocalDate e2 = LocalDate.of(end.getYear(), end.getMonthValue(), Math.min(start.getDayOfMonth(), maxDays));

            if (start.getDayOfMonth()>maxDays){

              //Create new date a few days after the end of the month
                LocalDate d = e2.plusDays(start.getDayOfMonth()-maxDays);

              //Calulate months between the start date and the new date
                m = ChronoUnit.MONTHS.between(start, d);


              //Calculate fraction
                if (startIsLastDayInMonth && endIsLastDayInMonth){}
                else{

                    if (!startIsLastDayInMonth){
                        fraction = -((start.lengthOfMonth()-start.getDayOfMonth())/(double)start.lengthOfMonth());
                    }
                    else{
                        fraction = -(1-((end.getDayOfMonth())/(double)maxDays));
                    }
                }
            }
            else{


              //Calulate months between the start date and the new end date
                m = ChronoUnit.MONTHS.between(start, e2);


              //Calculate fraction
                if (e2.isAfter(end)){

                  //subtract from e2
                    int n = e2.getDayOfMonth()-end.getDayOfMonth();
                    double f = (double)n/(double)end.lengthOfMonth();

                    if (m==0){
                        fraction = 1-f;
                    }
                    else{
                        fraction = -f;
                    }

                }
                else if (e2.isBefore(end)){

                  //add from e2
                    fraction = (end.getDayOfMonth()-start.getDayOfMonth())/(double)end.lengthOfMonth();

                }
            }
        }


      //Add months and fractions
        double diff = fraction+(double)m;


      //When the 2 dates fall on the the last day of the month, round up
        if (startIsLastDayInMonth && endIsLastDayInMonth){
            diff = Math.round(diff);
        }


      //Return diff
        return negate ? -diff : diff;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment