8

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

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

附加信息

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

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 

这里有关于这个函数的更多信息:如何在 Excel 中计算/计算两个日期之间的天数?

原始图像

在此处输入图像描述

方法

  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;
    }
}
4

2 回答 2

14

如果我们谈论 Java 8 API,为什么不使用 Java 8 的特性呢……</p>

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具有优于线性查找的 a 而不是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集合查找的性能并不重要,因为我们最多只查找六个值,但是,强制 a Set,即没有重复,允许我们使用集合的大小来计算包含在完整周中的天数范围。完整周的开始日期和(不包括)结束日期在一周中的同一天。所以代码只需要迭代日期,直到星期的开始和结束日期匹配。

于 2014-09-12T13:15:58.527 回答
4

您使用了错误的 Excel 公式。请参阅您提供的站点的“使用 SUM 和 INT 函数计算工作日数”部分。它将公式说明为:

=SUM(INT((WEEKDAY(A2-{2,3,4,5,6})+B2-A2)/7))

在 Excel 中,星期日是 1,星期六是 7。花括号内的数字表示要包括的星期几。因此,对于您的情况,公式将是:

=SUM(INT((WEEKDAY(A2-{2,3,5,7})+B2-A2)/7))

请参阅随附的屏幕截图:

在此处输入图像描述

它返回 2247,如下代码返回:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;


public class SO25798876 {

    public static void main(String[] args) {
        String strStartDate = "09/07/2014";
        String strEndDate = "06/13/2025";
        String pattern = "MM/dd/yyyy";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDate startDate = LocalDate.parse(strStartDate, formatter);
        LocalDate endDate = LocalDate.parse(strEndDate, formatter);

        int count = 0;

        while(startDate.isBefore(endDate) || startDate.isEqual(endDate)) {  // you may want to not to use the isEqual method        
            DayOfWeek dayOfWeek = startDate.getDayOfWeek();         

            if(!(dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.WEDNESDAY || dayOfWeek == DayOfWeek.FRIDAY)) {
                count++;
            }           

            startDate = startDate.plusDays(1);
        }

        System.out.println(count);  
    }
}

您还提到了您java.time可能没有考虑闰年的疑问,如果您添加以下代码,这是错误的

long year = startDate.getYear();

if(Year.isLeap(year)) {
    Month month = startDate.getMonth();

    if(month == Month.FEBRUARY && startDate.getDayOfMonth() == 29) {
        System.out.println("Calculated 29th Feb for the year: " + year);
    }
}

你会看到它正在打印:

Calculated 29th Feb for the year: 2016
Calculated 29th Feb for the year: 2020
Calculated 29th Feb for the year: 2024

最后计数将2247与 Excel 结果匹配。

快乐编码。

-小吃

于 2014-09-12T12:45:52.977 回答