如何计算时间戳范围之间的小时和分钟总数(在特定范围内)

时间:2018-07-19 20:14:47

标签: java date datetime java-8

用户可以按初始日期和结束日期/时间(时间戳)过滤报告。假设当前的过滤器:

首字母:2018-01-01 13:00:00

结尾:2018-01-05 04:00:00

如何在Java中计算过滤期间内22:0005:00 AM(第二天)之间的小时和分钟总数

我们当前正在使用Java 8类(LocalDateTime等)。

上述过滤器的预期结果:27小时0分钟(而不是87小时)

详细信息:

From day 01 to day 02 we overlap the interested hour range (22h - 5h) so
for day 01 to day 02 we add 7 hours to the total amount.
From day 02 to day 03 we add another 7 hours.
From day 03 to day 04 we add another 7 hours.
From day 04 to day 05 we add 6 hours because the end filter finishes at 04:00 AM so we should not consider the last hour.

如果结束时间戳为2018-01-05 04:30:00,则最终结果将为27小时30分钟。

此外,解决方案必须考虑DST更改。我们在操作中可以使用客户端时区,因此解决方案可能是使用OffsetDateTime类。但是我不知道在这种情况下如何正确处理DST。

5 个答案:

答案 0 :(得分:3)

误以为这个问题是重复的,@ Luiz纠正了我。向我道歉。

要针对DST计算Duration,请遵循以下answer。该代码还涵盖了leap年。

编辑:
@XiCoN JFS指出了一些错误。我查看并测试了我的代码。我发布了测试课程 here。我试图涵盖所有我能想到的情况。

这是我的解决方案:

public class Durations {

  public static Duration getSumOfHoursOnDays(ZonedDateTime dateTimeFrom, ZonedDateTime dateTimeTo, LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    Duration result = Duration.of(0, ChronoUnit.HOURS);

    Duration hoursOnFirstDay = getHoursOnFirstDay(dateTimeFrom, dateTimeTo, dailyTimeFrom, dailyTimeTo);
    result = result.plus(hoursOnFirstDay);

    long daysBetween = ChronoUnit.DAYS.between(dateTimeFrom.truncatedTo(ChronoUnit.DAYS), dateTimeTo.truncatedTo(ChronoUnit.DAYS));
    if (daysBetween > 0) {
      for (int i = 1; i < daysBetween; i++) {
        ZonedDateTime day = dateTimeFrom.plusDays(i);
        Duration hoursOnDay = getHoursOnDay(day, dailyTimeFrom, dailyTimeTo);
        result = result.plus(hoursOnDay);
      }

      Duration hoursOnLastDay = getHoursOnLastDay(dateTimeFrom, dateTimeTo, dailyTimeFrom, dailyTimeTo);
      result = result.plus(hoursOnLastDay);
    }

    return result;
  }

  protected static Duration getHoursOnFirstDay(ZonedDateTime dateTimeFrom, ZonedDateTime dateTimeTo, LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    ZonedDateTime dateTimeToOnFirstDay = dateTimeTo.truncatedTo(ChronoUnit.DAYS).isAfter(dateTimeFrom.truncatedTo(ChronoUnit.DAYS)) ?
      dateTimeFrom.plusDays(1).withHour(0) :
      dateTimeTo;

    return getHoursOnDay(dateTimeFrom, dateTimeToOnFirstDay, dailyTimeFrom, dailyTimeTo);
  }

  protected static Duration getHoursOnLastDay(ZonedDateTime dateTimeFrom, ZonedDateTime dateTimeTo, LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    return dateTimeTo.truncatedTo(ChronoUnit.DAYS).isAfter(dateTimeFrom.truncatedTo(ChronoUnit.DAYS)) ?
      getHoursOnDay(dateTimeTo.withHour(0), dateTimeTo, dailyTimeFrom, dailyTimeTo) :
      Duration.ofHours(0);
  }

