java - 使用 Java 8 计算两个日期之间的天数,同时忽略一周中的某些天数

标签 java performance java-8 java-time

下面我有3种方法。第一个很简单。它只是计算总天数。但是,第二个不仅会计算天数,还会忽略传递给该方法的星期几。

我的问题是第三种方法并不总是正确的。它应该匹配第二种方法。我猜它与闰年有关,因为当它不正确时,差异通常是 +=3|4 。

附加信息

我试图以某种方式模拟 Excel 的 weekday(serial_number,[return_type]) 公式。

serial_number = startDate:Date - daysOfWeekToInclude:Array<Integer>

例子

  | A       | B                                                  | C
  +---------+----------------------------------------------------+-----------
1 | Start   | =DATE(2014,9,7)                                    | 9/7/2014                 
2 | End     | =DATE(2025,6,13)                                   | 6/13/2025                    
3 | Include | ={1,2,4,6} (Mon, Tue, Thu, & Sat)                  | <Disp Only>
4 | Days    | =SUM(INT((WEEKDAY($B$1-{1,2,4,6},1)+$B$2-$B$1)/7)) | 2248 

这里有关于此功能的更多信息:How to count / calculate the number of days between two dates in Excel?

原始图像

enter image description here

方法

  1. 简单地计算两个日期之间的天数。

    public static int simpleDaysBetween(final LocalDate start,
            final LocalDate end) {
        return (int) ChronoUnit.DAYS.between(start, end);
    }
    
  2. 计算天数,忽略一周中的某些天,使用循环。

    public static int betterDaysBetween(final LocalDate start,
            final LocalDate end, final List<DayOfWeek> ignore) {
        int count = 0;
        LocalDate curr = start.plusDays(0);
    
        while (curr.isBefore(end)) {
            if (!ignore.contains(curr.getDayOfWeek())) {
                count++;
            }
            curr = curr.plusDays(1); // Increment by a day.
        }
    
        return count;
    }
    
  3. 计算天数。再次但没有循环。

    public static int bestDaysBetween(final LocalDate start,
            final LocalDate end, final List<DayOfWeek> ignore) {
        int days = simpleDaysBetween(start, end);
    
        if (days == 0) {
            return 0;
        }
    
        if (!ignore.isEmpty()) {
            int weeks = days / 7;
            int startDay = start.getDayOfWeek().getValue();
            int endDay = end.getDayOfWeek().getValue();
            int diff = weeks * ignore.size();
    
            for (DayOfWeek day : ignore) {
                int currDay = day.getValue();
                if (startDay <= currDay) {
                    diff++;
                }
                if (endDay > currDay) {
                    diff++;
                }
            }
    
            if (endDay > startDay) {
                diff -= endDay - startDay;
            }
    
            return days - diff;
        }
    
        return days;
    }
    

完整代码

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;

public class DayCounter {
    public static void main(String[] args) {
        final LocalDate start = LocalDate.of(2014, 9, 7);
        final LocalDate end = LocalDate.of(2025, 6, 13);
        List<DayOfWeek> ignore = Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY);

        print(start);
        print(end);

        System.out.println(simpleDaysBetween(start, end));
        System.out.println(betterDaysBetween(start, end, ignore));
        System.out.println(bestDaysBetween(start, end, ignore));
    }

    public static void print(LocalDate date) {
        System.out.printf("%s -> %s%n", date, date.getDayOfWeek());
    }

    public static int simpleDaysBetween(final LocalDate start,
            final LocalDate end) {
        return (int) ChronoUnit.DAYS.between(start, end);
    }

    public static int betterDaysBetween(final LocalDate start,
            final LocalDate end, final List<DayOfWeek> ignore) {
        int count = 0;
        LocalDate curr = start.plusDays(0);

        while (curr.isBefore(end)) {
            if (!ignore.contains(curr.getDayOfWeek())) {
                count++;
            }
            curr = curr.plusDays(1); // Increment by a day.
        }

        return count;
    }

    public static int bestDaysBetween(final LocalDate start,
            final LocalDate end, final List<DayOfWeek> ignore) {
        int days = simpleDaysBetween(start, end);

        if (days == 0) {
            return 0;
        }

        if (!ignore.isEmpty()) {
            int weeks = days / 7;
            int startDay = start.getDayOfWeek().getValue();
            int endDay = end.getDayOfWeek().getValue();
            int diff = weeks * ignore.size();

            for (DayOfWeek day : ignore) {
                int currDay = day.getValue();
                if (startDay <= currDay) {
                    diff++;
                }
                if (endDay > currDay) {
                    diff++;
                }
            }

            if (endDay > startDay) {
                diff -= endDay - startDay;
            }

            return days - diff;
        }

        return days;
    }
}

