简体   繁体   English

两个日期之间的小数月差(Scala 或 Java)

[英]Fractional month difference between 2 dates (Scala or Java)

I'm trying to find how many months are between 2 dates.我试图找出两个日期之间有多少个月。 My code is something like this right now我的代码现在是这样的

ChronoUnit.MONTHS.between(d1, d2)

The problem is that the result is a long.问题是结果很长。 For example if the dates differ only in a few days I should get a result something like 0.34 instead of 0.例如,如果日期仅在几天内不同,我应该得到类似 0.34 而不是 0 的结果。

Also I need my code to account for the calendar, I cant assume each month has 31 days.我还需要我的代码来说明日历,我不能假设每个月有 31 天。

Diff between 1999-05-12 and 1999-08-24
Assuming all months have 31 days for simplicity

result = (19/31 + 31/31 + 31/31 + 24/31) = 2.793
According to the calendar we replace the 31s with the correct number of days for that specific year and month

Here is my solution:这是我的解决方案:

public static double monthsBetween(LocalDate start, LocalDate end) {
    if (start.isAfter(end)) throw new IllegalArgumentException("Start must be before end!");

    var lastDayOfStartMonth = start.with(TemporalAdjusters.lastDayOfMonth());
    var firstDayOfEndMonth = end.with(TemporalAdjusters.firstDayOfMonth());
    var startMonthLength = (double)start.lengthOfMonth();
    var endMonthLength = (double)end.lengthOfMonth();
    if (lastDayOfStartMonth.isAfter(firstDayOfEndMonth)) { // same month
        return ChronoUnit.DAYS.between(start, end) / startMonthLength;
    }
    long months = ChronoUnit.MONTHS.between(lastDayOfStartMonth, firstDayOfEndMonth);
    double startFraction = ChronoUnit.DAYS.between(start, lastDayOfStartMonth.plusDays(1)) / startMonthLength;
    double endFraction = ChronoUnit.DAYS.between(firstDayOfEndMonth, end) / endMonthLength;
    return months + startFraction + endFraction;
}

The idea is that you find the last day of start 's month ( lastDayOfStartMonth ), and the first day of end 's month ( firstDayOfEndMonth ) using temporal adjusters.这个想法是您使用时间调整器找到start月份的最后一天 ( lastDayOfStartMonth ) 和end月份的第一天 ( firstDayOfEndMonth )。 These two dates are very important.这两个日期非常重要。 The number you want is the sum of:您想要的数字是以下各项的总和:

  • the fractional number of a month between start and lastDayOfStartMonth startlastDayOfStartMonth之间的月份小数
  • the whole number of months between lastDayOfStartMonth and firstDayOfEndMonth . lastDayOfStartMonthfirstDayOfEndMonth之间的整数月数。
  • the fractional number of a month between firstDayOfEndMonth and end . firstDayOfEndMonthend之间一个月的小数。

Then there is the edge case of when both dates are within the same month, which is easy to handle.然后是两个日期都在同一个月内的边缘情况,这很容易处理。

By using this definition, the nice property that the number of months between the first day of any two months is always a whole number is maintained.通过使用这个定义,任何两个月的第一天之间的月数总是一个整数这一很好的特性得以保持。

Note that in the first calculation, you have to add one day to lastDayOfStartMonth , because ChronoUnit.between treats the upper bound as exclusive, but we actually want to count it as one day here.请注意,在第一次计算中,您必须将一天添加到lastDayOfStartMonth ,因为ChronoUnit.between将上限视为独占,但我们实际上想将其计为一天。

To approach this problem, you need to consider the following cases:要解决此问题,您需要考虑以下情况:

  • dates belong to the same year and month;日期属于同年同月;

  • dates belong to different year and/or month;日期属于不同的年份和/或月份;

  • dates are invalid.日期无效。

When dates belong to the same year and month, then the result would be the difference in days between the two dates divided by the number of days in this month, which can be found using LocalDate.lengthOfMonth() .当日期属于同一年和同月时,结果将是两个日期之间的天数差除以本月的天数,可以使用LocalDate.lengthOfMonth()找到。

In the general case, the range of dates can be split into three parts:在一般情况下,日期范围可以分为三个部分:

  • two fractional parts at the beginning and at the end of the given range of dates (both could be evaluated using the approach for the simplest case when both data belong to the same year/month )给定日期范围开始和结束时的两个小数部分(当两个数据属于同一年/月时,都可以使用最简单情况的方法进行评估)
  • the whole part , we can use ChronoUnit.MONTHS.between() to calculate it.整个部分,我们可以使用ChronoUnit.MONTHS.between()来计算它。

Here's how implementation might look like ( d1 - inclusive, d2 - exclusive):下面是实现的样子( d1 - 包含, d2 - 不包含):

public static double getFractionalMonthDiff(LocalDate d1, LocalDate d2) {
    if (d1.isAfter(d2)) throw new IllegalArgumentException(); // or return a value like -1
    
    if (d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth()) { // dates belong to same month and year
        
        return getFractionalPartOfMonth(d2.getDayOfMonth() - d1.getDayOfMonth(), d1.lengthOfMonth());
    }
    
    int monthLen1 = d1.lengthOfMonth();
    
    return getFractionalPartOfMonth(monthLen1 - (d1.getDayOfMonth() - 1), monthLen1) // from the given day of month of the First Date d1 Inclusive to the Last day of month
        + getFractionalPartOfMonth(d2.getDayOfMonth() - 1, d2.lengthOfMonth())       // from the First day of month to given day of month of the Second Date d2 Exclusive (for that reason 1 day was subtracted, and similarly on the previous line 1 day was added)
        + ChronoUnit.MONTHS.between(d1.withDayOfMonth(monthLen1), d2.withDayOfMonth(1));
}

public static double getFractionalPartOfMonth(int daysInterval, int monthLength) {
    return daysInterval / (double) monthLength;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM