Count days between two dates with Java 8, ignoring specific days of the week

Below I have 3 methods. The first is very simple. It just counts the total number of days. The second, however, will not only take into account the days, but also ignore the days of the week that are passed to the method.

My problem is that the third method is not always correct. It should match the second method. I assume this has something to do with leap years, because the difference is usually + = 3 | 4 when it is wrong.

Additional Information

I am trying to mock Excel weekday(serial_number,[return_type]) formula weekday(serial_number,[return_type]) .

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

Example

  | 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 

Here is more information about this function: How to count / calculate the number of days between two dates in Excel?

Source image

enter image description here

Methods

  • Just count the number of days between two dates.

     public static int simpleDaysBetween(final LocalDate start, final LocalDate end) { return (int) ChronoUnit.DAYS.between(start, end); } 
  • Count the number of days ignoring specific days of the week using a loop.

     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; } 
  • Count the number of days. again, but without a loop.

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

Full code

 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; } } 
+7
source share
2 answers

If we are talking about the Java 8 API, why not use the Java 8 features, therefore ...

 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(); } 

Starting with Java 9, we can use an even simpler

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

Although it might be worth using Set with a better linear search than 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(); } 

You can consider changing the parameter to Set<DayOfWeek> , since it is not only more efficient, but also better suited for real-world use cases. Instead of Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY) you can pass EnumSet.of(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY) , but you can also use EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY) to indicate typical business days.

You can avoid iteration throughout the days, but this requires special attention to corner cases and, therefore, rigorous testing. And pay off only for really large ranges. For completeness, this is an optimized option:

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

Here, the performance of searching ignore sets does not matter, since we are looking for no more than six values, however, using Set , that is, the absence of duplicates, allows us to use the size of the sets to calculate the number of days contained in the full weeks of the range. Full weeks have the same day of the week for the start and (exclusive) end dates. Thus, the code should only iterate over the days until the start and end days of the week match.

+13
source

You are using the wrong Excel formula. See the โ€œUsing the SUM and INT Functions to Count the Number of Business Daysโ€ section of the site that you provided. He formulates the formula as:

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

In Excel, Sunday is 1 and Saturday is 7. The numbers inside the braces indicate the day of the week to be included. Thus, for your case, the formula will be:

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

See attached screenshot:

enter image description here

2247 is returned when the following code is returned:

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

You also mentioned your doubt that java.time might not take into account a leap year, which is not true if you add the following code snippet

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

You will see that it prints:

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

Finally, the counter will be 2247 , which corresponds to the result of Excel.

Happy coding.

-Tapas

+4
source

Source: https://habr.com/ru/post/1202390/


All Articles