Skip to content

Commit 1feea15

Browse files
committed
Header\Date no longer extends DateTime
1 parent cdc6395 commit 1feea15

File tree

7 files changed

+131
-112
lines changed

7 files changed

+131
-112
lines changed

lib/Headers/Date.php

+45-31
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,72 @@
22

33
namespace ICanBoogie\HTTP\Headers;
44

5+
use DateTimeImmutable;
56
use DateTimeInterface;
67
use DateTimeZone;
7-
use ICanBoogie\DateTime;
8-
9-
use function is_numeric;
108

119
/**
12-
* A date time object that renders into a string formatted for HTTP header fields.
10+
* Representation of a 'Date' header field.
11+
*
12+
* @property-read bool $is_empty
13+
* Whether the value of the {@see Date} is empty.
14+
* @property-read int|null $timestamp
15+
* The Unix timestamp in seconds, or null if {@see Date} is empty.
1316
*
1417
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
1518
*/
16-
class Date extends DateTime
19+
readonly class Date
1720
{
1821
public static function from(
19-
self|\DateTimeInterface|string|null $source,
20-
DateTimeZone|string|null $timezone = null
21-
): static {
22+
DateTimeInterface|int|string|null $source
23+
): self {
24+
$timezone = null;
25+
2226
if ($source === null) {
23-
return static::none();
27+
return new self();
28+
} elseif ($source instanceof DateTimeInterface) {
29+
$timezone = $source->getTimezone();
30+
$source = $source->format('Y-m-d\TH:i:s.u');
31+
} elseif (is_int($source)) {
32+
$timezone = 'UTC';
33+
$source = "@{$source}";
2434
}
2535

26-
return parent::from($source, $timezone);
27-
}
28-
29-
/**
30-
* @param string|int|DateTimeInterface $time If time is provided as a numeric value it is used
31-
* as
32-
* "@{$time}" and the time zone is set to UTC.
33-
* @param DateTimeZone|string $timezone A {@link \DateTimeZone} object representing the desired
34-
* time zone. If the time zone is empty `utc` is used instead.
35-
*/
36-
public function __construct($time = 'now', $timezone = null)
37-
{
38-
if ($time instanceof DateTimeInterface) {
39-
$time = $time->getTimestamp();
36+
if (is_string($timezone)) {
37+
$timezone = new DateTimeZone($timezone);
4038
}
4139

42-
if (is_numeric($time)) {
43-
$time = '@' . $time;
44-
$timezone = null;
45-
}
40+
$datetime = new DateTimeImmutable($source, $timezone);
4641

47-
parent::__construct($time, $timezone ?: 'utc');
42+
return new self($datetime);
43+
}
44+
45+
private function __construct(
46+
public ?DateTimeInterface $delegate = null
47+
) {
4848
}
4949

5050
/**
5151
* Formats the instance according to the RFC 1123.
52-
*
53-
* @inheritdoc
5452
*/
5553
public function __toString(): string
5654
{
57-
return $this->is_empty ? '' : $this->utc->as_rfc1123;
55+
return $this->is_empty
56+
? ''
57+
: str_replace('+0000', 'GMT', $this->delegate->format(DateTimeInterface::RFC1123));
58+
}
59+
60+
/**
61+
* The timestamp of a {@see \DateTime} or {@see DateTimeImmutable} created with "0000-00-00".
62+
*/
63+
private const EMPTY_TIMESTAMP = -62169984000;
64+
65+
public function __get($property)
66+
{
67+
return match ($property) {
68+
'is_empty' => $this->delegate === null || $this->timestamp == self::EMPTY_TIMESTAMP,
69+
'timestamp' => $this->delegate?->getTimestamp(),
70+
default => throw new \BadMethodCallException('Undefined property: ' . get_class($this) . '::' . $property),
71+
};
5872
}
5973
}

lib/Response.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ protected function get_age(): ?int
289289
}
290290

291291
if (!$this->headers->date->is_empty) {
292-
return max(0, time() - $this->headers->date->utc->timestamp);
292+
return max(0, time() - $this->headers->date->timestamp);
293293
}
294294

295295
return null;

tests/FileResponseTest.php

+10-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Test\ICanBoogie\HTTP;
44

5+
use DateTimeInterface;
56
use ICanBoogie\DateTime;
67
use ICanBoogie\HTTP\FileResponse;
78
use ICanBoogie\HTTP\Headers;
@@ -230,10 +231,16 @@ public static function provide_test_get_etag(): array
230231
}
231232

