根据 LocalDateTime 对象列表和带流的已占用时隙列表创建可用时隙列表

Creating a list of avalible Timeslots based on a list LocalDateTime objects and a List of taken Timeslots with Streams

我有一个 List<Timeslot>,其中包含具有以下字段的实体 Timeslot

  1. timeslot_id;
  2. day;
  3. start_time;
  4. end_time.

例如,这个列表包含两条记录:

第二个列表包含时间戳List<LocalDateTime>:

[2022-04-16T08:00, 2022-04-16T09:00, 2022-04-16T10:00, 2022-04-16T11:00, 
 2022-04-16T12:00, 2022-04-16T13:00, 2022-04-16T14:00, 2022-04-16T15:00]

我需要创建第三个 List<Timeslot>,它将包含 Timeslot,除了第一个列表中的这两个。

在这种情况下,因此,第三个列表应该包含 Timeslot class.

的六个对象

start_time 第一个应该等于 2022-04-16T08:00 和 end_time 2022-04-16T09:00。 IE。 start_timeend_time 之间每隔 时间段 的差异是 一个小时 .

因此,根据上面提供的 timestamps 列表构造的 result 应该包含六个对象:

start_time 9:0010:00 的对象不会出现在第三个列表中,因为它们已经被预订了(出现在第一个列表中).

我尝试使用 Java 流创建 第三个列表 ,它应该将字段 start_timeend_time 与来自 第二个列表.

我试过了,但结果列表总是空的:

List<Timeslot> availableSlots = query.stream()
    .filter(timeslot -> timestamps.contains(timeslot.getStartTime()))
    .toList();

时间段 class:

@Entity(name = "timeslot")
public class Timeslot {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "timeslot_id")
    private Integer id;
    @Column(name = "day", columnDefinition = "DATE")
    private LocalDateTime day;
    @Column(name = "start_time")
    private LocalDateTime startTime;
    @Column(name = "end_time")
    private LocalDateTime endTime;
    @Column(name = "user_id")
    private Integer userId;
    @Column(name = "is_recorded")
    private Boolean isRecorded;
}

我已针对此问题简化了您的 Timeslot class(用于演示目的),因为对于此任务,您主要关注 每个时间段的开始时间结束时间

我的方法是通过从已经存在的每个 timeslot 中提取 start time 来创建一组 LocalDateTime 对象采取(由您的第一个列表表示)。

然后在 query 列表上创建一个流并过滤集合中不存在的 date-time 对象。然后使用每个 date-time 对象创建一个 timeslot 作为 start time (end time = 开始时间 + 1 小时)。并将所有流元素收集到一个 list.

注意:终端操作toList()创建一个不可变列表,您可以通过应用collect(Collectors.toList())代替获得可变列表。

public static void main(String[] args) {
    List<LocalDateTime> query =
        List.of(LocalDateTime.of(2022, 04, 16, 8, 00),
                LocalDateTime.of(2022, 04, 16, 9, 00),
                LocalDateTime.of(2022, 04, 16, 10, 00),
                LocalDateTime.of(2022, 04, 16, 11, 00),
                LocalDateTime.of(2022, 04, 16, 12, 00),
                LocalDateTime.of(2022, 04, 16, 13, 00),
                LocalDateTime.of(2022, 04, 16, 14, 00),
                LocalDateTime.of(2022, 04, 16, 15, 00));

    List<Timeslot> timeslots = // timeslots that already taken
        List.of(new Timeslot(LocalDateTime.of(2022, 04, 16, 9, 00),
                             LocalDateTime.of(2022, 04, 16, 10, 00)),
                new Timeslot(LocalDateTime.of(2022, 04, 16, 10, 00),
                             LocalDateTime.of(2022, 04, 16, 11, 00)));
    
    Set<LocalDateTime> takenStartTime = timeslots.stream()
        .map(Timeslot::getStartTime)
        .collect(Collectors.toSet());

    List<Timeslot> availableSlots = query.stream()
        .filter(dateTime -> !takenStartTime.contains(dateTime))
        .map(dateTime -> new Timeslot(dateTime, dateTime.plusHours(1)))
        .toList();
    
    availableSlots.forEach(System.out::println);
}

简化虚拟Timeslotclass

public class Timeslot {
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    
    // constructor, getters, toString()
}

