【问题标题】:How to add Business hours to Date considering not adding weekends ? - Java考虑不添加周末,如何将营业时间添加到日期? - 爪哇
【发布时间】:2016-01-22 09:45:56
【问题描述】:

我想添加一定的小时数,忽略周末

例如,

(星期五 18:00)+ 48 =(星期二 18:00)(星期六和星期日被忽略)

由于公司 24 小时工作,所以营业时间是 24 小时。但我仍然不知道如何仅在工作日添加时间

函数可以是这样的:

public Date getTaskEndTime(Calendar startDate, int hours){
   // calculate the end time by adding the hours ignoring the weekends
}

【问题讨论】:

  • 您是否必须考虑非周末的国定假日?你有自己的努力吗?
  • 不仅周末(周六和周日)@skyking
  • 我认为您可以检查总和是否与周末重叠(多少次),然后添加 n-of-times X 48
  • 使用java.time.* 中的新 java 8 时间和日期 API,这种事情要容易得多。
  • @MBH 你可以使用 java.time 框架。在 ThreeTen-Backport 项目中向后移植到 Java 6 和 7。在ThreeTenABP 项目中适用于Android。

标签: java android


【解决方案1】:

以不大于 24 小时的步长添加小时数。如果您在星期六或星期日结束,请在每个步骤后检查。在每种情况下,再增加 24 小时。那应该做你想做的。

public Date getTaskEndTime(Calendar startDate, int hours){
    while (hours > 0){
        int step = 0;
        if(hours > 24) step = 24;
        else step = hours;          
        hours -= step;          
        startDate.add(Calendar.HOUR_OF_DAY, step);          
        int dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
        if(dayOfWeek == Calendar.SATURDAY) hours += 24;
        if(dayOfWeek == Calendar.SUNDAY) hours += 24;
    }
    return startDate.getTime();
}

【讨论】:

  • 就像你让我开心的魅力一样工作,我尊重 jodatime 是一个很好的图书馆,但当我需要到处使用它时很有用。只为这个功能包含 JodaTime 库是没有意义的!既不需要 JodaTime 也不需要 DateTime 和 Java 8 .. 干得好,谢谢
【解决方案2】:

我强烈建议为此使用JodaTime(或DateTimeof Java8),因为旧的日期/日历 API 非常无用。

public DateTime getEndtime(final DateTime startdate, final int hours) {
    final DateTime endOfWeek = endOfWeek(startdate);

    final Duration restOfWeek = new Duration(startdate, endOfWeek);

    final Duration hoursDuration = toDuration(hours);
    if (restOfWeek.isLongerThan(hoursDuration)) {
        return startdate.plus(hoursDuration);
    } else {
        final Duration durationForNextWeek = hoursDuration.minus(restOfWeek);
        return startOfWeek(startdate).plus(durationForNextWeek);
    }
}

//Converts number of hours as int to Duration
private Duration toDuration(final int hours) {
    return new Duration(hours * 60 * 60 * 1000);
}

//Returns coming friday, 1 millisecond to midnight
private DateTime endOfWeek(final DateTime dateTime) {
    DateTime copy = dateTime;
    while (copy.getDayOfWeek() != 6) {
        copy = copy.plusDays(1);
    }

    return copy.toDateMidnight().toDateTime().minusMillis(1);
}

//Returns the following monday at midnight
//If dateTime is on a monday, the next monday will be chosen
private DateTime startOfWeek(final DateTime dateTime) {
    DateTime copy = dateTime.plusDays(1);
    while (copy.getDayOfWeek() != 1) {
        copy = copy.plusDays(1);
    }

    return copy.toDateMidnight().toDateTime();
}

代码说明:

  • 检查是否可以在不跨入周末的情况下添加小时数
  • 如果没有,只需将小时数添加到开始日期
  • 如果是,找到要转移到下一周的持续时间,并将其添加到一周的开始

此代码不支持持续数周的任务,但这是一个开始,您可以对其进行修改以支持这一点。可能有些边缘情况没有得到很好的处理,我会留给您彻底测试它.

【讨论】:

  • Joda-Time 中的midnight 相关类和方法已被弃用,强烈建议不要使用它们。请改用withTimeAtStartOfDay 方法。
【解决方案3】:

避免使用旧的日期时间类

