Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ protected function getWeekGridEntries(
$nr_available = \ilBookingReservation::getAvailableObject(
[$object_id],
$slot_from,
$slot_to - 1,
$slot_to,
false,
true
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public function showNumberForm(): void
$this->log->debug("showNumberForm");
$object_id = $this->book_obj_id;
$from = $this->book_request->getSlotFrom();
$to = $this->book_request->getSlotTo() - 1;
$to = $this->book_request->getSlotTo();
$this->tabs_gui->clearTargets();
$this->tabs_gui->setBackTarget(
$this->lng->txt('book_back_to_list'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,17 @@ public function getNumberOfReservations(
? " AND date_to > {$this->db->quote(time(), ilDBConstants::T_INTEGER)}"
: '';

$date_to_match = 'date_to = ' . $this->db->quote($to, ilDBConstants::T_INTEGER);
if ($to > 0 && $to % 60 === 0) {
$date_to_match = '(' . $date_to_match
. ' OR date_to = ' . $this->db->quote($to - 1, ilDBConstants::T_INTEGER) . ')';
}

$set = $this->db->query(
'SELECT count(*) cnt, object_id FROM booking_reservation'
. " WHERE {$this->db->in('object_id', $ids, '', ilDBConstants::T_INTEGER)}{$date}"
. " AND (status IS NULL OR status <> {$this->db->quote(ilBookingReservation::STATUS_CANCELLED, ilDBConstants::T_INTEGER)})"
. " AND date_from = {$this->db->quote($from, ilDBConstants::T_INTEGER)} AND date_to = {$this->db->quote($to, ilDBConstants::T_INTEGER)}"
. " AND date_from = {$this->db->quote($from, ilDBConstants::T_INTEGER)} AND {$date_to_match}"
. ' GROUP BY object_id'
);

Expand Down Expand Up @@ -243,7 +249,7 @@ public function getListByDate(

if ($a_has_schedule && ($filter["slot"] ?? false)) {
$slot_idx = date("w", $row["date_from"]) . "_" . date("H:i", $row["date_from"]) .
"-" . date("H:i", $row["date_to"] + 1);
"-" . $this->formatReservationEndTimeLabel((int) $row["date_to"]);
if ($filter["slot"] != $slot_idx) {
continue;
}
Expand All @@ -270,7 +276,7 @@ public function getListByDate(
$res[$idx]["booking_reservation_id"] = $idx;
$res[$idx]["date"] = date("Y-m-d", $row["date_from"]);
$res[$idx]["slot"] = date("H:i", $row["date_from"]) . " - " .
date("H:i", $row["date_to"] + 1);
$this->formatReservationEndTimeLabel((int) $row["date_to"]);
$res[$idx]["week"] = date("W", $row["date_from"]);
$res[$idx]["weekday"] = date("w", $row["date_from"]);
$res[$idx]["can_be_cancelled"] = ($row["status"] != \ilBookingReservation::STATUS_CANCELLED &&
Expand Down Expand Up @@ -343,4 +349,17 @@ public function getReservationIdsByBookingObjectId(int $booking_object_id): arra

return $ret;
}

/**
* Display end time for reservations not yet migrated off legacy second-59 ends.
*/
private function formatReservationEndTimeLabel(int $date_to): string
{
$timestamp = $date_to;
if ($date_to > 0 && $date_to % 60 === 59) {
$timestamp++;
}

return date('H:i', $timestamp);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public function getMigrations(): array
new \ilBookingManagerObjectInfoMigration(),
new \ilBookingManagerBookingInfoMigration(),
new \ilBookingManagerOfflineMigration(),
new \ilBookingManagerReservationTimestampMigration(),
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

use ILIAS\Setup\Environment;
use ILIAS\Setup\Migration;

class ilBookingManagerReservationTimestampMigration implements Migration
{
private ilDBInterface $db;

public function getLabel(): string
{
return 'Normalize booking reservation timestamps to minute boundaries and linked personal calendar ends.';
}

public function getDefaultAmountOfStepsPerRun(): int
{
return 1000;
}

public function getPreconditions(Environment $environment): array
{
return [
new ilDatabaseInitializedObjective(),
new ilDatabaseUpdatedObjective(),
];
}

public function prepare(Environment $environment): void
{
$this->db = $environment->getResource(Environment::RESOURCE_DATABASE);
}

public function step(Environment $environment): void
{
$set = $this->db->query(
'SELECT booking_reservation_id, user_id, date_from, date_to FROM booking_reservation'
. ' WHERE (date_from > 0 AND MOD(date_from, 60) <> 0)'
. ' OR (date_to > 0 AND MOD(date_to, 60) <> 0)'
. ' LIMIT 1'
);

$row = $this->db->fetchAssoc($set);
if ($row === null) {
return;
}

$updates = [];
$normalized_from = $this->normalizeTimestamp((int) $row['date_from']);
if ($normalized_from !== (int) $row['date_from']) {
$updates['date_from'] = [ilDBConstants::T_INTEGER, $normalized_from];
}

$normalized_to = $this->normalizeTimestamp((int) $row['date_to']);
if ($normalized_to !== (int) $row['date_to']) {
$updates['date_to'] = [ilDBConstants::T_INTEGER, $normalized_to];
}

if ($updates === []) {
return;
}

if (isset($updates['date_to'])) {
$this->updateLinkedPersonalCalendarEnd(
(int) $row['booking_reservation_id'],
(int) $row['user_id'],
(int) $row['date_to'],
$normalized_to
);
}

$this->db->update(
'booking_reservation',
$updates,
[
'booking_reservation_id' => [ilDBConstants::T_INTEGER, (int) $row['booking_reservation_id']],
]
);
}

public function getRemainingAmountOfSteps(): int
{
$set = $this->db->query(
'SELECT COUNT(booking_reservation_id) AS cnt FROM booking_reservation'
. ' WHERE (date_from > 0 AND MOD(date_from, 60) <> 0)'
. ' OR (date_to > 0 AND MOD(date_to, 60) <> 0)'
);

return (int) ($this->db->fetchObject($set)?->cnt ?? 0);
}

private function normalizeTimestamp(int $timestamp): int
{
if ($timestamp <= 0) {
return $timestamp;
}

$remainder = $timestamp % 60;
if ($remainder === 0) {
return $timestamp;
}

return $timestamp + (60 - $remainder);
}

private function updateLinkedPersonalCalendarEnd(
int $booking_reservation_id,
int $user_id,
int $legacy_date_to,
int $normalized_date_to
): void {
if ($legacy_date_to <= 0) {
return;
}

$format = 'Y-m-d H:i:s';
$this->db->manipulateF(
<<<SQL
UPDATE cal_entries
INNER JOIN cal_cat_assignments ON cal_entries.cal_id = cal_cat_assignments.cal_id
INNER JOIN cal_categories ON cal_cat_assignments.cat_id = cal_categories.cat_id
SET cal_entries.enda = %s
WHERE cal_entries.context_id = %s
AND cal_categories.type = %s
AND cal_categories.obj_id = %s
AND cal_entries.enda = %s
SQL,
[
ilDBConstants::T_DATETIME,
ilDBConstants::T_INTEGER,
ilDBConstants::T_INTEGER,
ilDBConstants::T_INTEGER,
ilDBConstants::T_DATETIME,
],
[
gmdate($format, $normalized_date_to),
$booking_reservation_id,
ilCalendarCategory::TYPE_BOOK,
$user_id,
gmdate($format, $legacy_date_to),
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ protected function resolveRecords(?array $selected_ids = null): array
'object_id' => $object_id,
'title' => $titles[$object_id],
'slot_from' => $slot_from,
'slot_to' => $slot_to + 1,
'slot_to' => $slot_to,
'has_schedule' => count($parts) === 3,
'has_user_active_booking' =>
$user_id === (int) $reservation['user_id']
Expand All @@ -330,7 +330,7 @@ private function buildItemDescription(array $record): string
$this->lng->loadLanguageModule('dateplaner');
$period = ilDatePresentation::formatPeriod(
new ilDateTime($record['slot_from'], IL_CAL_UNIX),
new ilDateTime($record['slot_to'] - 1, IL_CAL_UNIX)
new ilDateTime($record['slot_to'], IL_CAL_UNIX)
);

return "{$record['title']} ({$period})";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ protected function formatBookingDescription(array $booking): string
if ($this->booking_pool->getScheduleType() !== ilObjBookingPool::TYPE_NO_SCHEDULE && $booking['date_from']) {
$parts[] = ilDatePresentation::formatPeriod(
new ilDateTime($booking['date_from'], IL_CAL_UNIX),
new ilDateTime($booking['date_to'] + 1, IL_CAL_UNIX)
new ilDateTime($booking['date_to'], IL_CAL_UNIX)
);
}
return implode(' – ', $parts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ protected function formatBookingDescription(array $booking): string
if ($this->booking_pool->getScheduleType() !== ilObjBookingPool::TYPE_NO_SCHEDULE && $booking['date_from']) {
$parts[] = ilDatePresentation::formatPeriod(
new ilDateTime($booking['date_from'], IL_CAL_UNIX),
new ilDateTime($booking['date_to'] + 1, IL_CAL_UNIX)
new ilDateTime($booking['date_to'], IL_CAL_UNIX)
);
}
return implode(' – ', $parts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ private function parseBookingPeriodValue(mixed $value): ?int
protected function getBookingTimeSlotFilterLabel(array $booking): string
{
$date_from = new DateTimeImmutable("@{$booking['date_from']}");
$date_to = new DateTimeImmutable('@' . ((int) $booking['date_to'] + 1));
$date_to = new DateTimeImmutable('@' . (int) $booking['date_to']);

return "{$date_from->format('H:i')} - {$date_to->format('H:i')}";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ public function getRows(
foreach ($records as $record) {
$user_id = $record['user_id'];

$record['date_to']++;

$date_from = new DateTimeImmutable("@{$record['date_from']}");
$time_slot = $this->isAllDayBooking($record)
? $this->lng->txt('book_all_day')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class BookingManagerReservationTimestampMigrationTest extends TestCase
{
#[DataProvider('normalizeTimestampProvider')]
public function testNormalizeTimestampRoundsUpToNextMinuteBoundary(int $input, int $expected): void
{
$migration = new ilBookingManagerReservationTimestampMigration();
$method = new ReflectionMethod($migration, 'normalizeTimestamp');

parent::assertSame($expected, $method->invoke($migration, $input));
}

public static function normalizeTimestampProvider(): Generator
{
yield 'zero stays zero' => [0, 0];
yield 'negative value stays unchanged' => [-100, -100];

$minute_aligned = mktime(12, 0, 0, 7, 15, 2026);
yield 'already minute aligned stays unchanged' => [$minute_aligned, $minute_aligned];

$legacy_end_minus_one = mktime(11, 59, 59, 7, 15, 2026);
yield 'legacy end minus one second rounds up' => [$legacy_end_minus_one, $minute_aligned];

$one_second_past_minute = mktime(12, 0, 1, 7, 15, 2026);
$next_minute = mktime(12, 1, 0, 7, 15, 2026);
yield 'one second past minute rounds up' => [$one_second_past_minute, $next_minute];
}
}
Loading
Loading