fix: carry pickup times as local clock readings, not instants
This commit is contained in:
1 parent
c2dc88b8ab
commit
b41aeb7a25
2 files changed
+37
-8
No files matched your search
@@ -458,7 +458,6 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface
|
||||
$date['services_general'] = serialize($combinedServices);
|
||||
|
||||
$pickups = [];
|
||||
$timezone = new \DateTimeZone('Europe/Berlin');
|
||||
foreach ($row['pickups'] as $pickup) {
|
||||
$busProId = (int) $pickup['busProId'];
|
||||
if ($busProId === 0) {
|
||||
@@ -467,9 +466,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface
|
||||
$pickupDate = $dateStart->format('d.m.Y');
|
||||
$pickupTime = '';
|
||||
if ($pickup['datetime'] !== null) {
|
||||
$pickupDateTime = (new \DateTimeImmutable($pickup['datetime']))->setTimezone($timezone);
|
||||
$pickupDate = $pickupDateTime->format('d.m.Y');
|
||||
$pickupTime = $pickupDateTime->format('H:i');
|
||||
[$pickupDate, $pickupTime] = $this->parsePickupDateTime($pickup['datetime']);
|
||||
}
|
||||
$pickups[] = [
|
||||
'bus_pro_id' => $busProId,
|
||||
@@ -803,6 +800,26 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pickup time is the local date and time as BusPro states it
|
||||
* ("2027-03-28T02:45:00"), taken over as is. An ISO-8601 time with an
|
||||
* offset is still accepted and converted to Europe/Berlin, which cannot
|
||||
* express a time inside the daylight saving gap.
|
||||
*
|
||||
* @param string $datetime
|
||||
* @return string[] date as d.m.Y and time as H:i
|
||||
*/
|
||||
protected function parsePickupDateTime(string $datetime): array
|
||||
{
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/', $datetime, $parts)) {
|
||||
return [$parts[3] . '.' . $parts[2] . '.' . $parts[1], $parts[4] . ':' . $parts[5]];
|
||||
}
|
||||
|
||||
$pickupDateTime = (new \DateTimeImmutable($datetime))->setTimezone(new \DateTimeZone('Europe/Berlin'));
|
||||
|
||||
return [$pickupDateTime->format('d.m.Y'), $pickupDateTime->format('H:i')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads back a price parseService() formatted for display ("1.234,50").
|
||||
*
|
||||
|
||||
@@ -25,6 +25,7 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
private const MARKER_TS_FORMAT = 'd.m.Y H:i:s';
|
||||
private const CODE_PSEUDOPRICE = 'PDGS';
|
||||
private const TIMEZONE = 'Europe/Berlin';
|
||||
private const LOCAL_DATETIME_FORMAT = 'Y-m-d\TH:i:s';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@@ -41,6 +42,15 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
*/
|
||||
private $timezone;
|
||||
|
||||
/**
|
||||
* Pickup times are clock readings, not instants. They are handled in UTC,
|
||||
* which has no daylight saving gaps, so a time BusPro plans inside one
|
||||
* (02:45 on the night clocks go forward) is kept as stated.
|
||||
*
|
||||
* @var \DateTimeZone
|
||||
*/
|
||||
private $wallClock;
|
||||
|
||||
/**
|
||||
* @var array|null pickup master data by BusPro id
|
||||
*/
|
||||
@@ -51,6 +61,7 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
$this->path = rtrim($path, '/');
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
$this->timezone = new \DateTimeZone(self::TIMEZONE);
|
||||
$this->wallClock = new \DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
@@ -245,7 +256,8 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
/**
|
||||
* A bare HH:mm takes the travel date's start, and is rolled onto the next
|
||||
* day when it would otherwise fall before the latest full date-time listed
|
||||
* before it: a coach does not board backwards.
|
||||
* before it: a coach does not board backwards. The result is the local
|
||||
* date and time as BusPro states it, without an offset.
|
||||
*/
|
||||
private function readDatePickups(\SimpleXMLElement $xmlDate, \DateTimeImmutable $from): array
|
||||
{
|
||||
@@ -257,12 +269,12 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
|
||||
$datetime = null;
|
||||
if ($time !== '' && strpos($time, ' ') !== false) {
|
||||
$datetime = \DateTimeImmutable::createFromFormat('!d.m.Y H:i:s', $time, $this->timezone) ?: null;
|
||||
$datetime = \DateTimeImmutable::createFromFormat('!d.m.Y H:i:s', $time, $this->wallClock) ?: null;
|
||||
if ($datetime !== null && ($latest === null || $datetime > $latest)) {
|
||||
$latest = $datetime;
|
||||
}
|
||||
} elseif ($time !== '') {
|
||||
$datetime = \DateTimeImmutable::createFromFormat('!Y-m-d H:i', $from->format('Y-m-d') . ' ' . $time, $this->timezone) ?: null;
|
||||
$datetime = \DateTimeImmutable::createFromFormat('!Y-m-d H:i', $from->format('Y-m-d') . ' ' . $time, $this->wallClock) ?: null;
|
||||
if ($datetime !== null && $latest !== null && $datetime < $latest) {
|
||||
$datetime = $datetime->modify('+1 day');
|
||||
}
|
||||
@@ -275,7 +287,7 @@ final class XmlProductReader implements \IteratorAggregate
|
||||
'city' => $masterData['city'] ?? null,
|
||||
'street' => $masterData['street'] ?? null,
|
||||
'price' => $this->parsePrice((string) $xmlPickup['preis']),
|
||||
'datetime' => $datetime !== null ? $datetime->format(\DateTimeInterface::ATOM) : null,
|
||||
'datetime' => $datetime !== null ? $datetime->format(self::LOCAL_DATETIME_FORMAT) : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user