输出

Timeslot{start_time=2022-04-16T08:00, end_time=2022-04-16T09:00}
Timeslot{start_time=2022-04-16T11:00, end_time=2022-04-16T12:00}
Timeslot{start_time=2022-04-16T12:00, end_time=2022-04-16T13:00}
Timeslot{start_time=2022-04-16T13:00, end_time=2022-04-16T14:00}
Timeslot{start_time=2022-04-16T14:00, end_time=2022-04-16T15:00}
Timeslot{start_time=2022-04-16T15:00, end_time=2022-04-16T16:00}

非常好。这里有一个替代方案,作为思考的食物。

顺便说一下,您的 day 字段是多余的。您从 LocalDateTime 开始就知道日期和 date-time。我建议从您的模型中删除它。

添加TimeSlot#contains方法

让我们将您的 TimeSlot class 更简单地定义为 record。我将使用 UUID 作为标识符,而不是 fiddle 计数整数。

record TimeSlot( UUID id , LocalDateTime start , LocalDateTime end ) {}

诀窍是添加一个 contains 方法,告诉调用者特定的 LocalDateTime 是否恰好位于我们的时间段范围内。我们使用 Half-Open 方法,其中时间跨度定义为开始 inclusive 而结束 exclusive.

请注意,“不早于”是“等于或晚于”的缩写。

record TimeSlot( UUID id , LocalDateTime start , LocalDateTime end )
{
    boolean contains ( LocalDateTime localDateTime )
    {
        return ( ( ! localDateTime.isBefore( this.start ) ) && localDateTime.isBefore( this.end ) );
    }
}

示例数据。

List < TimeSlot > timeslots =
        List.of(
                new TimeSlot(
                        UUID.fromString( "0500ce28-ad96-43d0-9b3d-b907cadd27f9" ) ,
                        LocalDateTime.of( 2022 , 04 , 16 , 9 , 00 ) ,
                        LocalDateTime.of( 2022 , 04 , 16 , 10 , 00 )
                ) ,
                new TimeSlot(
                        UUID.fromString( "887d72df-4787-4974-ab6d-f2a46cb7d7af" ) ,
                        LocalDateTime.of( 2022 , 04 , 16 , 10 , 00 ) ,
                        LocalDateTime.of( 2022 , 04 , 16 , 11 , 00 )
                )
        );

和一些示例输入。

List < LocalDateTime > inputs =
        List.of(
                LocalDateTime.of( 2022 , 04 , 16 , 8 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 9 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 10 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 11 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 12 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 13 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 14 , 00 ) ,
                LocalDateTime.of( 2022 , 04 , 16 , 15 , 00 )
        );

创建一个包含 TimeSlot 个对象的新列表,跳过我们现有的 TimeSlot 个对象所花费的时间跨度。

List < TimeSlot > resultingTimeSlots = new ArrayList <>( inputs.size() );

循环输入 LocalDateTime 个对象。对于每个,询问每个原始时隙是否恰好包含该 date-time 值。如果是这样,请将找到的时间段添加到我们的列表中。如果不是,则使用输入 LocalDateTime 作为开始创建一个新的 TimeSlot 对象,并假设结束应该在一个小时后。

for ( LocalDateTime input : inputs )
{
    Optional < TimeSlot > hit = timeslots.stream().filter( timeSlot -> timeSlot.contains( input ) ).findAny();
    resultingTimeSlots.add( hit.orElse( new TimeSlot( UUID.randomUUID() , input , input.plusHours( 1 ) ) ) );
}

该代码可以优化。使用 Optional#orElse 实际上是在 for 的每个循环上执行 new TimeSlot,无论是否需要。

我们可以通过向我们的记录添加一个紧凑的构造函数来验证此行为。

record TimeSlot( UUID id , LocalDateTime start , LocalDateTime end )
{
    TimeSlot
    {
        System.out.println( "running constructor for " + id );
    }

    boolean contains ( LocalDateTime localDateTime )
    {
        return ( ( ! localDateTime.isBefore( start ) ) && localDateTime.isBefore( end ) );
    }
}

让我们用 Optional#orElseGet 替换它,同时将我们的 new TimeSlot 转换为实现所需 Supplier 接口的 lambda。这个变化意味着一个新的 TimeSlot 对象只有在我们真正需要它时才会被实例化。

