Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.50% covered (success)
92.50%
37 / 40
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
DateTime
92.50% covered (success)
92.50%
37 / 40
66.67% covered (warning)
66.67%
6 / 9
22.20
0.00% covered (danger)
0.00%
0 / 1
 __construct
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 raw
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 format
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 localize
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 json
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 parse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseFormat
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
6.03
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Value;
6
7use Cosray\DateTime\Codec;
8use Cosray\Exception\RuntimeException;
9use Cosray\Field\Field;
10use Cosray\Field\Owner;
11use DateTimeImmutable;
12use DateTimeZone;
13use IntlDateFormatter;
14use Override;
15
16class DateTime extends Value
17{
18    public const FORMAT = Codec::STORAGE_FORMAT;
19
20    public readonly ?DateTimeImmutable $datetime;
21    public readonly DateTimeZone $timezone;
22
23    public function __construct(Owner $owner, Field $field, ValueContext $context)
24    {
25        parent::__construct($owner, $field, $context);
26
27        $timezone = $this->meta('timezone', 'UTC');
28        $this->timezone = Codec::timezone($timezone)
29            ?? throw new RuntimeException("Invalid timezone for field '{$this->fieldName}'");
30
31        $value = $this->value();
32
33        if (!is_string($value) || $value === '') {
34            $this->datetime = null;
35
36            return;
37        }
38
39        $this->datetime = $this->parse($value)
40            ?? throw new RuntimeException("Invalid date/time in field '{$this->fieldName}'");
41    }
42
43    #[Override]
44    public function __toString(): string
45    {
46        return $this->datetime === null ? '' : Codec::format($this->datetime);
47    }
48
49    #[Override]
50    public function isset(): bool
51    {
52        return isset($this->datetime) ? true : false;
53    }
54
55    #[Override]
56    public function raw(): ?DateTimeImmutable
57    {
58        return $this->datetime;
59    }
60
61    public function format(string $format): string
62    {
63        if ($this->datetime) {
64            return $this->datetime->format($format);
65        }
66
67        return '';
68    }
69
70    public function localize(
71        int $dateFormat = IntlDateFormatter::MEDIUM,
72        int $timeFormat = IntlDateFormatter::MEDIUM,
73    ): string {
74        if ($this->datetime) {
75            $formatter = new IntlDateFormatter(
76                $this->locale->id,
77                $dateFormat,
78                $timeFormat,
79                $this->timezone,
80            );
81            $formatted = $formatter->format($this->datetime->getTimestamp());
82
83            return $formatted !== false
84                ? $formatted
85                : throw new RuntimeException('Date formatting failed: ' . $formatter->getErrorMessage());
86        }
87
88        return '';
89    }
90
91    /** Dates as `YYYY-MM-DD`, times as `HH:MM`, date-times in UTC RFC 3339. */
92    #[Override]
93    public function json(): ?string
94    {
95        return $this->datetime === null ? null : $this->__toString();
96    }
97
98    protected function parse(string $value): ?DateTimeImmutable
99    {
100        return Codec::parse($value)?->setTimezone($this->timezone);
101    }
102
103    protected function parseFormat(string $value, string $format): ?DateTimeImmutable
104    {
105        $date = DateTimeImmutable::createFromFormat('!' . $format, $value, $this->timezone);
106        $errors = DateTimeImmutable::getLastErrors();
107
108        if (
109            $date === false
110            || $errors !== false
111            && (
112                $errors['warning_count'] > 0
113                || $errors['error_count'] > 0
114            )
115            || $date->format($format) !== $value
116        ) {
117            return null;
118        }
119
120        return $date;
121    }
122}