format( $format ) === $value ) { return $dt->format( 'Y-m-d H:i:s' ); } } return null; } /** * Split this window into consecutive lesson-length slots: 09:00–16:00 with * 60-minute lessons yields seven bookable slots. A trailing remainder shorter * than the lesson length is dropped, and an empty list is returned when the * window cannot fit a single lesson. * * @return list */ public function splitByDuration(): array { if ( $this->durationMinutes <= 0 ) { return []; } $end = new \DateTimeImmutable( $this->endDt ); $step = new \DateInterval( 'PT' . $this->durationMinutes . 'M' ); $cursor = new \DateTimeImmutable( $this->startDt ); $chunkEnd = $cursor->add( $step ); $slots = []; while ( $chunkEnd <= $end ) { $slots[] = new self( instructorId: $this->instructorId, startDt: $cursor->format( 'Y-m-d H:i:s' ), endDt: $chunkEnd->format( 'Y-m-d H:i:s' ), durationMinutes: $this->durationMinutes, offeringId: $this->offeringId, ); $cursor = $chunkEnd; $chunkEnd = $cursor->add( $step ); } return $slots; } public static function fromRow( \stdClass $row ): self { return new self( instructorId: Val::int( $row->instructor_id ), startDt: Val::string( $row->start_dt ), endDt: Val::string( $row->end_dt ), durationMinutes: Val::int( $row->duration_minutes ), offeringId: Val::intOrNull( $row->offering_id ), isBooked: Val::bool( $row->is_booked ), recurrenceGroup: Val::intOrNull( $row->recurrence_group ), id: Val::int( $row->id ), ); } /** * Returns a plain array representation of the slot. * * @return array */ public function toArray(): array { return [ 'id' => $this->id, 'instructor_id' => $this->instructorId, 'offering_id' => $this->offeringId, 'start_dt' => $this->startDt, 'end_dt' => $this->endDt, 'duration_minutes' => $this->durationMinutes, 'is_booked' => $this->isBooked, 'recurrence_group' => $this->recurrenceGroup, ]; } }