Как пропустить выходные при добавлении дней в LocalDate в Java 8?

другие ответы здесь относятся к Joda API. Я хочу сделать это с помощью java.time.

предположим, что сегодняшняя дата-26 ноября 2015-четверг, когда я добавляю к нему 2 рабочих дня, Я хочу получить результат в понедельник 30 ноября 2015 года.

Я работаю на свою реализацию, но было бы здорово, если что-то уже существует!

EDIT:

есть ли способ сделать это только перебором?

Я пытался получить функцию например:

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)
дано X1 и X2 (производный от дня недели даты), мы можем найти Y и затем использовать plusDays() метод LocalDate.

Я не смог вывести его до сих пор, его не согласуется. Может ли кто-нибудь подтвердить, что цикл до добавления желаемого количества рабочих дней является единственным способом?

5 ответов


следующий метод добавляет дни один за другим, пропуская выходные, для положительных значений 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;
}

после некоторой возни, я придумал алгоритм для вычислить количество рабочих дней для добавления или вычитания.

/**
 * @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;
}

Пример Использования:

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

Пример:

2015-11-26 плюс 2 рабочих дня: 2015-11-30

2015-11-26 минус 6 рабочих дней: 2015-11-18


вот версия, которая поддерживает как положительное, так и отрицательное количество дней и выставляет операцию как TemporalAdjuster. Что позволяет писать:

LocalDate datePlus2WorkingDays = date.with(addWorkingDays(2));

код:

/**
 * Returns the working day adjuster, which adjusts the date to the n-th following
 * working day (i.e. excluding Saturdays and Sundays).
 * <p>
 * If the argument is 0, the same date is returned if it is a working day otherwise the
 * next working day is returned.
 *
 * @param workingDays the number of working days to add to the date, may be negative
 *
 * @return the working day adjuster, not null
 */
public static TemporalAdjuster addWorkingDays(long workingDays) {
  return TemporalAdjusters.ofDateAdjuster(d -> addWorkingDays(d, workingDays));
}

private static LocalDate addWorkingDays(LocalDate startingDate, long workingDays) {
  if (workingDays == 0) return nextOrSameWorkingDay(startingDate);

  LocalDate result = startingDate;
  int step = Long.signum(workingDays); //are we going forward or backward?

  for (long i = 0; i < Math.abs(workingDays); i++) {
    result = nextWorkingDay(result, step);
  }

  return result;
}

private static LocalDate nextOrSameWorkingDay(LocalDate date) {
  return isWeekEnd(date) ? nextWorkingDay(date, 1) : date;
}

private static LocalDate nextWorkingDay(LocalDate date, int step) {
  do {
    date = date.plusDays(step);
  } while (isWeekEnd(date));
  return date;
}

private static boolean isWeekEnd(LocalDate date) {
  DayOfWeek dow = date.getDayOfWeek();
  return dow == SATURDAY || dow == SUNDAY;
}

определение рабочих дней-это, по сути, вопрос цикла дат, проверка того, является ли каждый из них выходным или праздничным.

на пласта проект от OpenGamma (я коммиттер) имеет реализацию календарь праздников. The API охватывает случай нахождения даты через 2 рабочих дня. Реализация имеет оптимизированный растровый дизайн это работает лучше, чем днем цикла. Это может представлять интерес здесь.


Это метод, который добавляет или вычитает рабочие дни для данного объекта календаря:

/**
 * This method adds workdays (MONDAY - FRIDAY) to a given calendar object.
 * If the number of days is negative than this method subtracts the working
 * days from the calendar object.
 * 
 * 
 * @param cal
 * @param days
 * @return new calendar instance
 */
public static Calendar addWorkDays(final Calendar baseDate, final int days) {
    Calendar resultDate = null;
    Calendar workCal = Calendar.getInstance();
    workCal.setTime(baseDate.getTime());

    int currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK);

    // test if SATURDAY ?
    if (currentWorkDay == Calendar.SATURDAY) {
        // move to next FRIDAY
        workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -1 : +2));
        currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK);
    }
    // test if SUNDAY ?
    if (currentWorkDay == Calendar.SUNDAY) {
        // move to next FRIDAY
        workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -2 : +1));
        currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK);
    }

    // test if we are in a working week (should be so!)
    if (currentWorkDay >= Calendar.MONDAY && currentWorkDay <= Calendar.FRIDAY) {
        boolean inCurrentWeek = false;
        if (days > 0)
            inCurrentWeek = (currentWorkDay + days < 7);
        else
            inCurrentWeek = (currentWorkDay + days > 1);

        if (inCurrentWeek) {
            workCal.add(Calendar.DAY_OF_MONTH, days);
            resultDate = workCal;
        } else {
            int totalDays = 0;
            int daysInCurrentWeek = 0;

            // fill up current week.
            if (days > 0) {
                daysInCurrentWeek = Calendar.SATURDAY - currentWorkDay;
                totalDays = daysInCurrentWeek + 2;
            } else {
                daysInCurrentWeek = -(currentWorkDay - Calendar.SUNDAY);
                totalDays = daysInCurrentWeek - 2;
            }

            int restTotalDays = days - daysInCurrentWeek;
            // next working week... add 2 days for each week.
            int x = restTotalDays / 5;
            totalDays += restTotalDays + (x * 2);

            workCal.add(Calendar.DAY_OF_MONTH, totalDays);
            resultDate = workCal;

        }
    }   
    return resultDate;
}

Это способ добавить рабочие дни с помощью java.классы времени, некоторые функциональные интерфейсы и лямбда...

IntFunction<TemporalAdjuster> addBusinessDays = days -> TemporalAdjusters.ofDateAdjuster(
    date -> {
      LocalDate baseDate =
          days > 0 ? date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
              : days < 0 ? date.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) : date;
      int businessDays = days + Math.min(Math.max(baseDate.until(date).getDays(), -4), 4);
      return baseDate.plusWeeks(businessDays / 5).plusDays(businessDays % 5);
    });

LocalDate.of(2018, 1, 5).with(addBusinessDays.apply(2));
//Friday   Jan 5, 2018 -> Tuesday Jan  9, 2018

LocalDate.of(2018, 1, 6).with(addBusinessDays.apply(15));
//Saturday Jan 6, 2018 -> Friday  Jan 26, 2018

LocalDate.of(2018, 1, 7).with(addBusinessDays.apply(-10));
//Sunday   Jan 7, 2018 -> Monday  Dec 25, 2017

поддерживает отрицательные значения и с любого дня недели!