最佳答案

如果我们谈论 Java 8 API,为什么不相应地使用 Java 8 功能......

static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
    return Stream.iterate(start, d->d.plusDays(1))
                 .limit(start.until(end, ChronoUnit.DAYS))
                 .filter(d->!ignore.contains(d.getDayOfWeek()))
                 .count();
}

从 Java 9 开始,我们可以使用更简单的方法

static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
    return start.datesUntil(end)
        .filter(d->!ignore.contains(d.getDayOfWeek()))
        .count();
}

不过,可能值得使用 Set使用优于线性查找而不是 List :

static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
    if(ignore.isEmpty()) return start.until(end, ChronoUnit.DAYS);
    EnumSet<DayOfWeek> set = EnumSet.copyOf(ignore);
    return start.datesUntil(end)
        .filter(d->!ignore.contains(d.getDayOfWeek()))
        .count();
}

您可以考虑将参数更改为 Set<DayOfWeek> ,因为它不仅效率更高,而且更适合实际用例。而不是 Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY) , 你可以通过 EnumSet.of(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY) , 但您也可以使用像 EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY) 这样的结构, 表示典型的工作日。

您可以避免整天迭代,但需要特别注意极端情况,因此需要进行彻底的测试。并且只会在非常大的范围内得到返回。为了完整起见,这是优化后的变体:

static long daysBetween(LocalDate start, LocalDate end, Set<DayOfWeek> ignore) {
    long d1 = start.toEpochDay(), d2 = end.toEpochDay();
    if(d1 > d2) throw new IllegalArgumentException();
    if(ignore.isEmpty()) return d2 - d1;
    int incompleteWeek = 0;
    DayOfWeek startDoW = start.getDayOfWeek(), endDoW = end.getDayOfWeek();
    if(startDoW != endDoW) {
        for(int v1 = startDoW.getValue(), v2 = endDoW.getValue();
            v1 != v2 && d1 < d2; v1 = v1%7+1, d1++) {
                if(!ignore.contains(DayOfWeek.of(v1))) incompleteWeek++;
        }
    }
    return incompleteWeek + (d2 - d1) * (7 - ignore.size()) / 7;
}

在这里,ignore 的性能set 的查找并不重要,因为我们最多只查找六个值,但是,强制执行 Set ,即没有重复项,允许我们使用集合的大小来计算范围内完整周中包含的天数。完整周的开始日期和(唯一的)结束日期在一周中的同一天。所以代码只需要迭代天数,直到一周的开始和结束日期匹配。

关于java - 使用 Java 8 计算两个日期之间的天数,同时忽略一周中的某些天数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25798876/

相关文章:

java - 'generally accepted' 库中是否存在 FilteringBlockingQueue 的现有实现(Jakarta Commons、Guava、Spring,...?)

java - 将具有不同 header 的大消息写入多个接收者的效率问题

mysql - EXPLAIN 结果中的 "key_len"列更重要还是 "rows"列?

python - 在某些字符串上匹配正则表达式的 URL 非常慢

java - 如何在Java中同时计算近似值?

java - 交易ID创建

java - 子组件setSize的地方在哪里

java - 服务器JRE和客户端JRE的区别

java - 为什么 String::isEmpty 在无法从静态上下文中引用非静态方法时起作用?

java - Spring 文件上传失败,原因是请求的资源上存在 'Access-Control-Allow-Origin' header