如何在不使用Joda Time的情况下计算某些工作时间的两个日期之间的小时数?

时间:2017-07-13 14:57:59

标签: java date

我有某种事件,其特点是开始日期和 持续时间。

我需要确定当前时间已经完成的事件以及完成后剩余或通过了多少小时。

我有条件只在上午10点到下午6点(public String getProgramEndDate(Date dateStart, int totalDuration){ long durationInMillis = totalDuration * 3600000; long end = dateStart.getTime() + durationInMillis; Date date=new Date(end); SimpleDateFormat df2 = new SimpleDateFormat("dd/MM/yyyy"); String endD = df2.format(date); return endD; } public StringBuilder getDaysEndOfTheProgram(Long howMuchTime) { SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH"); long diffHours = howMuchTime / (60 * 60 * 1000) % 8; long diffDays = howMuchTime / (24 * 60 * 60 * 1000); StringBuilder sb = new StringBuilder(); sb.append(diffDays + " days, "); sb.append(diffHours + " hours. "); return sb; )计算工作时间。 如果现在是上午9点它应该只计算昨天的小时作为最后工作时间,如果今天是01PM,它应该计算今天3小时已经过去

我创建了两种方法,但计算没有考虑工作时间。如何只计算工作时间?

使用Joda Time的条件 NOT 。有可能吗?

我的两种方法是:

Sub TransData()
    Dim vDB, vR()
    Dim n As Long, i As Long, j As Integer, k As Integer
    vDB = Range("a1").CurrentRegion

    For i = 2 To UBound(vDB, 1)
        For j = 4 To UBound(vDB, 2)
            If vDB(i, j) <> "" Then
                n = n + 1
                ReDim Preserve vR(1 To 4, 1 To n)
                For k = 1 To 3
                    vR(k, n) = vDB(i, k)
                Next k
                vR(4, n) = vDB(i, j)
            End If
        Next j
    Next i
    Sheets.Add
    Range("a1").Resize(1, 4) = Array("Item", "Amount", "Price", "Color")
    Range("a2").Resize(n, 4) = WorksheetFunction.Transpose(vR)
End Sub

2 个答案:

答案 0 :(得分:1)

首先,您正在使用现在遗留下来的麻烦的旧日期类,取而代之的是java.time类。

相当于DateInstant,是UTC时间轴上的一个时刻,但具有更精细的纳秒级分辨率。将此类的对象作为第一个参数传递。如果您获得Date,请使用添加到旧班级Date::toInstant的新方法进行转换。

Instant = myUtilDate.toInstant() ;

为您使用一个持续时间而不仅仅是一个整数。这使您的代码更加自我记录并为您提供类型安全性。

Duration d = Duration.ofHours( … ) ;

由于您需要一天中的特定时段和当前日期,因此我们需要一个时区。我们必须将UTC值调整到该时区。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdtInitial = instant.atZone( z ) ;

您是否理解Date实际上是日期时间?如果您打算在没有时间的情况下传递仅限日期的值,请改为传递LocalDate对象。您的代码表明您确实意味着包含了实际的时间。

首先测试您的开始时间是否在工作时间内。

LocalTime ltStart = LocalTime.of( 10 , 0 ) ;
LocalTime ltStart = LocalTime.of( 18 , 0 ) ;

LocalTime ltInitial = zdtInitial.toLocalTime() ;
if( ( ! ltInitial.isBefore( ltStart ) ) && ltInitial.isBefore( ltStop ) ) { … }

接下来,测试您的开始时间是否尚未发生。

ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
if( zdtNow.isBefore( zdtInitial ) ) { … }

对于工作时间问题没有神奇的答案。刚刚开始计算,在Duration处消失。

Duration dRemaining = d ; 

ZonedDateTime zdtInitialEndOfDay = ZonedDateTime.of( zdtInitial.toLocalDate() , ltStop ) ; 
Duration dBetween = Duration.between( zdt.Initial , zdtInitialEndOfDay ) ; 

测试中间金额是否等于或超过剩余金额。

if( dBetween.compareTo( dRemaining ) >= 0 ) {
    // Add remaining duration to the zdt to get the ending moment. End looping. 
} else {  // between is less than remaining. So subtract, and move on to next day.
    dRemaining = dRemaining.minus( dBetween ); 
    // get `LocalDate as seen above, call `plusDays` and create another `ZonedDateTime` as seen above.
}

答案 1 :(得分:0)

您可以使用new Date and Time API

如果您正在使用 Java 8 ,则它本身就在java.time包中。

如果您正在使用 Java&lt; = 7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ThreeTenABP(更多关于如何使用它here)。

以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time而在ThreeTen Backport(或Android的ThreeTenABP中)为org.threeten.bp),但类和方法名称是一样的。

我假设整整一天算作8小时,所以这样的情况就是这样:

  • 从7月10日 th 开始,下午3点
  • 于7月12日 th 结束,下午1点

将导致14小时(第一天3小时,7月11日8小时 th 和最后一天3小时)。算法如下:

// get system's default timezone
ZoneId zone = ZoneId.systemDefault();

// or even better, get a specific timezone (more details on that below)
ZoneId zone = ZoneId.of("Europe/Kiev");


// start and end working hours
LocalTime workStart = LocalTime.of(10, 0);
LocalTime workEnd = LocalTime.of(18, 0);

// start and end dates
ZonedDateTime start = ZonedDateTime.of(2017, 7, 10, 15, 0, 0, 0, zone);
ZonedDateTime end = ZonedDateTime.of(2017, 7, 12, 13, 0, 0, 0, zone);

long totalHours = 0;
ZonedDateTime startHour = start;
// if start is before 10AM or 6PM, adjust it
if (start.toLocalTime().isBefore(workStart)) { // before 10 AM
    startHour = start.with(workStart); // set time to 10 AM
} else if (start.toLocalTime().isAfter(workEnd)) { // after 6 PM
    startHour = start.with(workEnd); // set time to 6 PM
}
ZonedDateTime endHour = end;
// if end is before 10AM or 6PM, adjust it
if (end.toLocalTime().isAfter(workEnd)) { // after 6 PM
    endHour = end.with(workEnd); // set time to 6 PM
} else if (end.toLocalTime().isBefore(workStart)) { // before 10 AM
    endHour = end.with(workStart); // set time to 10 AM
}

while(startHour.isBefore(endHour)) {
    if (startHour.toLocalDate().equals(endHour.toLocalDate())) { // same day
        totalHours += ChronoUnit.HOURS.between(startHour, endHour);
        break;
    } else {
        ZonedDateTime endOfDay = startHour.with(workEnd); // 6PM of the day
        totalHours += ChronoUnit.HOURS.between(startHour, endOfDay);
        startHour = startHour.plusDays(1).with(workStart); // go to next day
    }
}

System.out.println(totalHours); // 14

totalHours的结果为14。如果您想在几天和几小时内打破此值,您可以执行以下操作(假设每天有8个工作小时):

long days = totalHours / 8;
long hours = totalHours % 8;

System.out.println(days + " days, " + hours + " hours");

输出将是:

  

1天,6小时

甚至更好(获得正确的单数/复数):

System.out.println(days + " day" + (days > 1 ? "s" : "") + ", " + hours + " hour" + (hours > 1 ? "s" : ""));
  

1天,6小时

备注

  • 我使用ZonedDateTime(因为@BasilBourque在评论中提醒我),因为它可以处理DST更改和其他时区问题。
  • 我已使用ZoneId.systemDefault()获取系统的默认时区,但最好使用特定区域(因为系统的默认值可以更改为运行时,导致意外结果),使用ZoneId.of(zoneName)zoneName可以是IANA timezones names中的任何一个(始终采用Region/City格式,例如Europe/KievEurope/Berlin)。 避免使用3个字母的缩写(例如CSTPST),因为它们是ambiguous and not standard
    您可以致电ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。
  • 如果您需要将java.util.Date转换为新API,那就很简单:

    // Java 8
    ZonedDateTime dt = date.toInstant().atZone(zone);
    
    // Java 7 ThreeTen Backport
    ZonedDateTime dt = DateTimeUtils.toInstant(date).atZone(zone);