Count days between two dates with Java 8 while ign

2019-03-20 16:35发布

问题:

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

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

Additional Info

I am attempting to mock Excel's weekday(serial_number,[return_type]) formula in a way.

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 

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

Raw Image

Methods

  1. Simply 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);
    }
    
  2. Count number of days, ignoring certain days of 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;
    }
    
  3. Count 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;
    }
}

回答1:

If we talk about a Java 8 API, why not use the Java 8 features consequently…

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


回答2:

You you are using wrong Excel formula. See the section "Using SUM and INT function to count the number of workdays" of the site that you have provided. It is stating 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 curly braces indicates the day-of-weeks to be included. So for your case the formula will be:

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

Please see the attached screenshot:

It is returning 2247 as the following code returns:

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 have mentioned your doubt that the java.time may be not considering leap year, which is wrong, if you add the following piece of code

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 is printing:

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

Lastly the count will be 2247 which matches the Excel result.

Happy coding.

-Tapas