web-dev-qa-db-fra.com

Comment ignorer les week-ends lors de l'ajout de jours à LocalDate dans Java 8?

D'autres réponses ici font référence à l'API Joda. Je veux le faire en utilisant Java.time.

Supposons que la date du jour soit le 26 novembre 2015 au jeudi, date à laquelle j'ajoute 2 jours ouvrables, .__ Je veux que le résultat soit présenté le lundi 30 novembre 2015.

Je travaille sur ma propre implémentation mais ce serait génial si quelque chose existait déjà!

MODIFIER:

Y a-t-il un moyen de le faire en dehors de la boucle?

J'essayais de dériver une fonction comme:

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)

Puis, étant donné X1 et X2 (dérivés du jour de la semaine de la date), nous pouvons trouver Y et ensuite utiliser la méthode plusDays() de LocalDate

Je n'ai pas été capable de le tirer jusqu'à présent, ce n'est pas cohérent. Quelqu'un peut-il confirmer que le bouclage jusqu'à l'ajout du nombre de jours de travail souhaité est le seul moyen?

17
Anmol Gupta

La méthode suivante ajoute les jours un par un, en sautant les week-ends, pour les valeurs positives de 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;
}

Après quelques manipulations, j'ai proposé un algorithme pour calculer le nombre de jours ouvrables à ajouter ou soustraire.

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

Exemple d'utilisation:

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

Exemple de sortie:

2015-11-26 plus 2 jours ouvrables: 2015-11-30

2015-11-26 moins 6 jours ouvrables: 2015-11-18

18
Modus Tollens

Voici une version qui prend en charge le nombre de jours positif et négatif et expose l'opération sous la forme TemporalAdjuster. Cela vous permet d'écrire:

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

Code:

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

Déterminer les jours ouvrables est fondamentalement une question de boucle sur les dates, en vérifiant s’il s’agit d’un week-end ou d’un jour férié.

Le projet Strata de OpenGamma (je suis un committer) a une implémentation d'un calendrier holiday . Le API couvre le cas de la recherche de la date 2 jours ouvrables plus tard. L'implémentation a une conception bitmap optimisée qui fonctionne mieux que la mise en boucle jour par jour. Cela peut être intéressant ici.

4
JodaStephen

C'est un moyen d'ajouter des jours ouvrables à l'aide de Java.time Classes, de certaines interfaces fonctionnelles et de lambda ...

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

Prend en charge les valeurs négatives et à partir de n'importe quel jour de la semaine!

1
Franchoo

Il s'agit d'une méthode qui consiste à ajouter ou à soustraire des jours ouvrables à un objet de calendrier donné:

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