  protected static Duration getHoursOnDay(ZonedDateTime day, LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    ZonedDateTime zoneTimeFrom = day.with(dailyTimeFrom);
    ZonedDateTime zoneTimeTo = day.with(dailyTimeTo);
    return zoneTimeFrom.isBefore(zoneTimeTo) ?
      Duration.between(zoneTimeFrom, zoneTimeTo) :
      Duration.between(day.withHour(0), zoneTimeTo).plus(Duration.between(zoneTimeFrom, day.plusDays(1).withHour(0)));
  }

  protected static Duration getHoursOnDay(ZonedDateTime dateTimeFrom, ZonedDateTime dateTimeTo, LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    ZonedDateTime dailyDateTimeFrom = dateTimeFrom.with(dailyTimeFrom);
    ZonedDateTime dailyDateTimeTo = dateTimeFrom.with(dailyTimeTo);

    if (dailyDateTimeFrom.isBefore(dailyDateTimeTo)) {
      if (dailyDateTimeFrom.isAfter(dateTimeTo) || dailyDateTimeTo.isBefore(dateTimeFrom)) {
        return Duration.ofHours(0);
      }

      ZonedDateTime from = dateTimeFrom.isAfter(dailyDateTimeFrom) ?
        dateTimeFrom :
        dailyDateTimeFrom;

      ZonedDateTime to = dateTimeTo.isBefore(dailyDateTimeTo) ?
        dateTimeTo :
        dailyDateTimeTo;

      return Duration.between(from, to);
    }

    Duration result = Duration.ofHours(0);

    ZonedDateTime to = dateTimeTo.isBefore(dailyDateTimeTo) ?
      dateTimeTo :
      dailyDateTimeTo;
    if (dateTimeFrom.isBefore(dailyDateTimeTo)) {
      result = result.plus(Duration.between(dateTimeFrom, to));
    }
    ZonedDateTime from = dateTimeFrom.isAfter(dailyDateTimeFrom) ?
      dateTimeFrom :
      dailyDateTimeFrom;
    if (from.isBefore(dateTimeTo)) {
      result = result.plus(Duration.between(from, dateTimeTo));
    }
    return result;
  }
}

主要挑战是应对开始时间开始时间之后的白天。问题中给出了一个示例:从22到5。
在这种情况下,一天中可能有两个时间范围:22-24/0-5。这两个时间范围必须单独评估,因为其中可能会有DST更改。
但是也可以有一个时间范围,例如:22到0。对于一天,结果是22到24。

我在链接测试课程中介绍了这些案例以及许多其他案例。我花了一些时间才能将其用于所有测试用例。所以我希望它能为某人服务。

答案 1 :(得分:2)

要了解此解决方案,您应该熟悉位字段和位掩码。如果没有,请考虑进行研究。

这是一个简单的例子:

Bit field: 1 0 1 1 1 0  AND
Bit mask : 1 1 0 1 0 1
______________________
Result   : 1 0 0 1 0 0

通过这种方法,您可以将一天中的实际小时显示为位字段,将所需的小时显示为位掩码。

我们从下午22点到凌晨5点的24位掩码(24小时)将如下所示(最后MSB):

23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
 1  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1  1  1  1  1

以十六进制表示0xC0001F

编辑:

在计算某些范围内的日历天数与小时数时,我的最后一种方法存在问题。原来,我不得不将问题简化为矩阵以识别模式。

CD\HR <24 =24 >24 <48 =48 >48 <72 =72 >72 <96 =96 >96 
   0   x
   1   x   x   x   x
   2           x   x   x   x   x
   3                       x   x   x   x   x
   4                                   x   x   x   x

 CD = Calendar Days => 2017-01-01 23:00 until 2017-01-02 00:00 is one CD
 HR  = Hour Range

目标是为bitFieldstartDate创建一个endDate。对于隔日,我们只需将24个bitMask中的位数相加即可。 bitField始终是从startTimeendTime的范围。如果startTime> endTime,则我们将它们和计算出的bitField翻转。