您正在使用旧的日期时间类,这些类已被证明设计不佳、令人困惑且麻烦。避开他们。

java.time

使用 Java 8 及更高版本中内置的 java.time 框架。见Tutorial。对于 Java 6 和 7,请使用 ThreeTen-Backport 项目。对于 Android,该反向端口的适配,ThreeTenABP

这里有一些示例代码可以帮助您进行操作。我只是即兴创作的,所以它可能不够健壮。此代码似乎确实适用于您的一个示例用法:

(星期五 18:00)+ 48 =(星期二 18:00)(星期六和星期日被忽略)

我稍微概括了这段代码。而不是几个小时,它需要Duration 来表示任何时间跨度(内部表示为总秒数加上以纳秒为单位的秒的分数)。您会注意到 toString 方法的输出使用标准 ISO 8601 表示法,例如 PT48H 用于您的示例 48 小时。

假设您想要时间线上的真实时刻,我们需要使用时区来解释夏令时 (DST) 等异常情况。为此,我们需要从 ZonedDateTime 开始,它将 UTC 时间线上的时刻 (Instant) 与时区 (ZoneId) 结合起来。

我们还传递了一个持续时间(例如 48 小时)和一组星期几值(例如周六和周日)。跳过此方法以在下面讨论这些类型。

这种方法的策略是利用我们的持续时间(例如 48 小时),一次一天地减少它,直到第二天开始为止。如果第二天恰好是被禁止的星期几,我们滑到之后的第二天,并继续滑动直到我们到达允许的(非禁止的)星期几。我们继续蚕食剩余的时间,直到它达到零。阅读代码中的 cmets 进行更多讨论。

在计算第二天的开始时间时,不要假设一天中的时间是00:00:00.0。由于夏令时 (DST) 以及某些时区的其他异常情况,这一天可能会在另一个时间开始。我们调用atStartOfDay 让 java.time 确定一天中的第一个时刻。

public ZonedDateTime addDurationSkippingDaysOfWeek ( ZonedDateTime zdt , Duration duration , EnumSet<DayOfWeek> daysOfWeek ) {
    // Purpose: Start at zdt, add duration but skip over entire dates where day-of-week is contained in EnumSet of prohibited days-of-week. For example, skip over United States weekend of Saturday-Sunday.
    // Verify inputs.
    if ( ( null == zdt ) || ( null == zdt ) || ( null == zdt ) ) {
        throw new IllegalArgumentException ( "Passed null argument. Message # bf186439-c4b2-423a-b5c9-76edebd87cf0." );
    }
    if ( daysOfWeek.size () == DayOfWeek.values ().length ) { // We must receive 6 or less days. If passed all 7 days of the week, no days left to use for calculation.
        throw new IllegalArgumentException ( "The EnumSet argument specified all days of the week. Count: " + daysOfWeek.size () + ". So, impossible to calculate if we skip over all days. Message # 103a3088-5600-4d4e-a1e0-54410afa14f8." );
    }

    // Move through time, day-to-day, allocating remaining duration.
    ZoneId zoneId = zdt.getZone (); // Passed as argument in code below.
    ZonedDateTime moment = zdt; // This var is reassigned in loop below to fresh value, later and later, as we allocate the Duration to determine our target date-time.
    Duration toAllocate = duration; // Loop below chips away at this Duration until none left.
    while (  ! toAllocate.isZero () ) { // Loop while some duration remains to be allocated.
        if ( toAllocate.isNegative () ) {  // Bad - Going negative should be impossible. Means our logic is flawed.
            throw new RuntimeException ( "The duration to allocate ran into a negative amount. Should not be possible. Message # 15a4267d-c16a-417e-a815-3c8f87af0232." );
        }
        ZonedDateTime nextDayStart = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId );
        Duration untilTomorrow = Duration.between ( moment , nextDayStart );
        //  ZonedDateTime oldMoment = moment;  // Debugging.
        Duration allocation = null;
        if ( untilTomorrow.compareTo ( toAllocate ) >= 0 ) { // If getting to tomorrow exceeds our remaining duration to allocate, we are done.
            // Done -- we can allocate the last of the duration. Remaining to allocate is logically zero after this step.
            moment = moment.plus ( toAllocate );
            allocation = toAllocate; // Allocating all of our remaining duration.
            // Do not exit here; do not call "return". Code below checks to see if the day-of-week of this date is prohibited.
        } else { // Else more duration to allocate, so increment to next day.
            moment = nextDayStart;
            allocation = untilTomorrow; // Allocating the amount of time to take us to the start of tomorrow.
        }
        toAllocate = toAllocate.minus ( allocation ); // Subtract the amount of time allocated to get a fresh amount remaining to be
        // Check to see if the moment has a date which happens to be a prohibited day-of-week.
        while ( daysOfWeek.contains ( moment.getDayOfWeek () ) ) { // If 'moment' is a date which is a day-of-week on oun prohibited list, move on to the next date.
            moment = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId ); // Move to start of the next day after. Continue loop to check this next day.
        }

    }
    return moment;
}

