Passed
Pull Request — master (#11)
by Alex
02:52
created

testCreateFromFormatWillCatchAndReThrowException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 24
rs 9.7333
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\DateTime;
6
7
use Arp\DateTime\DateTimeFactory;
8
use Arp\DateTime\DateTimeFactoryInterface;
9
use Arp\DateTime\DateTimeZoneFactoryInterface;
10
use Arp\DateTime\Exception\DateTimeFactoryException;
11
use Arp\DateTime\Exception\DateTimeZoneFactoryException;
12
use PHPUnit\Framework\MockObject\MockObject;
13
use PHPUnit\Framework\TestCase;
14
15
/**
16
 * @covers  \Arp\DateTime\DateTimeFactory
17
 *
18
 * @author  Alex Patterson <[email protected]>
19
 * @package ArpTest\DateTime
20
 */
21
final class DateTimeFactoryTest extends TestCase
22
{
23
    /**
24
     * @var DateTimeZoneFactoryInterface&MockObject
25
     */
26
    private $dateTimeZoneFactory;
27
28
    /**
29
     * Prepare the test case dependencies
30
     */
31
    public function setUp(): void
32
    {
33
        $this->dateTimeZoneFactory = $this->createMock(DateTimeZoneFactoryInterface::class);
34
    }
35
36
    /**
37
     * Ensure that the factory implements DateTimeFactoryInterface
38
     */
39
    public function testImplementsDateTimeFactoryInterface(): void
40
    {
41
        $factory = new DateTimeFactory();
42
43
        $this->assertInstanceOf(DateTimeFactoryInterface::class, $factory);
44
    }
45
46
    /**
47
     * Assert that a DateTimeFactoryException is thrown if trying to create the class without a valid
48
     * $dateTimeClassName constructor argument
49
     *
50
     * @throws DateTimeFactoryException
51
     */
52
    public function testDateTimeFactoryExceptionIsThrownWhenProvidingInvalidDateTimeClassName(): void
53
    {
54
        $dateTimeClassName = \stdClass::class;
55
56
        $this->expectException(DateTimeFactoryException::class);
57
        $this->expectExceptionMessage(
58
            sprintf(
59
                'The \'dateTimeClassName\' parameter must be a class name that implements \'%s\'',
60
                \DateTimeInterface::class
61
            )
62
        );
63
64
        new DateTimeFactory($this->dateTimeZoneFactory, $dateTimeClassName);
65
    }
66
67
    /**
68
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
69
     *
70
     * @param string|null               $spec     The date and time specification.
71
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test.
72
     *
73
     * @dataProvider getCreateDateTimeData
74
     *
75
     * @throws DateTimeFactoryException
76
     * @throws \Exception
77
     */
78
    public function testCreateDateTime(?string $spec, $timeZone = null): void
79
    {
80
        $factory = new DateTimeFactory();
81
82
        $expectedDateTime = new \DateTime(
83
            $spec ?? 'now',
84
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
85
        );
86
87
        $dateTime = $factory->createDateTime($spec, $timeZone);
88
89
        $this->assertDateTime($expectedDateTime, $dateTime);
90
    }
91
92
    /**
93
     * @return array<int, mixed>
94
     */
95
    public function getCreateDateTimeData(): array
96
    {
97
        return [
98
            [
99
                null,
100
            ],
101
102
            [
103
                'now',
104
            ],
105
106
            [
107
                'now',
108
                'Europe/London',
109
            ],
110
111
            [
112
                '2019-05-14 12:33:00',
113
            ],
114
115
            [
116
                '2019-08-14 17:34:55',
117
                'UTC',
118
            ],
119
120
            [
121
                '2020-08-22 14:43:12',
122
                null,
123
            ],
124
125
            [
126
                '2020-08-22 14:44:37',
127
                new \DateTimeZone('Europe/London'),
128
            ],
129
        ];
130
    }
131
132
    /**
133
     * Ensure that if the DateTime cannot be created because the provided $spec is invalid, a new
134
     * DateTimeFactoryException will be thrown.
135
     *
136
     * @throws DateTimeFactoryException
137
     */
138
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(): void
139
    {
140
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
141
142
        $spec = 'foo'; // invalid argument
143
144
        $exceptionMessage = sprintf(
145
            'DateTime::__construct(): Failed to parse time string (%s) at position 0 (%s)',
146
            $spec,
147
            $spec[0]
148
        );
149
150
        $this->expectException(DateTimeFactoryException::class);
151
        $this->expectExceptionMessage(
152
            sprintf(
153
                'Failed to create a valid \DateTime instance using \'%s\': %s',
154
                $spec,
155
                $exceptionMessage
156
            )
157
        );
158
159
        $factory->createDateTime($spec);
160
    }
161
162
    /**
163
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
164
     * createFromFormat()
165
     *
166
     * @throws DateTimeFactoryException
167
     */
168
    public function testCreateFromFormatWillCatchAndReThrowException(): void
169
    {
170
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
171
172
        $spec = '2021-05-01 23:38:12';
173
        $format = 'Y-m-d H:i:s';
174
        $timeZone = 'UTC';
175
176
        $exceptionMessage = 'This is a test exception';
177
        $exceptionCode = 123;
178
        $exception = new DateTimeZoneFactoryException($exceptionMessage, $exceptionCode);
179
180
        $this->dateTimeZoneFactory->expects($this->once())
181
            ->method('createDateTimeZone')
182
            ->with($timeZone)
183
            ->willThrowException($exception);
184
185
        $this->expectException(DateTimeFactoryException::class);
186
        $this->expectExceptionCode($exceptionCode);
187
        $this->expectExceptionMessage(
188
            sprintf('Failed to create date time zone: %s', $exceptionMessage),
189
        );
190
191
        $factory->createFromFormat($format, $spec, $timeZone);
192
    }
193
194
    /**
195
     * Assert that a DateTimeFactoryException will be thrown if providing an invalid $spec
196
     * argument to createFromFormat().
197
     *
198
     * @param string                    $format
199
     * @param string                    $spec
200
     * @param \DateTimeZone|string|null $timeZone
201
     *
202
     * @dataProvider getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData
203
     *
204
     * @throws DateTimeFactoryException
205
     */
206
    public function testCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(
207
        string $format,
208
        string $spec,
209
        $timeZone = null
210
    ): void {
211
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
212
213
        $this->expectException(DateTimeFactoryException::class);
214
        $this->expectExceptionMessage(
215
            sprintf(
216
                'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
217
                $spec,
218
                $format
219
            )
220
        );
221
222
        $factory->createFromFormat($format, $spec, $timeZone);
223
    }
224
225
    /**
226
     * @return array<int, mixed>
227
     */
228
    public function getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData(): array
229
    {
230
        return [
231
            [
232
                'test',
233
                'Y-m-d',
234
            ],
235
        ];
236
    }
237
238
    /**
239
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
240
     * createDateTime()
241
     *
242
     * @throws DateTimeFactoryException
243
     */
244
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeZone(): void
245
    {
246
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
247
248
        $spec = 'now';
249
        $timeZone = 'UTC';
250
251
        $exceptionMessage = 'This is a test exception';
252
        $exceptionCode = 123;
253
        $exception = new DateTimeZoneFactoryException($exceptionMessage, $exceptionCode);
254
255
        $this->dateTimeZoneFactory->expects($this->once())
256
            ->method('createDateTimeZone')
257
            ->with($timeZone)
258
            ->willThrowException($exception);
259
260
        $this->expectException(DateTimeFactoryException::class);
261
        $this->expectExceptionCode($exceptionCode);
262
        $this->expectExceptionMessage(
263
            sprintf('Failed to create date time zone: %s', $exceptionMessage),
264
        );
265
266
        $factory->createDateTime($spec, $timeZone);
267
    }
268
269
    /**
270
     * Ensure that a \DateTime instance can be created from the provided format
271
     *
272
     * @param string                    $format
273
     * @param string                    $spec
274
     * @param string|\DateTimeZone|null $timeZone
275
     *
276
     * @dataProvider getCreateFromFormatData
277
     *
278
     * @throws DateTimeFactoryException
279
     */
280
    public function testCreateFromFormat(string $format, string $spec, $timeZone = null): void
281
    {
282
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
283
284
        $dateTimeZone = is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone;
285
286
        if (is_string($timeZone)) {
287
            $dateTimeZone = new \DateTimeZone($timeZone);
288
            $this->dateTimeZoneFactory->expects($this->once())
289
                ->method('createDateTimeZone')
290
                ->with($timeZone)
291
                ->willReturn($dateTimeZone);
292
        }
293
294
        /** @var \DateTimeInterface $expectedDateTime */
295
        $expectedDateTime = \DateTime::createFromFormat(
296
            $format,
297
            $spec,
298
            $dateTimeZone
299
        );
300
301
        $this->assertDateTime($expectedDateTime, $factory->createFromFormat($format, $spec, $timeZone));
302
    }
303
304
    /**
305
     * @see https://www.php.net/manual/en/timezones.europe.php
306
     *
307
     * @return array<int, mixed>
308
     */
309
    public function getCreateFromFormatData(): array
310
    {
311
        return [
312
            [
313
                'Y-m-d',
314
                '2019-04-01',
315
            ],
316
            [
317
                'Y/m/d',
318
                '1976/01/14',
319
            ],
320
            [
321
                'Y-m-d H:i:s',
322
                '2019-08-14 17:34:55',
323
                'UTC',
324
            ],
325
            [
326
                'Y-m-d H:i:s',
327
                '2010-10-26 11:19:32',
328
                'Europe/London',
329
            ],
330
        ];
331
    }
332
333
    /**
334
     * @param \DateTimeInterface $expectedDateTime
335
     * @param \DateTimeInterface $dateTime
336
     */
337
    private function assertDateTime(\DateTimeInterface $expectedDateTime, \DateTimeInterface $dateTime): void
338
    {
339
        $this->assertSame($expectedDateTime->format('Y'), $dateTime->format('Y'), 'Years do not match');
340
        $this->assertSame($expectedDateTime->format('M'), $dateTime->format('M'), 'Months do not match');
341
        $this->assertSame($expectedDateTime->format('d'), $dateTime->format('d'), 'Days do not match');
342
        $this->assertSame($expectedDateTime->format('H'), $dateTime->format('H'), 'Hours do not match');
343
        $this->assertSame($expectedDateTime->format('i'), $dateTime->format('i'), 'Minuets do not match');
344
        $this->assertSame($expectedDateTime->format('s'), $dateTime->format('s'), 'Seconds do not match');
345
        $this->assertSame($expectedDateTime->format('f'), $dateTime->format('f'), 'Milliseconds do not match');
346
347
        $this->assertSame(
348
            $expectedDateTime->getTimezone()->getName(),
349
            $dateTime->getTimezone()->getName(),
350
            'Timezones do not match'
351
        );
352
    }
353
}
354