三个条件决定在应用bitMask时是否需要忽略日历日:

calendarDays > 0

bitField != 0

startTime > endTime

这将计算24小时掩码范围内的小时数,并考虑可能的DST:

private double getHoursInRange(ZonedDateTime startDate, ZonedDateTime endDate, int bitMask) {
    if (!startDate.isBefore(endDate)) throw new InputMismatchException("endDate is before or equal startDate");
    ZoneRules rules = startDate.getZone().getRules();
    int offset = 0;
    ZoneOffsetTransition zoneOffsetTransition = rules.nextTransition(startDate.toInstant());
    while (true) {
        Instant transitionInstant = zoneOffsetTransition.getInstant();
        if (transitionInstant.isBefore(endDate.toInstant()) || transitionInstant.equals(endDate.toInstant())) {
            ZoneOffset offsetAfter = zoneOffsetTransition.getOffsetAfter();
            offset += offsetAfter.getTotalSeconds() == 3600 ? -1 : 1;
            zoneOffsetTransition = rules.nextTransition(transitionInstant);
        } else {
            break;
        }
    }
    long calendarDays = Duration.between(startDate.withHour(0), endDate.withHour(23)).toDays();
    int startTime = startDate.getHour();
    int endTime = endDate.getHour();
    int bitField = 0;
    for (int o = startTime < endTime ? startTime : endTime; startTime < endTime ? o < endTime : o < startTime; o++) {
        bitField = bitField | (1 << o);
    }
    if (startTime > endTime) {
        bitField = ~bitField;
    }
    if (calendarDays > 0 && bitField != 0 && startTime > endTime) {
        calendarDays = calendarDays - 1;
    }
    double hoursInRange = calendarDays * Integer.bitCount(bitMask);
    hoursInRange += Integer.bitCount(bitField & bitMask);
    hoursInRange += offset;
    return hoursInRange;
}

如果您还想检查几分钟,则可以添加以下行:

hoursInRange += (endDate.getMinute() - startDate.getMinute()) / 60.0;

或秒:

hoursInRange += (endDate.getSecond() - startDate.getSecond()) / 3600.0;

通过快速的单元测试进行了测试:

@Test
public void test() {
    ZoneId london = ZoneId.of("Europe/London");
    LocalDateTime startDate = LocalDateTime.of(2018, 1, 1, 13, 0, 0, 0);
    LocalDateTime endDate = LocalDateTime.of(2018, 1, 5, 4, 0, 0, 0);
    ZonedDateTime from = startDate.atZone(london);
    ZonedDateTime to = endDate.atZone(london);
    double hours = getHoursInRange(from, to, 0xC0001F);
    double allHours = getHoursInRange(from, to, 0xFFFFFF);
    assertEquals(27, hours, 0);
    assertEquals(87, allHours, 0);
}

答案 2 :(得分:2)

我已经给了answer。但是已经给出的答案相当复杂。我认为它们并不容易理解。因此,我对这些答案不满意,并想知道是否可以找到一个简单易懂的解决方案。

我想,我找到了一个。方法是定义日期时间范围(如OP所述)并流式传输其单位并过滤适当的单位。

这是我的DateTimeRange

public class DateTimeRange {

    private final ZonedDateTime from;
    private final ZonedDateTime to;

    DateTimeRange(ZonedDateTime from, ZonedDateTime to) {
        this.from = from;
        this.to = to;
    }

    public static DateTimeRange of(LocalDateTime from, LocalDateTime to, ZoneId zoneId) {
        Objects.requireNonNull(from);
        Objects.requireNonNull(to);
        Objects.requireNonNull(zoneId);

        return new DateTimeRange(ZonedDateTime.of(from, zoneId), ZonedDateTime.of(to, zoneId));
    }
    public Stream<ZonedDateTime> streamOn(ChronoUnit unit) {
        Objects.requireNonNull(unit);

        ZonedDateTimeSpliterator zonedDateTimeSpliterator = new ZonedDateTimeSpliterator(from, to, unit);
        return StreamSupport.stream(zonedDateTimeSpliterator, false);
    }

