Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
ParserDateTime
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
12 / 12
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getLocale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTimeZone
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTimeZone
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDateType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDateType
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 getTimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTimeType
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getCalendarType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCalendarType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseValue
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 setMsgContent
n/a
0 / 0
n/a
0 / 0
0
1<?php
2/**
3 * @author: Doug Wilbourne (dougwilbourne@gmail.com)
4 */
5
6declare(strict_types=1);
7
8namespace pvc\parser\date_time;
9
10use DateTimeZone;
11use IntlDateFormatter;
12use pvc\interfaces\intl\LocaleInterface;
13use pvc\interfaces\msg\MsgInterface;
14use pvc\parser\err\InvalidDateTimeTypeException;
15use pvc\parser\Parser;
16
17/**
18 * Class ParserDateTime
19 * @extends Parser<float>
20 *
21 * wrapper for the IntlDateFormatter class.
22 * parsedValue is a timestamp.  This class has no notion of whether the pattern in IntlDateFormatter permits
23 * fractions of a second or not so the data type for the parsedValue (e.g. the generic data type for the class)
24 * is float, not int.
25 *
26 * The locale and timezone attributes look like they might be redundant because IntlDateFormatter also
27 * has those attributes.  However, in order to keep everything pvc-branded (encapsulated), this object
28 * has properties which are the pvc implementations of these concepts.  The IntlDateFormatter returns a string
29 * from getLocale and an IntlTimeZone object from getTimeZone, so the attributes in this class are just object instances
30 * of the correct type which can be returned from the getters.
31 */
32abstract class ParserDateTime extends Parser
33{
34
35    protected LocaleInterface $locale;
36
37    protected DateTimeZone $timeZone;
38
39    protected int $dateType;
40
41    protected int $timeType;
42
43    protected int $calendarType;
44
45    public function __construct(
46        MsgInterface $msg,
47        LocaleInterface $locale,
48        DateTimeZone $timeZone,
49    ) {
50        parent::__construct($msg);
51        $this->setLocale($locale);
52        $this->setTimeZone($timeZone);
53        $this->setDateType(IntlDateFormatter::NONE);
54        $this->setTimeType(IntlDateFormatter::NONE);
55        $this->setCalendarType(IntlDateFormatter::GREGORIAN);
56    }
57
58    public function getLocale(): LocaleInterface
59    {
60        return $this->locale;
61    }
62
63    public function setLocale(LocaleInterface $locale): void
64    {
65        $this->locale = $locale;
66    }
67
68    public function getTimeZone(): DateTimeZone
69    {
70        return $this->timeZone;
71    }
72
73    public function setTimeZone(DateTimeZone $timeZone): void
74    {
75        $this->timeZone = $timeZone;
76    }
77
78    public function getDateType(): int
79    {
80        return $this->dateType;
81    }
82
83    public function setDateType(int $dateType): void
84    {
85        $validDateTypes = [
86            IntlDateFormatter::NONE,
87            IntlDateFormatter::FULL,
88            IntlDateFormatter::LONG,
89            IntlDateFormatter::MEDIUM,
90            IntlDateFormatter::SHORT,
91            IntlDateFormatter::RELATIVE_FULL,
92            IntlDateFormatter::RELATIVE_LONG,
93            IntlDateFormatter::RELATIVE_MEDIUM,
94            IntlDateFormatter::RELATIVE_SHORT,
95        ];
96        if (!in_array($dateType, $validDateTypes)) {
97            throw new InvalidDateTimeTypeException();
98        }
99        $this->dateType = $dateType;
100    }
101
102    public function getTimeType(): int
103    {
104        return $this->timeType;
105    }
106
107    public function setTimeType(int $timeType): void
108    {
109        $validTimeTypes = [
110            IntlDateFormatter::NONE,
111            IntlDateFormatter::FULL,
112            IntlDateFormatter::LONG,
113            IntlDateFormatter::MEDIUM,
114            IntlDateFormatter::SHORT,
115        ];
116        if (!in_array($timeType, $validTimeTypes)) {
117            throw new InvalidDateTimeTypeException();
118        }
119        $this->timeType = $timeType;
120    }
121
122    public function getCalendarType(): int
123    {
124        return $this->calendarType;
125    }
126
127    public function setCalendarType(int $calendarType): void
128    {
129        $validCalendarTypes = [IntlDateFormatter::GREGORIAN, IntlDateFormatter::TRADITIONAL];
130        if (!in_array($calendarType, $validCalendarTypes)) {
131            throw new InvalidDateTimeTypeException();
132        }
133        $this->calendarType = $calendarType;
134    }
135
136    /**
137     * @function parseValue
138     * @param string $data
139     * @return bool
140     */
141    public function parseValue(string $data): bool
142    {
143        $formatter = new IntlDateFormatter(
144            (string)$this->locale,
145            $this->getDateType(),
146            $this->getTimeType(),
147            $this->getTimeZone(),
148            $this->getCalendarType()
149        );
150        $pos = 0;
151        $expectedPos = strlen($data);
152        $result = $formatter->parse($data, $pos);
153
154        /**
155         * IntlDateFormatter 'fails gracefully' if it successfully parses a part of a string, e.g. it will not throw
156         * an exception if it parses the first x characters of the string into the return type and can't parse any
157         * more from the x + 1 character to the end of the string.  The $pos variable holds the offset of the last
158         * character successfully parsed.
159         *
160         * There is also the possibility that it parses the whole string but the result is false.  I came across an
161         * date time combination where this was the case: try parsing the date / time '5/9/96 23:14' with the en_US
162         * locale.  It expects an am/pm designation and does not permit the hours to be more than 12.  So it gets to
163         * the end of the string but the parse is unsuccessful.
164         *
165         */
166        if (($pos == $expectedPos) && ($result !== false)) {
167            $this->parsedValue = $result;
168            return true;
169        } else {
170            return false;
171        }
172    }
173
174    abstract protected function setMsgContent(MsgInterface $msg): void;
175}