考虑不添加周末,如何将营业时间添加到日期? – Java

我想增加一些特定的小时数,忽略周末

例如,

(周五18:00)+ 48 =(Tuseday 18:00)(周六和周日被忽略)

由于公司24小时工作,营业时间是24小时。但我仍然无法在工作日如何增加工作时间

function可以是这样的:

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

添加小时数不超过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 DateTime ),因为旧的Date / Calendar 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(); } 

代码说明:

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

这段代码不支持延长数周的任务,但是这是一个可以修改以支持这一点的开始。可能是一些不能处理的边缘情况,我会留给你彻底测试它。

避免旧的日期时间类

您正在使用旧的日期时间类,这些类已被certificate设计糟糕,令人困惑且麻烦。 避免他们。

java.time

使用Java 8及更高版本中内置的java.time框架。 请参阅教程 。 对于Java 6和7,请使用ThreeTen-Backport项目。 对于Android,改编后端口, ThreeTenABP 。

这里有一些示例代码可以帮助您。 我只是即兴鞭打它,所以它可能不健壮。 此代码似乎适用于您的一个示例用法:

(周五18:00)+ 48 =(Tuseday 18:00)(周六和周日被忽略)

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

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

我们还会传递48小时的持续时间,以及周六和周日等一组星期值。 跳过此方法,以便在下面讨论这些类型。

这种方法的策略是花费我们的持续时间,例如48小时,并且每次一天将其消耗掉一定数量,以达到第二天的开始。 如果第二天恰好是禁止的星期几,我们会在此之后滑到第二天,并继续滑动,直到我们达到允许(不禁止)的星期几日期。 我们继续蚕食剩余的时间,直到它达到零。 阅读代码中的注释以获得更多讨论。

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

 public ZonedDateTime addDurationSkippingDaysOfWeek ( ZonedDateTime zdt , Duration duration , EnumSet 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 ); 

我还将“ 周末 ”概括为任何一组星期值,而不是周六和周日的硬编码。 java.time类包含一个方便的枚举 , DayOfWeek – 比在我们的代码中使用字符串或数字要好得多。

Java中的枚举工具比大多数语言中的简单数字掩码一样灵活且更有用。 它的function之一是一个特殊的Set实现EnumSet用于收集枚举定义的可能项的子集。 对于我们这里,我们想要收集一对项目,星期六项目和星期日项目。

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

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

现在我们准备调用我们的计算方法。 通过三个论点:

  • 起始ZonedDateTime
  • 要添加到开始日期时间的Duration时间
  • DayOfWeek项目的EnumSet

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

 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 [美国/蒙特利尔]加上持续时间:PT48H同时跳过daysOfWeek:[SATURDAY,SUNDAY]是zdtLater:2016-01-05T18:00-05:00 [美国/蒙特利尔]

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

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

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

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

在你的例子中,你的开始时间是在你的开始时间和24:00 @ Fri 6h之间,那时间小于48h所以你从中减去6h并得到42h。 现在42小时不到120小时,所以你不要跳过几个星期,然后你加上42小时到星期一00:00 @周一晚上18点到达。

请参考以下代码,看看是否对您有所帮助。

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

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

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