Skip to content

Commit c8a5173

Browse files
committed
Introduce DateTimeTimestamp type with strict Unix timestamp validation and comprehensive tests
- Added `DateTimeTimestamp` class for immutable Unix timestamp (UTC) representation. - Extended `DateTimeType` with Unix timestamp-specific format and utilities. - Implemented rigorous unit tests for parsing, formatting, and error scenarios. - Updated documentation (`USAGE.md`) with examples showcasing `DateTimeTimestamp` usage. - Enhanced `psalmTest.php` with additional `DateTimeTimestamp` demonstrations.
1 parent 4feec32 commit c8a5173

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

docs/USAGE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Floats (PhpTypedValues\Float):
3535
DateTime (PhpTypedValues\DateTime):
3636

3737
- DateTimeAtom — immutable DateTime in RFC3339 (ATOM) format
38+
- DateTimeTimestamp — immutable DateTime represented as Unix timestamp (seconds since epoch, UTC)
3839

3940
Static usage examples
4041
---------------------
@@ -49,6 +50,7 @@ use PhpTypedValues\String\NonEmptyStr;
4950
use PhpTypedValues\Float\FloatBasic;
5051
use PhpTypedValues\Float\NonNegativeFloat;
5152
use PhpTypedValues\DateTime\DateTimeAtom;
53+
use PhpTypedValues\DateTime\DateTimeTimestamp;
5254

5355
// Integers
5456
$any = IntegerBasic::fromInt(-10);
@@ -71,11 +73,15 @@ $ratio = NonNegativeFloat::fromFloat(0.5); // >= 0
7173
// DateTime (RFC 3339 / ATOM)
7274
$dt = DateTimeAtom::fromString('2025-01-02T03:04:05+00:00');
7375

76+
// DateTime (Unix timestamp, seconds)
77+
$unix = DateTimeTimestamp::fromString('1735787045');
78+
7479
// Accessing value and string form
7580
$posValue = $pos->value(); // 1 (int)
7681
$wdText = $wd->toString(); // "7"
7782
$priceStr = $price->toString(); // "19.99"
7883
$isoText = $dt->toString(); // "2025-01-02T03:04:05+00:00"
84+
$unixText = $unix->toString(); // e.g. "1735787045"
7985
```
8086

8187
Validation errors (static constructors)

src/DateTime/DateTimeTimestamp.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpTypedValues\DateTime;
6+
7+
use DateTimeImmutable;
8+
use DateTimeZone;
9+
use PhpTypedValues\Code\DateTime\DateTimeType;
10+
use PhpTypedValues\Code\Exception\DateTimeTypeException;
11+
12+
/**
13+
* Unix timestamp (seconds since Unix epoch, UTC).
14+
*
15+
* @psalm-immutable
16+
*/
17+
readonly class DateTimeTimestamp extends DateTimeType
18+
{
19+
/**
20+
* DateTime::format() pattern for Unix timestamp.
21+
*
22+
* @see https://www.php.net/manual/en/datetime.format.php
23+
*/
24+
protected const FORMAT = 'U';
25+
26+
/**
27+
* Parse from a numeric Unix timestamp string (seconds).
28+
*
29+
* @throws DateTimeTypeException
30+
*/
31+
public static function fromString(string $value): self
32+
{
33+
return new self(
34+
self::createFromFormat(
35+
$value,
36+
static::FORMAT,
37+
new DateTimeZone(self::ZONE)
38+
)
39+
);
40+
}
41+
42+
public function toString(): string
43+
{
44+
return $this->value()->format(self::FORMAT);
45+
}
46+
47+
public static function fromDateTime(DateTimeImmutable $value): self
48+
{
49+
return new self($value);
50+
}
51+
}

src/psalmTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use PhpTypedValues\DateTime\DateTimeAtom;
1212
use PhpTypedValues\DateTime\DateTimeRFC3339;
13+
use PhpTypedValues\DateTime\DateTimeTimestamp;
1314
use PhpTypedValues\Float\FloatBasic;
1415
use PhpTypedValues\Float\NonNegativeFloat;
1516
use PhpTypedValues\Integer\IntegerBasic;
@@ -59,6 +60,10 @@
5960
$dt = DateTimeRFC3339::fromString('2025-01-02T03:04:05+00:00')->value();
6061
echo DateTimeRFC3339::fromDateTime($dt)->toString() . \PHP_EOL;
6162

63+
// Timestamp
64+
$tsVo = DateTimeTimestamp::fromString('1735787045');
65+
echo DateTimeTimestamp::fromDateTime($tsVo->value())->toString() . \PHP_EOL;
66+
6267
/**
6368
* Artificial functions.
6469
*/
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpTypedValues\DateTime\DateTimeTimestamp;
6+
7+
it('fromDateTime returns same instant and toString is unix timestamp', function (): void {
8+
$dt = new DateTimeImmutable('2001-09-09 01:46:40');
9+
$vo = DateTimeTimestamp::fromString('1000000000');
10+
11+
expect($vo->value()->format('U'))->toBe($dt->format('U'))
12+
->and($vo->toString())->toBe($dt->format('U'));
13+
});
14+
15+
it('fromDateTime returns same instant and toString is ISO 8601', function (): void {
16+
$dt = new DateTimeImmutable('2025-01-02T03:04:05+00:00');
17+
$vo = DateTimeTimestamp::fromDateTime($dt);
18+
19+
expect($vo->value()->format('U'))->toBe('1735787045')
20+
->and($vo->toString())->toBe('1735787045');
21+
});
22+
23+
it('getFormat returns unix timestamp format U', function (): void {
24+
expect(DateTimeTimestamp::getFormat())->toBe('U');
25+
});
26+
27+
it('fromString throws on non-numeric input and reports details', function (): void {
28+
try {
29+
DateTimeTimestamp::fromString('not-a-number');
30+
expect()->fail('Exception was not thrown');
31+
} catch (Throwable $e) {
32+
$msg = $e->getMessage();
33+
expect($e)->toBeInstanceOf(PhpTypedValues\Code\Exception\DateTimeTypeException::class)
34+
->and($msg)->toContain('Invalid date time value')
35+
->and((bool) preg_match('/(Error at|Warning at)/', $msg))->toBeTrue();
36+
}
37+
});
38+
39+
it('fromString throws on trailing data (warnings/errors path)', function (): void {
40+
try {
41+
DateTimeTimestamp::fromString('1000000000 ');
42+
expect()->fail('Exception was not thrown');
43+
} catch (Throwable $e) {
44+
$msg = $e->getMessage();
45+
expect($e)->toBeInstanceOf(PhpTypedValues\Code\Exception\DateTimeTypeException::class)
46+
->and($msg)->toContain('Invalid date time value')
47+
->and((bool) preg_match('/(Error at|Warning at)/', $msg))->toBeTrue();
48+
}
49+
});
50+
51+
it('round-trip mismatch produces Unexpected conversion error for leading zeros', function (): void {
52+
try {
53+
DateTimeTimestamp::fromString('0000000005');
54+
expect()->fail('Exception was not thrown');
55+
} catch (Throwable $e) {
56+
expect($e)->toBeInstanceOf(PhpTypedValues\Code\Exception\DateTimeTypeException::class)
57+
->and($e->getMessage())->toContain('Unexpected conversion')
58+
->and($e->getMessage())->toContain('0000000005')
59+
->and($e->getMessage())->toContain('5');
60+
}
61+
});

0 commit comments

Comments
 (0)