javaalgorithmjava-time

How to skip weekends while adding days to LocalDate in Java 8?


Other answers here refer to Joda API. I want to do it using java.time.

Suppose today's date is 26th Nov 2015-Thursday, when I add 2 business days to it, I want the result as Monday 30th Nov 2015.

I am working on my own implementation but it would be great if something already exists!

EDIT:

Is there a way to do it apart from looping over?

I was trying to derive a function like:

Y = f(X1,X2) where
Y is actual number of days to add,
X1 is number of business days to add, 
X2 is day of the week (1-Monday to 7-Sunday)

Then given X1 and X2 (derived from day of week of the date), we can find Y and then use plusDays() method of LocalDate.

I have not been able to derive it so far, its not consistent. Can anyone confirm that looping over until desired number of workdays are added is the only way?


Solution

  • The following method adds days one by one, skipping weekends, for positive values of workdays:

    public LocalDate add(LocalDate date, int workdays) {
        if (workdays < 1) {
            return date;
        }
    
        LocalDate result = date;
        int addedDays = 0;
        while (addedDays < workdays) {
            result = result.plusDays(1);
            if (!(result.getDayOfWeek() == DayOfWeek.SATURDAY ||
                  result.getDayOfWeek() == DayOfWeek.SUNDAY)) {
                ++addedDays;
            }
        }
    
        return result;
    }
    

    After some fiddling around, I came up with an algorithm to calculate the number of workdays to add or subtract.

    /**
     * @param dayOfWeek
     *            The day of week of the start day. The values are numbered
     *            following the ISO-8601 standard, from 1 (Monday) to 7
     *            (Sunday).
     * @param businessDays
     *            The number of business days to count from the day of week. A
     *            negative number will count days in the past.
     * 
     * @return The absolute (positive) number of days including weekends.
     */
    public long getAllDays(int dayOfWeek, long businessDays) {
        long result = 0;
        if (businessDays != 0) {
            boolean isStartOnWorkday = dayOfWeek < 6;
            long absBusinessDays = Math.abs(businessDays);
    
            if (isStartOnWorkday) {
                // if negative businessDays: count backwards by shifting weekday
                int shiftedWorkday = businessDays > 0 ? dayOfWeek : 6 - dayOfWeek;
                result = absBusinessDays + (absBusinessDays + shiftedWorkday - 1) / 5 * 2;
            } else { // start on weekend
                // if negative businessDays: count backwards by shifting weekday
                int shiftedWeekend = businessDays > 0 ? dayOfWeek : 13 - dayOfWeek;
                result = absBusinessDays + (absBusinessDays - 1) / 5 * 2 + (7 - shiftedWeekend);
            }
        }
        return result;
    }
    

    Usage Example:

    LocalDate startDate = LocalDate.of(2015, 11, 26);
    int businessDays = 2;
    LocalDate endDate = startDate.plusDays(getAllDays(startDate.getDayOfWeek().getValue(), businessDays));
    
    System.out.println(startDate + (businessDays > 0 ? " plus " : " minus ") + Math.abs(businessDays)
            + " business days: " + endDate);
    
    businessDays = -6;
    endDate = startDate.minusDays(getAllDays(startDate.getDayOfWeek().getValue(), businessDays));
    
    System.out.println(startDate + (businessDays > 0 ? " plus " : " minus ") + Math.abs(businessDays)
            + " business days: " + endDate);
    

    Example Output:

    2015-11-26 plus 2 business days: 2015-11-30

    2015-11-26 minus 6 business days: 2015-11-18