如何在Java 8中将天数加到LocalDate时跳过周末?

27

这里的其他答案涉及Joda API。 我想使用java.time来完成。

假设今天是2015年11月26日星期四,当我在这个日期上加2个工作日时, 我希望结果为2015年11月30日星期一。

我正在做自己的实现,但如果已经存在某些东西,那将是很好的!

编辑:

除了循环以外,还有其他方法吗?

我试图得出一个像这样的函数:

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)

然后,鉴于日期的星期几派生出X1X2,我们可以找到Y,然后使用LocalDateplusDays()方法。

到目前为止我还没能够推导出来它,它并不一致。有人能确认循环增加到需要的工作日是唯一的方法吗?


https://www.baeldung.com/java-localdate-add-days-skip-weekends - firstpostcommenter
6个回答

41
以下方法按照正数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


这看起来很不错!我已经测试了一下,将每周从星期一到星期日的工作日增加到15天,并且它按照要求正常工作。你测试了多远?我也在类似的方向上工作,但是无法通过! - Anmol Gupta
1
我进行了类似的测试(对每周的每一天增加了越来越多的工作日),随后生成了数千个随机日期和工作日并将结果与简单的循环解决方案进行了测试。 - Modus Tollens
太好了!我想知道为什么大多数解决方案都涉及循环。这种解决方案在性能方面会更好,对吧?特别是当需要添加大量工作日时。 - Anmol Gupta
可能是因为通常需要一个跳过周末和节假日的解决方案。如果在我的应用程序中不是关键性能问题,我可能会自己使用简单的循环,因为它更容易理解。 - Modus Tollens
好的。意识到假期会使算法版本变得复杂。 - Anmol Gupta
显示剩余3条评论

14

这是一个支持正数和负数天数的版本,并将操作公开为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;
}

非常好!我需要接近那个并稍微调整一下,但是哇..这真的很性感。谢谢你。 - Eugene

6
确定工作日本质上是一个关于循环日期的问题,检查每个日期是否为周末或假期。
OpenGamma的Strata项目(我是其中的一名提交者)具有一个假期日历实现。API覆盖了在两个工作日后找到日期的情况。该实现具有优化的位图设计,性能比逐日循环更好。这可能会引起您的兴趣。请参考:Strata假期日历API位图设计

谢谢JodaStephen!您对Katja Christiansen的解决方案有什么评论吗? - Anmol Gupta
3
算法可以避免周六/周日,但是如果考虑到假期,除非使用位图,否则必须循环。 - JodaStephen

2

这是使用java.time类、一些函数式接口和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

支持负值和任何一周的日期!


1
这是一种将工作日加减到给定日历对象的方法:
/**
 * 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

例子:

计算我开始工作后除了周六和周日的总天数。

public class App {
    public static void main(String[] args) throws Exception {
        /** I write the code when 2019-8-15 */
        LocalDate now = LocalDate.now();
        LocalDate startWork = LocalDate.parse("2019-06-17");
        /** get all days */
        long allDays = Duration.between(startWork.atStartOfDay(), now.atStartOfDay()).toDays() + 1;
        System.out.println("This is the " + allDays + "th day you enter the company.");
        /** variable to store day except sunday and saturday */
        long workDays = allDays;

        for (int i = 0; i < allDays; i++) {

            if (startWork.getDayOfWeek() == DayOfWeek.SATURDAY || startWork.getDayOfWeek() == DayOfWeek.SUNDAY) {
                workDays--;
            }

            startWork = startWork.plusDays(1);
        }

        System.out.println("You actually work for a total of " + workDays + " days.");

    }
}
/**
This is the 60th day you enter the company.
You actually work for a total of 44 days.
*/
  • 希望能对你有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接