    static class ZonedDateTimeSpliterator implements Spliterator<ZonedDateTime> {

        private final ChronoUnit unit;

        private ZonedDateTime current;
        private ZonedDateTime to;

        ZonedDateTimeSpliterator(ZonedDateTime from, ZonedDateTime to, ChronoUnit unit) {
            this.current = from.truncatedTo(unit);
            this.to = to.truncatedTo(unit);
            this.unit = unit;
        }

        @Override
        public boolean tryAdvance(Consumer<? super ZonedDateTime> action) {
            boolean canAdvance = current.isBefore(to);

            if (canAdvance) {
                action.accept(current);
                current = current.plus(1, unit);
            }

            return canAdvance;
        }

        @Override
        public Spliterator<ZonedDateTime> trySplit() {
            long halfSize = estimateSize() / 2;
            if (halfSize == 0) {
                return null;
            }

            ZonedDateTime splittedFrom = current.plus(halfSize, unit);
            ZonedDateTime splittedTo = to;
            to = splittedFrom;

            return new ZonedDateTimeSpliterator(splittedFrom, splittedTo, unit);
        }

        @Override
        public long estimateSize() {
            return unit.between(current, to);
        }

        @Override
        public Comparator<? super ZonedDateTime> getComparator() {
            // sorted in natural order
            return null;
        }

        @Override
        public int characteristics() {
            return Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED | Spliterator.SORTED | Spliterator.DISTINCT;
        }

    }

}

这是经过改编的Durations类:

public class Durations {

  public static Duration getSumOfHoursOnDays(ZoneId zoneId, LocalDateTime dateTimeFrom, LocalDateTime dateTimeTo, LocalTime dailyTimeFrom,
    LocalTime dailyTimeTo) {
    return getDuration(zoneId, dateTimeFrom, dateTimeTo, dailyTimeFrom, dailyTimeTo, ChronoUnit.HOURS);
  }

  public static Duration getDuration(ZoneId zoneId, LocalDateTime dateTimeFrom, LocalDateTime dateTimeTo, LocalTime dailyTimeFrom,
    LocalTime dailyTimeTo, ChronoUnit precision) {
    long count = DateTimeRange.of(dateTimeFrom, dateTimeTo, zoneId)
      .streamOn(precision)
      .filter(getFilter(dailyTimeFrom, dailyTimeTo))
      .count();
    return Duration.of(count, precision);
  }

  protected static Predicate<? super ZonedDateTime> getFilter(LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    return dailyTimeFrom.isBefore(dailyTimeTo) ?
      filterFromTo(dailyTimeFrom, dailyTimeTo) :
      filterToFrom(dailyTimeFrom, dailyTimeTo);
  }

  protected static Predicate<? super ZonedDateTime> filterFromTo(LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    return zdt -> {
      LocalTime time = zdt.toLocalTime();
      return (time.equals(dailyTimeFrom) || time.isAfter(dailyTimeFrom)) && time.isBefore(dailyTimeTo);
    };
  }

  protected static Predicate<? super ZonedDateTime> filterToFrom(LocalTime dailyTimeFrom, LocalTime dailyTimeTo) {
    return zdt -> {
      LocalTime time = zdt.toLocalTime();
      return (time.equals(dailyTimeFrom) || time.isAfter(dailyTimeFrom)) || (time.isBefore(dailyTimeTo));
    };
  }

}

由于Stream-接口是众所周知的,因此这种方法应该更容易理解。此外,将其与其他ChronoUnit一起使用很简单。应用Stream接口可以轻松计算其他基于日期时间的值。此外,这遵循Java 9 LocalDate.datesUntil的示例。

虽然更容易理解此解决方案,但它不会像前面提到的两者一样快。我认为,只要多年来没有人能够以纳米精度流动,那是可以接受的;)


