From 81ba660c5fb8b3470f2a3b81b34370af492c867b Mon Sep 17 00:00:00 2001 From: Simon Hatt Date: Tue, 18 Aug 2026 14:35:53 +0200 Subject: [PATCH] Refactor date validation --- server/lib/PhpStan/IsoDate.php | 7 ++++--- server/lib/PhpStan/IsoDateTime.php | 7 ++++--- server/lib/PhpStan/IsoTime.php | 7 ++++--- server/tests/UnitTests/PhpStan/IsoDateTest.php | 8 ++------ server/tests/UnitTests/PhpStan/IsoDateTimeTest.php | 8 ++------ server/tests/UnitTests/PhpStan/IsoTimeTest.php | 8 ++------ 6 files changed, 18 insertions(+), 27 deletions(-) diff --git a/server/lib/PhpStan/IsoDate.php b/server/lib/PhpStan/IsoDate.php index 24581c6..1bb4511 100644 --- a/server/lib/PhpStan/IsoDate.php +++ b/server/lib/PhpStan/IsoDate.php @@ -14,10 +14,11 @@ public static function fromWire(mixed $data): IsoDate { if (!is_string($data)) { throw new \InvalidArgumentException("IsoDate must be string"); } - if (!preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $data)) { - throw new \InvalidArgumentException("IsoDate must be Y-m-d"); + $date_time = \DateTime::createFromFormat('Y-m-d', $data); + if ($date_time === false || $date_time->format('Y-m-d') !== $data) { + throw new \InvalidArgumentException("IsoDate must be valid Y-m-d format, got: {$data}"); } - return new IsoDate($data); + return new IsoDate($date_time->format('Y-m-d')); } public static function fromDateTime(?\DateTimeInterface $date_time): ?IsoDate { diff --git a/server/lib/PhpStan/IsoDateTime.php b/server/lib/PhpStan/IsoDateTime.php index 655410c..06ffa36 100644 --- a/server/lib/PhpStan/IsoDateTime.php +++ b/server/lib/PhpStan/IsoDateTime.php @@ -14,10 +14,11 @@ public static function fromWire(mixed $data): IsoDateTime { if (!is_string($data)) { throw new \InvalidArgumentException("IsoDateTime must be string"); } - if (!preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $data)) { - throw new \InvalidArgumentException("IsoDateTime must be Y-m-d H:i:s"); + $date_time = \DateTime::createFromFormat('Y-m-d H:i:s', $data); + if ($date_time === false || $date_time->format('Y-m-d H:i:s') !== $data) { + throw new \InvalidArgumentException("IsoDateTime must be valid Y-m-d H:i:s format, got: {$data}"); } - return new IsoDateTime($data); + return new IsoDateTime($date_time->format('Y-m-d H:i:s')); } public static function fromDateTime(?\DateTimeInterface $date_time): ?IsoDateTime { diff --git a/server/lib/PhpStan/IsoTime.php b/server/lib/PhpStan/IsoTime.php index 6c70e20..753319c 100644 --- a/server/lib/PhpStan/IsoTime.php +++ b/server/lib/PhpStan/IsoTime.php @@ -14,10 +14,11 @@ public static function fromWire(mixed $data): IsoTime { if (!is_string($data)) { throw new \InvalidArgumentException("IsoTime must be string"); } - if (!preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $data)) { - throw new \InvalidArgumentException("IsoTime must be H:i:s"); + $date_time = \DateTime::createFromFormat('H:i:s', $data); + if ($date_time === false || $date_time->format('H:i:s') !== $data) { + throw new \InvalidArgumentException("IsoTime must be valid H:i:s format, got: {$data}"); } - return new IsoTime($data); + return new IsoTime($date_time->format('H:i:s')); } public static function fromDateTime(?\DateTimeInterface $date_time): ?IsoTime { diff --git a/server/tests/UnitTests/PhpStan/IsoDateTest.php b/server/tests/UnitTests/PhpStan/IsoDateTest.php index 8fca003..029dc46 100644 --- a/server/tests/UnitTests/PhpStan/IsoDateTest.php +++ b/server/tests/UnitTests/PhpStan/IsoDateTest.php @@ -39,7 +39,7 @@ public function testDeserializeMalformedIsoDate(): void { IsoDate::fromWire('malformed'); $this->fail('Error expected'); } catch (\Throwable $th) { - $this->assertSame('IsoDate must be Y-m-d', $th->getMessage()); + $this->assertSame('IsoDate must be valid Y-m-d format, got: malformed', $th->getMessage()); } } @@ -48,11 +48,7 @@ public function testDeserializeInvalidIsoDate(): void { IsoDate::fromWire('2024-99-99'); $this->fail('Error expected'); } catch (\Throwable $th) { - if (\PHP_VERSION_ID < 80300) { - $this->assertSame(\Exception::class, get_class($th)); - } else { - $this->assertSame(\DateMalformedStringException::class, get_class($th)); - } + $this->assertSame(\InvalidArgumentException::class, get_class($th)); } } diff --git a/server/tests/UnitTests/PhpStan/IsoDateTimeTest.php b/server/tests/UnitTests/PhpStan/IsoDateTimeTest.php index d21bab2..6694cd3 100644 --- a/server/tests/UnitTests/PhpStan/IsoDateTimeTest.php +++ b/server/tests/UnitTests/PhpStan/IsoDateTimeTest.php @@ -39,7 +39,7 @@ public function testDeserializeMalformedIsoDateTime(): void { IsoDateTime::fromWire('malformed'); $this->fail('Error expected'); } catch (\Throwable $th) { - $this->assertSame('IsoDateTime must be Y-m-d H:i:s', $th->getMessage()); + $this->assertSame('IsoDateTime must be valid Y-m-d H:i:s format, got: malformed', $th->getMessage()); } } @@ -48,11 +48,7 @@ public function testDeserializeInvalidIsoDateTime(): void { IsoDateTime::fromWire('2024-99-99 99:99:99'); $this->fail('Error expected'); } catch (\Throwable $th) { - if (\PHP_VERSION_ID < 80300) { - $this->assertSame(\Exception::class, get_class($th)); - } else { - $this->assertSame(\DateMalformedStringException::class, get_class($th)); - } + $this->assertSame(\InvalidArgumentException::class, get_class($th)); } } diff --git a/server/tests/UnitTests/PhpStan/IsoTimeTest.php b/server/tests/UnitTests/PhpStan/IsoTimeTest.php index 48d060e..0b9e1a4 100644 --- a/server/tests/UnitTests/PhpStan/IsoTimeTest.php +++ b/server/tests/UnitTests/PhpStan/IsoTimeTest.php @@ -39,7 +39,7 @@ public function testDeserializeMalformedIsoTime(): void { IsoTime::fromWire('malformed'); $this->fail('Error expected'); } catch (\Throwable $th) { - $this->assertSame('IsoTime must be H:i:s', $th->getMessage()); + $this->assertSame('IsoTime must be valid H:i:s format, got: malformed', $th->getMessage()); } } @@ -48,11 +48,7 @@ public function testDeserializeInvalidIsoTime(): void { IsoTime::fromWire('99:99:99'); $this->fail('Error expected'); } catch (\Throwable $th) { - if (\PHP_VERSION_ID < 80300) { - $this->assertSame(\Exception::class, get_class($th)); - } else { - $this->assertSame(\DateMalformedStringException::class, get_class($th)); - } + $this->assertSame(\InvalidArgumentException::class, get_class($th)); } }