chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Framework\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateTimeImmutable;
use Exception;
interface Clock
{
/**
* Gibt die aktuelle Zeit zurück.
*
* @return DateTimeImmutable
*/
public function now(): DateTimeImmutable;
/**
* Erzeugt ein DateTimeImmutable-Objekt aus einem Zeitstempel.
*
* @param int $timestamp UNIX-Zeitstempel
* @return DateTimeImmutable
*/
public function fromTimestamp(int $timestamp): DateTimeImmutable;
/**
* Erzeugt ein DateTimeImmutable-Objekt aus einem String im angegebenen Format.
*
* @param string $dateTime Der zu parsende Zeitstring
* @param string|null $format Optionales Format für die Analyse
* @return DateTimeImmutable
* @throws InvalidDateTimeException wenn das Parsen fehlschlägt
* @throws InvalidTimezoneException wenn eine angegebene Zeitzone ungültig ist
*/
public function fromString(string $dateTime, ?string $format = null): DateTimeImmutable;
public function today(): DateTimeImmutable;
public function yesterday(): DateTimeImmutable;
public function tomorrow(): DateTimeImmutable;
/**
* Gibt den aktuellen UNIX-Zeitstempel zurück.
*
* @return int
*/
public function time(): int;
/**
* Gibt den aktuellen Zeitstempel mit Mikrosekunden zurück.
*
* @param bool $asFloat Wenn true, wird ein float zurückgegeben, sonst ein Array
* @return float|array{float, int}
*/
public function microtime(bool $asFloat = false): float|array;
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime;
use App\Framework\Config\AppConfig;
use App\Framework\DI\Initializer;
use DateTimeZone;
final readonly class ClockInitializer
{
public function __construct(
private AppConfig $config
) {}
#[Initializer]
public function __invoke(): Clock
{
$timezone = $this->config->timezone;
$timezone = new DateTimeZone($timezone->value);
return new SystemClock($timezone);
}
#[Initializer]
public function initTimer(): Timer
{
return new SystemTimer();
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace App\Framework\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
final readonly class DateRange
{
/**
* @param DateTimeImmutable $start Startdatum des Bereichs
* @param DateTimeImmutable $end Enddatum des Bereichs
* @throws InvalidArgumentException wenn das Enddatum vor dem Startdatum liegt
*/
public function __construct(
private DateTimeImmutable $start,
private DateTimeImmutable $end
) {
if ($end < $start) {
throw new InvalidArgumentException(
'Enddatum kann nicht vor dem Startdatum liegen.'
);
}
}
/**
* Erzeugt einen DateRange aus zwei Datumsstrings.
*
* @param string $start Startdatum als String
* @param string $end Enddatum als String
* @param DateTimeZone|string|null $timezone Optionale Zeitzone (Standard: UTC)
* @return self
* @throws InvalidTimezoneException
* @throws InvalidDateTimeException
*/
public static function fromStrings(
string $start,
string $end,
DateTimeZone|string|null $timezone = null
): self {
if (is_string($timezone)) {
$timezone = DateTime::createTimezone($timezone);
}
$tz = $timezone ?? DateTime::createTimezone('UTC');
$startDate = DateTime::fromString($start, $tz);
$endDate = DateTime::fromString($end, $tz);
return new self($startDate, $endDate);
}
/**
* Gibt das Startdatum zurück.
*
* @return DateTimeImmutable
*/
public function getStart(): DateTimeImmutable
{
return $this->start;
}
/**
* Gibt das Enddatum zurück.
*
* @return DateTimeImmutable
*/
public function getEnd(): DateTimeImmutable
{
return $this->end;
}
/**
* Überprüft, ob ein bestimmtes Datum im Bereich liegt.
*
* @param DateTimeInterface $date Das zu überprüfende Datum
* @return bool
*/
public function contains(DateTimeInterface $date): bool
{
return ($date >= $this->start && $date <= $this->end);
}
/**
* Überprüft, ob ein anderer Bereich vollständig in diesem Bereich enthalten ist.
*
* @param DateRange $range Der zu überprüfende Bereich
* @return bool
*/
public function containsRange(DateRange $range): bool
{
return ($range->getStart() >= $this->start && $range->getEnd() <= $this->end);
}
/**
* Überprüft, ob sich dieser Bereich mit einem anderen überschneidet.
*
* @param DateRange $range Der zu überprüfende Bereich
* @return bool
*/
public function overlaps(DateRange $range): bool
{
return ($this->start <= $range->getEnd() && $this->end >= $range->getStart());
}
/**
* Berechnet die Dauer dieses Bereichs.
*
* @return DateInterval
*/
public function getDuration(): DateInterval
{
return $this->start->diff($this->end);
}
/**
* Gibt die Dauer in Sekunden zurück.
*
* @return int
*/
public function getDurationInSeconds(): int
{
return $this->end->getTimestamp() - $this->start->getTimestamp();
}
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
final class DateTime
{
private static ?DateTimeZone $defaultTimezone = null;
/**
* Erstellt ein DateTimeImmutable-Objekt aus einem Zeitstempel.
*
* @param int $timestamp UNIX-Zeitstempel
* @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
* @return DateTimeImmutable
* @throws InvalidTimezoneException|InvalidDateTimeException wenn die Zeitzone ungültig ist
*/
public static function fromTimestamp(int $timestamp, DateTimeZone|string|null $timezone = null): DateTimeImmutable
{
$tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();
try {
return new DateTimeImmutable('@' . $timestamp)->setTimezone($tz);
} catch (\Exception $e) {
throw new InvalidDateTimeException((string)$timestamp, 'timestamp', $e);
}
}
/**
* Erstellt ein DateTimeImmutable-Objekt aus einem String.
*
* @param string $dateTime Der zu parsende Zeitstring
* @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
* @return DateTimeImmutable
* @throws InvalidDateTimeException wenn das Parsen fehlschlägt
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public static function fromString(string $dateTime, DateTimeZone|string|null $timezone = null): DateTimeImmutable
{
$tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();
try {
return new DateTimeImmutable($dateTime, $tz);
} catch (\Exception $e) {
throw new InvalidDateTimeException($dateTime, null, $e);
}
}
/**
* Erstellt ein DateTimeImmutable-Objekt aus einem String mit spezifischem Format.
*
* @param string $dateTime Der zu parsende Zeitstring
* @param string $format Das erwartete Format
* @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
* @return DateTimeImmutable
* @throws InvalidDateTimeException wenn das Parsen fehlschlägt
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public static function fromFormat(string $dateTime, string $format, DateTimeZone|string|null $timezone = null): DateTimeImmutable
{
$tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();
try {
$date = DateTimeImmutable::createFromFormat($format, $dateTime, $tz);
if ($date === false) {
throw new InvalidDateTimeException($dateTime, $format);
}
return $date;
} catch (\Exception $e) {
if ($e instanceof InvalidDateTimeException) {
throw $e;
}
throw new InvalidDateTimeException($dateTime, $format, $e);
}
}
/**
* Erstellt ein DateTimeImmutable-Objekt aus einem anderen DateTimeInterface.
*
* @param DateTimeInterface $dateTime Das zu konvertierende DateTime
* @param DateTimeZone|string|null $timezone Optionale neue Zeitzone
* @return DateTimeImmutable
* @throws InvalidDateTimeException wenn die Zeitzone ungültig ist
*/
public static function fromDateTime(DateTimeInterface $dateTime, DateTimeZone|string|null $timezone = null): DateTimeImmutable
{
try {
if ($dateTime instanceof DateTimeImmutable) {
$result = $dateTime;
} else {
$result = DateTimeImmutable::createFromInterface($dateTime);
}
if ($timezone !== null) {
$tz = self::createTimezone($timezone);
$result = $result->setTimezone($tz);
}
return $result;
} catch (\Exception $e) {
throw new InvalidDateTimeException($dateTime->format('c'), null, $e);
}
}
/**
* Erstellt ein DateInterval-Objekt.
*
* @param string $interval Das Interval im ISO 8601 Format (z.B. 'P1D', 'PT1H')
* @return DateInterval
* @throws InvalidDateTimeException wenn das Interval ungültig ist
*/
public static function createInterval(string $interval): DateInterval
{
try {
return new DateInterval($interval);
} catch (\Exception $e) {
throw new InvalidDateTimeException($interval, 'DateInterval', $e);
}
}
/**
* Erstellt eine DateTimeZone.
*
* @param DateTimeZone|string $timezone Die Zeitzone
* @return DateTimeZone
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public static function createTimezone(DateTimeZone|string $timezone): DateTimeZone
{
if ($timezone instanceof DateTimeZone) {
return $timezone;
}
try {
return new DateTimeZone($timezone);
} catch (\Exception $e) {
throw new InvalidTimezoneException($timezone, $e);
}
}
/**
* Gibt die Standard-Zeitzone zurück.
*
* @return DateTimeZone
*/
public static function getDefaultTimezone(): DateTimeZone
{
return self::$defaultTimezone ?? new DateTimeZone('Europa/Berlin');
}
/**
* Setzt eine neue Standard-Zeitzone.
*
* @param DateTimeZone|string $timezone Die neue Standard-Zeitzone
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public static function setDefaultTimezone(DateTimeZone|string $timezone): void
{
self::$defaultTimezone = self::createTimezone($timezone);
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace App\Framework\DateTime;
use DateInvalidTimeZoneException;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
class DateTimeFormatter
{
/**
* Standard-Zeitzone für Formatierungen
*/
private DateTimeZone $timezone;
/**
* @param DateTimeZone|string|null $timezone Die zu verwendende Zeitzone (Standard: UTC)
* @throws DateInvalidTimeZoneException
*/
public function __construct(DateTimeZone|string|null $timezone = null)
{
if (is_string($timezone)) {
$timezone = new DateTimeZone($timezone);
}
$this->timezone = $timezone ?? new DateTimeZone('Europe/Berlin');
}
/**
* Formatiert ein DateTime im ISO8601-Format.
*
* @param DateTimeInterface $dateTime Das zu formatierende DateTime
* @return string
* @throws DateMalformedStringException
*/
public function formatIso8601(DateTimeInterface $dateTime): string
{
return $this->format($dateTime, DateTimeInterface::ATOM);
}
/**
* Formatiert ein DateTime im SQL-Datetime-Format.
*
* @param DateTimeInterface $dateTime Das zu formatierende DateTime
* @return string
* @throws DateMalformedStringException
*/
public function formatSql(DateTimeInterface $dateTime): string
{
return $this->format($dateTime, 'Y-m-d H:i:s');
}
/**
* Formatiert ein DateTime nur als Datum.
*
* @param DateTimeInterface $dateTime Das zu formatierende DateTime
* @return string
* @throws DateMalformedStringException
*/
public function formatDate(DateTimeInterface $dateTime): string
{
#return $this->format($dateTime, 'Y-m-d');
return $this->format($dateTime, 'd.m.Y');
}
/**
* Formatiert ein DateTime nur als Zeit.
*
* @param DateTimeInterface $dateTime Das zu formatierende DateTime
* @return string
* @throws DateMalformedStringException
*/
public function formatTime(DateTimeInterface $dateTime): string
{
return $this->format($dateTime, 'H:i:s');
}
/**
* Formatiert ein DateTime in einem benutzerdefinierten Format.
*
* @param DateTimeInterface $dateTime Das zu formatierende DateTime
* @param string $format Das Format-Pattern
* @return string
* @throws DateMalformedStringException
*/
public function format(DateTimeInterface $dateTime, string $format): string
{
// Stelle sicher, dass die Zeitzone korrekt ist
$dateInCorrectTimezone = $this->ensureTimezone($dateTime);
return $dateInCorrectTimezone->format($format);
}
/**
* Stellt sicher, dass ein DateTime die korrekte Zeitzone hat.
*
* @param DateTimeInterface $dateTime Das zu überprüfende DateTime
* @return DateTimeImmutable
* @throws DateMalformedStringException
*/
private function ensureTimezone(DateTimeInterface $dateTime): DateTimeImmutable
{
if ($dateTime->getTimezone()->getName() !== $this->timezone->getName()) {
if ($dateTime instanceof DateTimeImmutable) {
return $dateTime->setTimezone($this->timezone);
}
// Konvertiere DateTime zu DateTimeImmutable mit der richtigen Zeitzone
return new DateTimeImmutable('@' . $dateTime->getTimestamp())
->setTimezone($this->timezone);
}
if ($dateTime instanceof DateTimeImmutable) {
return $dateTime;
}
// Konvertiere DateTime zu DateTimeImmutable
return DateTimeImmutable::createFromInterface($dateTime);
}
/**
* Gibt die verwendete Zeitzone zurück.
*
* @return DateTimeZone
*/
public function getTimezone(): DateTimeZone
{
return $this->timezone;
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime\Exceptions;
use Exception;
class InvalidDateTimeException extends Exception
{
public function __construct(string $dateTime, ?string $format = null, ?\Throwable $previous = null)
{
$message = $format !== null
? sprintf('Konnte DateTime nicht aus "%s" mit Format "%s" erstellen', $dateTime, $format)
: sprintf('Ungültiges DateTime-Format: "%s"', $dateTime);
parent::__construct($message, 0, $previous);
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime\Exceptions;
use Exception;
class InvalidTimezoneException extends Exception
{
public function __construct(string $timezone, ?\Throwable $previous = null)
{
parent::__construct(
sprintf('Ungültige Zeitzone: "%s"', $timezone),
0,
$previous
);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace App\Framework\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateInvalidOperationException;
use DateTimeImmutable;
use DateTimeZone;
final class FrozenClock implements Clock
{
private DateTimeImmutable $frozenTime;
private DateTimeZone $timezone;
/**
* @param DateTimeImmutable|string|null $time Die fixierte Zeit (Standard: aktuelle Zeit über SystemClock)
* @param DateTimeZone|string|null $timezone Die zu verwendende Zeitzone (Standard: UTC)
* @throws InvalidDateTimeException wenn die Zeit ungültig ist
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public function __construct(
DateTimeImmutable|string|null $time = null,
DateTimeZone|string|null $timezone = null
) {
$this->timezone = $timezone !== null
? DateTime::createTimezone($timezone)
: DateTime::getDefaultTimezone();
if ($time === null) {
// Verwende SystemClock für die initiale Zeit
$systemClock = new SystemClock($this->timezone);
$this->frozenTime = $systemClock->now();
} elseif (is_string($time)) {
$this->frozenTime = DateTime::fromString($time, $this->timezone);
} else {
$this->frozenTime = DateTime::fromDateTime($time, $this->timezone);
}
}
/**
* {@inheritDoc}
*/
public function now(): DateTimeImmutable
{
return $this->frozenTime;
}
/**
* {@inheritDoc}
* @throws InvalidTimezoneException
*/
public function fromTimestamp(int $timestamp): DateTimeImmutable
{
return DateTime::fromTimestamp($timestamp, $this->timezone);
}
/**
* {@inheritDoc}
*/
public function fromString(string $dateTime, ?string $format = null): DateTimeImmutable
{
if ($format === null) {
return DateTime::fromString($dateTime, $this->timezone);
}
return DateTime::fromFormat($dateTime, $format, $this->timezone);
}
/**
* Erstellt ein Today-Datum (00:00:00) basierend auf der gefrorenen Zeit.
*
* @return DateTimeImmutable
*/
public function today(): DateTimeImmutable
{
return $this->frozenTime->setTime(0, 0, 0);
}
/**
* Erstellt ein Tomorrow-Datum (00:00:00) basierend auf der gefrorenen Zeit.
*
* @return DateTimeImmutable
*/
public function tomorrow(): DateTimeImmutable
{
return $this->frozenTime->modify('+1 day')->setTime(0, 0, 0);
}
/**
* Erstellt ein Yesterday-Datum (00:00:00) basierend auf der gefrorenen Zeit.
*
* @return DateTimeImmutable
*/
public function yesterday(): DateTimeImmutable
{
return $this->frozenTime->modify('-1 day')->setTime(0, 0, 0);
}
/**
* Setzt die fixierte Zeit auf einen neuen Wert.
*
* @param DateTimeImmutable|string $time Die neue fixierte Zeit
* @return self
* @throws InvalidDateTimeException|InvalidTimezoneException wenn die Zeit ungültig ist
*/
public function setTo(DateTimeImmutable|string $time): self
{
if (is_string($time)) {
$this->frozenTime = DateTime::fromString($time, $this->timezone);
} else {
$this->frozenTime = DateTime::fromDateTime($time, $this->timezone);
}
return $this;
}
/**
* Bewegt die fixierte Zeit um eine bestimmte Zeit vorwärts.
*
* @param string $interval Eine DateInterval-kompatible Zeichenfolge (z.B. 'PT1H' für eine Stunde)
* @return self
* @throws InvalidDateTimeException wenn das Interval ungültig ist
*/
public function moveForward(string $interval): self
{
$dateInterval = DateTime::createInterval($interval);
$this->frozenTime = $this->frozenTime->add($dateInterval);
return $this;
}
/**
* Bewegt die fixierte Zeit um eine bestimmte Zeit zurück.
*
* @param string $interval Eine DateInterval-kompatible Zeichenfolge (z.B. 'PT1H' für eine Stunde)
* @return self
* @throws InvalidDateTimeException|DateInvalidOperationException wenn das Interval ungültig ist
*/
public function moveBackward(string $interval): self
{
$dateInterval = DateTime::createInterval($interval);
$this->frozenTime = $this->frozenTime->sub($dateInterval);
return $this;
}
public function time(): int
{
return $this->now()->getTimestamp();
}
public function microtime(bool $asFloat = false): float|array
{
$timestamp = $this->now()->getTimestamp();
$microseconds = 0; // In FrozenClock haben wir keine Mikrosekunden-Präzision
if ($asFloat) {
return (float)$timestamp;
}
return [(float)$timestamp, $microseconds];
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Framework\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateTimeImmutable;
use DateTimeZone;
use function microtime;
use function time;
final readonly class SystemClock implements Clock
{
private DateTimeZone $timezone;
/**
* @param DateTimeZone|string|null $timezone Die zu verwendende Zeitzone (Standard: UTC)
* @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
*/
public function __construct(DateTimeZone|string|null $timezone = null)
{
$this->timezone = $timezone !== null
? DateTime::createTimezone($timezone)
: DateTime::getDefaultTimezone();
}
/**
* {@inheritDoc}
* @throws InvalidDateTimeException
*/
public function now(): DateTimeImmutable
{
try {
return new DateTimeImmutable('now', $this->timezone);
} catch (\Exception $e) {
throw new InvalidDateTimeException('now', null, $e);
}
}
/**
* {@inheritDoc}
* @throws InvalidTimezoneException|InvalidDateTimeException
*/
public function fromTimestamp(int $timestamp): DateTimeImmutable
{
return DateTime::fromTimestamp($timestamp, $this->timezone);
}
/**
* {@inheritDoc}
*/
public function fromString(string $dateTime, ?string $format = null): DateTimeImmutable
{
if ($format === null) {
return DateTime::fromString($dateTime, $this->timezone);
}
return DateTime::fromFormat($dateTime, $format, $this->timezone);
}
/**
* Erstellt ein Today-Datum (00:00:00).
*
* @return DateTimeImmutable
*/
public function today(): DateTimeImmutable
{
return DateTime::fromString('today', $this->timezone);
}
/**
* Erstellt ein Tomorrow-Datum (00:00:00).
*
* @return DateTimeImmutable
*/
public function tomorrow(): DateTimeImmutable
{
return DateTime::fromString('tomorrow', $this->timezone);
}
/**
* Erstellt ein Yesterday-Datum (00:00:00).
*
* @return DateTimeImmutable
*/
public function yesterday(): DateTimeImmutable
{
return DateTime::fromString('yesterday', $this->timezone);
}
public function time(): int
{
return time();
}
public function microtime(bool $asFloat = false): float|array
{
return microtime($asFloat);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime;
use function sleep;
use function usleep;
final readonly class SystemTimer implements Timer
{
public function sleep(int $seconds): void
{
sleep($seconds);
}
public function usleep(int $microseconds): void
{
usleep($microseconds);
}
public function msleep(int $milliseconds): void
{
usleep($milliseconds * 1000);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Framework\DateTime;
interface Timer
{
/**
* Pausiert die Ausführung für die angegebene Anzahl von Sekunden.
*
* @param int $seconds Anzahl der Sekunden
*/
public function sleep(int $seconds): void;
/**
* Pausiert die Ausführung für die angegebene Anzahl von Mikrosekunden.
*
* @param int $microseconds Anzahl der Mikrosekunden
*/
public function usleep(int $microseconds): void;
/**
* Pausiert die Ausführung für die angegebene Anzahl von Millisekunden.
*
* @param int $milliseconds Anzahl der Millisekunden
*/
public function msleep(int $milliseconds): void;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Framework\DateTime;
enum Timezone: string
{
case UTC = 'UTC';
case EuropeBerlin = 'Europe/Berlin';
}