我的资源链接:

答案 3 :(得分:1)

tl; dr

  • 不要太努力。使用在 ThreeTen-Extra 项目中找到的Interval类来表示和比较与时间轴相关的时间跨度。
  • ZonedDateTime中的分区时间和Instant中的UTC时间之间切换。相同的时刻,不同的时钟时间。

ThreeTen-Extra 项目

似乎其他答案正在尝试重新创建已经编写的类。有关为Java 8和更高版本中内置的 java.time 框架添加功能的类,请参见 ThreeTen-Extra 项目。

具体来说,我们可以使用Interval –一对Instant对象,以UTC为日期时间范围。此类提供了比较方法,例如overlapsintersection等。

从两个输入(日期和时间)开始。将它们解析为LocalDateTime,因为您忽略了提及时区。用T代替中间的SPACE,以符合ISO 8601标准格式。

LocalDateTime ldtStart = LocalDateTime.parse( "2018-01-01T13:00:00" );
LocalDateTime ldtStop = LocalDateTime.parse( "2018-01-05T04:00:00" );

LocalDateTime不能表示时刻,因为它缺少任何时区或UTC偏移量的概念。没有那种背景,它就没有真正的意义。您是要从印度加尔各答,法国巴黎或加拿大蒙特利尔的下午1点开始?那是三个非常不同的时刻。

因此,指定一个时区ZoneId,以获得ZonedDateTime

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用3-4个字母的缩写,例如ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = ldtStart.atZone( z );
ZonedDateTime zdtStop = ldtStop.atZone( z );

使用org.threeten.extra.Interval类表示与时间轴相关的时间跨度。此类表示一对Instant对象。根据定义,Instant采用UTC。我们正在按分区的日期时间进行工作。该怎么办?从我们的Instant中提取一个ZonedDateTime,以适应UTC。您可以在概念上将其视为由ZonedDateTimeInstant组成的ZoneIdInstantZonedDateTime代表相同的同时时刻,时间轴上的相同点。只有挂钟时间不同。

Interval interval = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );

您的参考系是在指定时区中感知的我们区间内的日期。因此,从每个LocalDate中提取一个仅用于日期的值ZonedDateTime

LocalDate ldStart = zdtStart.toLocalDate();
LocalDate ldStop = zdtStop.toLocalDate();

我们将按日期循环显示,一次增加一天。因此,将该日期复制到一个递增变量中。

LocalDate localDate = ldStart;

指定您要定位的一天中的时间对。

LocalTime timeStart = LocalTime.of( 22 , 0 );
LocalTime timeStop = LocalTime.of( 5 , 0 );

设置Map来存储我们的结果。我们将每个日期(在区域中看到的日期)映射到Interval,这对Instant对象代表我们从该日期开始的晚上10点到凌晨5点目标区域中的多少输入间隔。

long initialCapacity = ( ChronoUnit.DAYS.between( ldStart , ldtStop ) + 1 );
Map< LocalDate, Interval > dateToIntervalMap = new HashMap<>( ( int ) initialCapacity );

循环显示每个日期,直到输入间隔结束为止。