要调用该代码,我们将使用您的示例数据值。作为获取原始数据输入的一种方式,我们将字符串解析为LocalDateTime

String input = "2016-01-01T18:00:00";
LocalDateTime ldt = LocalDateTime.parse ( input );

LocalDateTime 对象确实代表时间轴上的实际时刻。因此,我们应用时区来获取实际时刻,ZonedDateTime

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone ( zoneId );
Duration duration = Duration.ofHours ( 48 );

我还从“the weekend”推广到任何一组星期几值,而不是硬编码周六和周日。 java.time 类包括一个方便的enumDayOfWeek——比在我们的代码中使用字符串或数字要好得多。

Java 中的枚举功能比大多数语言中的简单数字掩码为常量更加灵活和有用。它的一个特点是一个特殊的Set 实现EnumSet,当你想要收集由你的枚举定义的可能项目的子集时。对于我们这里,我们想收集一对物品,周六物品和周日物品。

EnumSet<DayOfWeek> daysOfWeek = EnumSet.of ( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );

也许工作日是一周四天,例如周一至周二加上周四至周五,然后使用EnumSet.of ( DayOfWeek.WEDNESDAY , DayOfWeek.SATURDAY , DayOfWeek.SUNDAY )

现在我们准备调用我们的计算方法。传递三个参数:

  • 首发ZonedDateTime
  • Duration 添加到开始日期时间
  • EnumSetDayOfWeek 项。

我们取回我们计算出来的未来日期时间。

ZonedDateTime zdtLater = this.addDurationSkippingDaysOfWeek ( zdt , duration , daysOfWeek );

转储到控制台。

System.out.println ( "zdt: " + zdt + " plus duration: " + duration + " while skipping daysOfWeek: " + daysOfWeek + " is zdtLater: " + zdtLater );

zdt: 2016-01-01T18:00-05:00[America/Montreal] 加上持续时间: PT48H 同时跳过 daysOfWeek: [SATURDAY, SUNDAY] is zdtLater: 2016-01-05T18:00-05:00[America /蒙特利尔]