232233
#[DataProvider('provide_test_get_expires')]
233-
public function test_get_expires(DateTime $expected, string $file, array $options = [], array $headers = []): void
234-
{
234+
public function test_get_expires(
235+
DateTimeInterface $expected,
236+
string $file,
237+
array $options = [],
238+
array $headers = [],
239+
): void {
235240
$response = new FileResponse($file, Request::from(), $options, $headers);
236-
$this->assertGreaterThanOrEqual($expected->utc->format('YmdHi'), $response->expires->utc->format('YmdHi'));
241+
$actual = $response->expires->delegate;
242+
243+
$this->assertGreaterThanOrEqual($expected, $actual);
237244
}
238245

239246
public static function provide_test_get_expires(): array

tests/Headers/DateHeaderTest.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the ICanBoogie package.
5+
*
6+
* (c) Olivier Laviale <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Test\ICanBoogie\HTTP\Headers;
13+
14+
use ICanBoogie\DateTime;
15+
use ICanBoogie\HTTP\Headers\Date;
16+
use ICanBoogie\HTTP\Headers\Date as DateHeader;
17+
use PHPUnit\Framework\Attributes\DataProvider;
18+
use PHPUnit\Framework\TestCase;
19+
20+
class DateHeaderTest extends TestCase
21+
{
22+
public function test_from_datetime(): void
23+
{
24+
$datetime = new \DateTime();
25+
$sut = DateHeader::from($datetime);
26+
$datetime->setTimezone(new \DateTimeZone('GMT'));
27+
28+
$this->assertEquals($datetime->format('D, d M Y H:i:s') . ' GMT', (string) $sut);
29+
}
30+
31+
public function test_from_string(): void
32+
{
33+
$datetime = new \DateTime('now', new \DateTimeZone('GMT'));
34+
35+
$this->assertEquals(
36+
$datetime->format('D, d M Y H:i:s') . ' GMT',
37+
(string) DateHeader::from($datetime->format('D, d M Y H:i:s P'))
38+
);
39+
40+
$this->assertEquals(
41+
$datetime->format('D, d M Y H:i:s') . ' GMT',
42+
(string) DateHeader::from($datetime->format('D, d M Y H:i:s'))
43+
);
44+
45+
$this->assertEquals(
46+
$datetime->format('D, d M Y H:i:s') . ' GMT',
47+
(string) DateHeader::from($datetime->format('Y-m-d H:i:s'))
48+
);
49+
}
50+
51+
#[DataProvider('provide_test_to_string')]
52+
public function test_to_string($expected, $datetime)
53+
{
54+
$field = Date::from($datetime);
55+
56+
$this->assertEquals($expected, (string) $field);
57+
}
58+
59+
public static function provide_test_to_string(): array
60+
{
61+
$now = DateTime::now();
62+
63+
return [
64+
65+
[ $now->as_rfc1123, $now ],
66+
[ '', DateTime::none() ],
67+
[ '', null ]
68+
69+
];
70+
}
71+
}

tests/Headers/DateTimeHeaderTest.php

-41
This file was deleted.

tests/HeadersTest.php

+3-35
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,6 @@
1616

1717
final class HeadersTest extends TestCase
1818
{
19-
public function testDateTimeFromDateTime(): void
20-
{
21-
$datetime = new \DateTime();
22-
$headers_datetime = new DateHeader($datetime);
23-
$datetime->setTimezone(new DateTimeZone('GMT'));
24-
25-
$this->assertEquals($datetime->format('D, d M Y H:i:s') . ' GMT', (string) $headers_datetime);
26-
}
27-
28-
/**
29-
* @throws Exception
30-
*/
31-
public function testDateTimeFromDateTimeString(): void
32-
{
33-
$datetime = new \DateTime('now', new DateTimeZone('GMT'));
34-
35-
$this->assertEquals(
36-
$datetime->format('D, d M Y H:i:s') . ' GMT',
37-
(string) new DateHeader($datetime->format('D, d M Y H:i:s P'))
38-
);
39-
40-
$this->assertEquals(
41-
$datetime->format('D, d M Y H:i:s') . ' GMT',
42-
(string) new DateHeader($datetime->format('D, d M Y H:i:s'))
43-
);
44-
45-
$this->assertEquals(
46-
$datetime->format('D, d M Y H:i:s') . ' GMT',
47-
(string) new DateHeader($datetime->format('Y-m-d H:i:s'))
48-
);
49-
}
50-
5119
public function test_cache_control(): void
5220
{
5321
$headers = new Headers();
@@ -105,7 +73,7 @@ public function test_date(): void
10573
$now = new DateTime();
10674
$headers->date = $now;
10775
$this->assertInstanceOf(Headers\Date::class, $headers->date);
108-
$this->assertEquals($now, $headers->date);
76+
$this->assertEquals($now, $headers->date->delegate);
10977
}
11078

11179
public function test_etag(): void
@@ -129,7 +97,7 @@ public function test_last_modified(): void
12997

13098
$value = DateTime::now();
13199
$headers->last_modified = $value;
132-
$this->assertEquals($value, $headers->last_modified);
100+
$this->assertEquals($value->as_rfc1123, (string) $headers->last_modified);
133101

134102
$headers->last_modified = null;
135103
$this->assertEmpty((string) $headers->last_modified);
@@ -152,7 +120,7 @@ public function test_retry_after(): void
152120

153121
$value = DateTime::now();
154122
$headers->retry_after = $value;
155-
$this->assertEquals($value, $headers->retry_after);
123+
$this->assertEquals($value->as_rfc1123, (string) $headers->retry_after);
156124
}
157125

158126
#[DataProvider('provide_test_date_header')]

tests/ResponseTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function test_expires(): void
7878

7979
$value = new DateTime('+1 days');
8080
$response->expires = $value;
81-
$this->assertEquals($value->as_iso8601, $response->expires->as_iso8601);
81+
$this->assertEquals($value, $response->expires->delegate);
8282
$this->assertSame(86400, $response->headers->cache_control->max_age);
8383

8484
$response->expires = null;

0 commit comments

Comments
 (0)