while ( ! localDate.isAfter( ldStop ) ) {

一个接一个地获取每个日期,并应用我们的目标时间值来确定我们时区中的时刻。

  

我不知道如何正确处理DST

ZonedDateTime类的核心目的是处理在不同时间点在各个区域中发现的诸如DST之类的异常。 DST并不是唯一这样的异常。世界各地的政客都对重新定义时区表现出好奇。

当我们将时区应用于给定的日期和时间时,如果该日期在该时区中的那个时间无效(例如夏令时DST转换),则ZonedDateTime类会根据需要自动调整。请务必阅读文档,以确保您了解并同意其调整逻辑。

    ZonedDateTime zdtTargetStart = localDate.atTime( timeStart ).atZone( z );
    ZonedDateTime zdtTargetStop = localDate.plusDays( 1 ).atTime( timeStop ).atZone( z );

间隔我们的目标时间范围,即目标Interval

    Interval target = Interval.of( zdtTargetStart.toInstant() , zdtTargetStop.toInstant() );

测试以查看目标间隔是否与我们的输入间隔重叠。当然,根据我们对问题的定义,我们希望一开始就是这种情况。但是最后,在最后一个日期,情况可能并非如此。在最后一个日期,我们的输入间隔可能会在下一个目标日期发生之前结束。确实,这正是我们在课题中提供的输入所看到的(请参见下面的输出)。

使用intersection方法产生一个Interval,该 Interval intersection; if ( interval.overlaps( target ) ) { intersection = interval.intersection( target ); } else { ZonedDateTime emptyInterval = localDate.atTime( timeStart ).atZone( z ); // Better than NULL I suppose. intersection = Interval.of( emptyInterval.toInstant() , emptyInterval.toInstant() ); } 表示目标间隔和输入间隔之间的公共时间跨度。

    dateToIntervalMap.put( localDate , intersection );
    // Setup the next loop.
    localDate = localDate.plusDays( 1 );
}

将得到的相交间隔存储在我们的地图中,并分配给我们循环的日期。

// Report
System.out.println( "interval: " + interval + " = " + zdtStart + "/" + zdtStop );
int nthDate = 0;

完成业务逻辑。现在我们可以报告结果了。

java.time.Duration

我们使用Duration totalDuration = Duration.ZERO; 类来跟踪每个路口间隔中包含的经过时间。

Map::keySet

我们必须完成一些额外的工作才能按时间顺序设置报告循环。 List< LocalDate > dates = new ArrayList<>( dateToIntervalMap.keySet() ); Collections.sort( dates ); List< LocalDate > keys = List.copyOf( dates ); for ( LocalDate date : keys ) { nthDate++; Interval i = dateToIntervalMap.get( date ); Instant startInstant = i.getStart(); Instant stopInstant = i.getEnd(); Duration d = Duration.between( startInstant , stopInstant ); totalDuration = totalDuration.plus( d ); ZonedDateTime start = startInstant.atZone( z ); ZonedDateTime stop = stopInstant.atZone( z ); System.out.println( "Day # " + nthDate + " = " + date + " ➙ " + i + " = " + start + "/" + stop + " = " + d ); } 方法不一定按我们想要的顺序返回结果。

String

报告我们所有路口间隔中包含的总时间。我们的Duration对象生成的P使用标准ISO 8601 duration formatT标志着开始,而System.out.println("Total duration: " + totalDuration); 则将任何年月日与任何时分秒分开。

LocalDateTime ldtStart = LocalDateTime.parse( "2018-01-01T13:00:00" );
LocalDateTime ldtStop = LocalDateTime.parse( "2018-01-05T04:00:00" );

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = ldtStart.atZone( z );
ZonedDateTime zdtStop = ldtStop.atZone( z );

Interval interval = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );

LocalDate ldStart = zdtStart.toLocalDate();
LocalDate ldStop = zdtStop.toLocalDate();
LocalDate localDate = ldStart;

LocalTime timeStart = LocalTime.of( 22 , 0 );
LocalTime timeStop = LocalTime.of( 5 , 0 );

long initialCapacity = ( ChronoUnit.DAYS.between( ldStart , ldtStop ) + 1 );
Map< LocalDate, Interval > dateToIntervalMap = new HashMap<>( ( int ) initialCapacity );

while ( ! localDate.isAfter( ldStop ) ) {
    ZonedDateTime zdtTargetStart = localDate.atTime( timeStart ).atZone( z );
    ZonedDateTime zdtTargetStop = localDate.plusDays( 1 ).atTime( timeStop ).atZone( z );
    Interval target = Interval.of( zdtTargetStart.toInstant() , zdtTargetStop.toInstant() );
    Interval intersection;
    if ( interval.overlaps( target ) ) {
        intersection = interval.intersection( target );
    } else {
        ZonedDateTime emptyInterval = localDate.atTime( timeStart ).atZone( z );   // Better than NULL I suppose.
        intersection = Interval.of( emptyInterval.toInstant() , emptyInterval.toInstant() );
    }
    dateToIntervalMap.put( localDate , intersection );
    // Setup the next loop.
    localDate = localDate.plusDays( 1 );
}

