【问题标题】:How to skip weekends while adding days to LocalDate in Java 8?如何在 Java 8 中向 LocalDate 添加天数时跳过周末?
【发布时间】:2015-11-26 15:48:39
【问题描述】:

这里的其他答案参考 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()方法。

到目前为止我还没有能够推导出它,它并不一致。任何人都可以确认循环直到添加所需的工作日数是唯一的方法吗?

【问题讨论】:

    标签: java algorithm java-time


    【解决方案1】:

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

    【讨论】:

    • 非常好!我需要接近那个并稍微调整一下,但是男孩..这很性感。谢谢你
    【解决方案2】:

    对于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 个工作日来测试它,并且它可以按要求工作。你测试了多远?我也在做类似的工作,但无法通过它!
    • 我做了一个类似的测试(在一周的每一天添加越来越多的工作日),然后生成数千个随机日期和工作日,并根据简单循环解决方案测试结果。
    • 太棒了!我想知道为什么大多数解决方案都提到循环。这种解决方案在性能方面会好得多,对吧?特别是当需要添加大量工作日时
    • 可能是因为经常需要跳过周末节假日的解决方案。如果它在我的应用程序中对性能不是很重要,我可能会自己使用简单循环,因为它更容易理解。
    • 对。意识到假期,算法版本会变得复杂。
    【解决方案3】:

    这是一种在给定日历对象中增加或减少工作日的方法:

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

    【讨论】:

      【解决方案4】:

      确定工作日基本上是一个循环日期的问题,检查每个日期是周末还是假期。

      来自 OpenGamma(我是提交者)的 Strata 项目有一个 holiday calendar 的实现。 API 涵盖了在 2 个工作日后找到日期的情况。该实现有一个optimized bitmap design,它比日常循环执行得更好。这里可能很有趣。

      【讨论】:

      • 感谢乔达斯蒂芬!您对 Katja Christiansen 对这个问题的解决方案有任何意见吗?
      • 算法可以避开周六/周日,但是一旦考虑到假期,除非使用位图,否则必须循环。
      【解决方案5】:

      这是一种使用 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
      

      支持负值和任何工作日!

      【讨论】:

        【解决方案6】:

        示例:

        计算从我开始工作之日起的总天数,周六和周日除外。

        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.
        */
        
        • 希望对您有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-16
          • 2019-02-24
          • 2014-11-18
          • 1970-01-01
          相关资源
          最近更新 更多