for ( LocalDateTime input : inputs )
{
    Optional < TimeSlot > hit = timeslots.stream().filter( timeSlot -> timeSlot.contains( input ) ).findAny();
    resultingTimeSlots.add( hit.orElseGet( ( ) -> new TimeSlot( UUID.randomUUID() , input , input.plusHours( 1 ) ) ) );
}

转储到控制台。

System.out.println( "resultingTimeSlots.size(): " + resultingTimeSlots.size() );
System.out.println( "resultingTimeSlots.containsAll( timeslots ): " + resultingTimeSlots.containsAll( timeslots ) );
System.out.println( resultingTimeSlots );

当运行.

running constructor for 0500ce28-ad96-43d0-9b3d-b907cadd27f9
running constructor for 887d72df-4787-4974-ab6d-f2a46cb7d7af
running constructor for ce2b9c66-ef69-4ecd-a451-7a51ebbb259e
running constructor for c77cf83f-5ea8-4da3-9a44-104a56f4de03
running constructor for 139280b6-20c4-4428-b2cb-80717a00756b
running constructor for 1d219e16-0513-466e-9b84-091312e4ff5e
running constructor for 4b0b6c11-c6ae-4e04-a8fe-6c1245f7e80b
running constructor for 1ccdbd7f-ff4c-4d7d-b900-54d14898a50f
resultingTimeSlots.size(): 8
resultingTimeSlots.containsAll( timeslots ): true
[TimeSlot[id=ce2b9c66-ef69-4ecd-a451-7a51ebbb259e, start=2022-04-16T08:00, end=2022-04-16T09:00], TimeSlot[id=0500ce28-ad96-43d0-9b3d-b907cadd27f9, start=2022-04-16T09:00, end=2022-04-16T10:00], TimeSlot[id=887d72df-4787-4974-ab6d-f2a46cb7d7af, start=2022-04-16T10:00, end=2022-04-16T11:00], TimeSlot[id=c77cf83f-5ea8-4da3-9a44-104a56f4de03, start=2022-04-16T11:00, end=2022-04-16T12:00], TimeSlot[id=139280b6-20c4-4428-b2cb-80717a00756b, start=2022-04-16T12:00, end=2022-04-16T13:00], TimeSlot[id=1d219e16-0513-466e-9b84-091312e4ff5e, start=2022-04-16T13:00, end=2022-04-16T14:00], TimeSlot[id=4b0b6c11-c6ae-4e04-a8fe-6c1245f7e80b, start=2022-04-16T14:00, end=2022-04-16T15:00], TimeSlot[id=1ccdbd7f-ff4c-4d7d-b900-54d14898a50f, start=2022-04-16T15:00, end=2022-04-16T16:00]]

如果您想跳过原始项目,请将 for 循环更改为此。

for ( LocalDateTime input : inputs )
{
    Optional < TimeSlot > hit = timeslots.stream().filter( timeSlot -> timeSlot.contains( input ) ).findAny();
    if ( hit.isEmpty() ) { resultingTimeSlots.add( new TimeSlot( UUID.randomUUID() , input , input.plusHours( 1 ) ) ); }
}

跟踪约会

如果你想做一个未来的约会跟踪应用程序,你的方法是错误的。

您应该只跟踪 LocalDateTime 的开始,而不是结束。不是结束,而是将约会的长度跟踪为 Duration 对象。至关重要的是,为时区 (ZoneId) 添加一个字段作为此日期和时间的预期上下文。

要理解的概念是政治时间与自然时间不同。天不一定是 24 小时。它们可能是 23、23.5、25 或其他小时数。因此,1 小时的约会可能从 2 点开始,但在 4 点结束。

当您需要构建时刻表、时间轴上的特定点时,将时区应用到开始。并在结尾添加持续时间。

ZonedDateTime start = startLocalDateTime.atZone( storedZoneId ) ;
ZonedDateTime end = start.plus( storedDuration ) ;

但是从不存储这些ZonedDateTime对象。如果政治家改变其管辖范围内的时区规则,它们将变得无效。世界各地的政治家以惊人的频率这样做。

我和其他人在 Stack Overflow 上多次写过这个主题。所以搜索以了解更多信息。