// Report
System.out.println( "interval: " + interval + " = " + zdtStart + "/" + zdtStop );
int nthDate = 0;
Duration totalDuration = Duration.ZERO;
List< LocalDate > dates = new ArrayList<>( dateToIntervalMap.keySet() );
Collections.sort( dates );
List< LocalDate > keys = List.copyOf( dates );
for ( LocalDate date : keys ) {
    nthDate++;
    Interval i = dateToIntervalMap.get( date );
    Instant startInstant = i.getStart();
    Instant stopInstant = i.getEnd();
    Duration d = Duration.between( startInstant , stopInstant );
    totalDuration = totalDuration.plus( d );
    ZonedDateTime start = startInstant.atZone( z );
    ZonedDateTime stop = stopInstant.atZone( z );
    System.out.println( "Day # " + nthDate + " = " + date + " ➙ " + i + " = " + start + "/" + stop + " = " + d );
}
System.out.println("Total duration: " + totalDuration);

为方便起见,让我们在一个块中再次查看相同的代码。

java.sql.*

运行时,我们得到此输出。我们:

  • 在大多数时间里花整整七个小时
  • 倒数第二个日期降至六个小时
  • 最后一天是最后一天的零小时,因为我们的输入间隔在该天的开始时间之前结束。
  

时间间隔:2018-01-01T18:00:00Z / 2018-01-05T09:00:00Z = 2018-01-01T13:00-05:00 [美国/蒙特利尔] / 2018-01-05T04:00- 05:00 [美国/蒙特利尔]

     

第1天= 2018-01-01➙2018-01-02T03:00:00Z / 2018-01-02T10:00:00Z = 2018-01-01T22:00-05:00 [美国/蒙特利尔] / 2018-01-02T05:00-05:00 [美国/蒙特利尔] = PT7H

     

第2天= 2018-01-02➙2018-01-03T03:00:00Z / 2018-01-03T10:00:00Z = 2018-01-02T22:00-05:00 [美国/蒙特利尔] / 2018-01-03T05:00-05:00 [美国/蒙特利尔] = PT7H

     

第3天= 2018-01-03➙2018-01-04T03:00:00Z / 2018-01-04T10:00:00Z = 2018-01-03T22:00-05:00 [美国/蒙特利尔] / 2018-01-04T05:00-05:00 [美国/蒙特利尔] = PT7H

     

第4天= 2018-01-04➙2018-01-05T03:00:00Z / 2018-01-05T09:00:00Z = 2018-01-04T22:00-05:00 [美国/蒙特利尔] / 2018-01-05T04:00-05:00 [美国/蒙特利尔] = PT6H

     

第5天= 2018-01-05➙2018-01-06T03:00:00Z / 2018-01-06T03:00:00Z = 2018-01-05T22:00-05:00 [美国/蒙特利尔] / 2018-01-05T22:00-05:00 [美国/蒙特利尔] = PT0S

     

总持续时间:PT27H


关于 java.time

java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要{{1}}类。

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

答案 4 :(得分:-1)

我尝试过:

LocalDateTime startDate = LocalDateTime.now().minusDays(1);
LocalDateTime endDate = LocalDateTime.now();

long numberOfHours = Duration.between(startDate, endDate).toHours();

并按预期工作

输出:24小时

或者您可以乔达,上面的代码将像这样:

DateTime startDate = new DateTime().minusDays(1);
DateTime endDate = new DateTime();
Hours hours = Hours.hoursBetween(startDate, endDate);
int numberOfHours = hours.getHours();