【讨论】:

    【解决方案4】:

    下面的代码确实解决了这个目的。

    public static Date addBusinessHours(Calendar startDate, int hours, int workingHourStart, int workingHourEnd){
        System.out.println("Entering: Date Time " + startDate.getTime() + " | Remaining Hours: "+ hours + " | Working hours ("+workingHourStart+"-"+workingHourEnd+")");
    
        if(hours == 0){
            return startDate.getTime();
        }
        int hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
        if(startDate.get(Calendar.MINUTE) > 0){
            hourOfDay = hourOfDay +1;
        }
    
        int dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
    
        if(dayOfWeek == Calendar.SATURDAY){
            startDate.add(Calendar.DATE, 2);
            startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
            startDate.set(Calendar.MINUTE, 0);
            startDate.set(Calendar.SECOND, 0);
            addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
        }
        if(dayOfWeek == Calendar.SUNDAY){
            startDate.add(Calendar.DATE, 1);
            startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
            startDate.set(Calendar.MINUTE, 0);
            startDate.set(Calendar.SECOND, 0);
            addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
        }
    
        if(dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY){
            if(hourOfDay < workingHourStart){
                startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
                startDate.set(Calendar.MINUTE, 0);
                startDate.set(Calendar.SECOND, 0);
                hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
                dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
                addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
            }
            else if(hourOfDay >= workingHourEnd){
    
                startDate.add(Calendar.DATE, 1);
                startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
                startDate.set(Calendar.MINUTE, 0);
                startDate.set(Calendar.SECOND, 0);
                hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
                dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
                addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
            }
    
            else if(hourOfDay >= workingHourStart && hourOfDay < workingHourEnd){
                if(hours+hourOfDay <= workingHourEnd){
                    startDate.add(Calendar.HOUR_OF_DAY, hours);
                    return startDate.getTime();
                }else{
                    //System.out.println("¤¤" + startDate.getTime() );
                    startDate.add(Calendar.DATE, 1);
                    //System.out.println("¤¤" + startDate.getTime() );
                    startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
                    startDate.set(Calendar.MINUTE, 0);
                    startDate.set(Calendar.SECOND, 0);
                    //System.out.println("¤¤" + startDate.getTime() );
    
                    System.out.println("##"+hours+ "##"+ workingHourEnd + "##" + hourOfDay);
                    int remaining_hours = hours - (workingHourEnd - hourOfDay);
                    addBusinessHours(startDate, remaining_hours, workingHourStart, workingHourEnd);
                }
            }
        }
    
        return startDate.getTime();
    }
    

    【讨论】:

      【解决方案5】:

      基本上,您需要做的是首先计算一周还剩多少小时(这是当前时间与 24:00@Fri 之间的差异)。如果小时数少于您刚刚添加的小时数。

      否则,您从小时数中减去该数量,然后如果余数超过 120 小时(一周),您将取整数商并跳过该数周。最后,您将剩余部分添加到该周的 00:00@Mon。

      在您的示例中,您在开始和 24:00@Fri 6h 之间,小于 48h,因此您从中减去 6h 并得到 42h。现在 42 小时小于 120 小时,因此您不会跳过几周,然后将 42 小时添加到 00:00@Mon,然后您将在 18:00@Tue 到达。

      【讨论】:

        【解决方案6】:

        请参考下面的代码,看看是否对你有帮助。

        public static Date getTaskEndTime(Date startDate, int hours) {
            // calculate the end time by adding the hours ignoring the weekends
            Calendar endCal = Calendar.getInstance();
            endCal.setTime(startDate);
        
            for (int i = 0; i < hours; hours = hours - 8) {
                if (!(endCal.get(Calendar.DAY_OF_WEEK) == 1 || endCal.get(Calendar.DAY_OF_WEEK) == 7)) {
                    endCal.add(Calendar.DAY_OF_MONTH, 1);
                } else {
                    endCal.add(Calendar.DAY_OF_MONTH, 1);
                    hours = hours + 8;
                }
            }
            return endCal.getTime();
        }
        

        【讨论】:

        • 我只认为工作时间是 8 小时。这种方法存在一个问题,如果估计的时间不是工作时间的倍数,那么它可能会多出一天作为结束日期。
        【解决方案7】:

        您需要以自定义方式处理它:

          public Date getTaskEndTime(Calendar startDate, int hours) {
                Calendar endTime = Calendar.getInstance();
                endTime.setTime(startDate.getTime());
                endTime.add(Calendar.HOUR, hours); // add 2 for saturday and sunday
                int dayOfWeek = endTime.get(Calendar.DAY_OF_WEEK);
                if (dayOfWeek == Calendar.SATURDAY) {
                    endTime.add(Calendar.DATE, 2); // add 2 for saturday and sunday
                } else if (dayOfWeek == Calendar.SATURDAY) {
                    endTime.add(Calendar.DATE, 1); // add 1 for sunday
                }
                return endTime.getTime();
            }
        

        【讨论】:

        • 开始日期是否在周末无关紧要。如果 startdate 是 friday,hours 是 40,那么你需要自定义逻辑。
        • 你有 2 次 SUNDAY :)
        • 我刚刚给出了语法。您可以检查是开始日期还是当前日期。 OP 需要忽略周末,以便检查他必须将其放入代码中。
        • 没关系,你的代码无助于解决问题。
        • 所以如果 startDate 是 thursday,hours 是 48,那么...?
        猜你喜欢
        • 2012-08-06
        • 1970-01-01
        • 2012-06-10
        • 1970-01-01
        • 1970-01-01
        • 2017-02-05
        • 1970-01-01
        • 1970-01-01
        • 2018-04-28
        相关资源
        最近更新 更多