Passed
Pull Request — master (#11)
by Alex
03:00
created

testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 15
rs 10
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
        $this->expectException(DateTimeFactoryException::class);
145
        $this->expectExceptionMessage(
146
            sprintf(
147
                'Failed to create a valid \DateTime instance using \'%s\':',
148
                $spec
149
            )
150
        );
151
152
        $factory->createDateTime($spec);
153
    }
154
155
    /**
156
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
157
     * createFromFormat()
158
     *
159
     * @throws DateTimeFactoryException
160
     */
161
    public function testCreateFromFormatWillCatchAndReThrowException(): void
162
    {
163
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
164
165
        $spec = '2021-05-01 23:38:12';
166
        $format = 'Y-m-d H:i:s';
167
        $timeZone = 'UTC';
168
169
        $exceptionMessage = 'This is a test exception';
170
        $exceptionCode = 123;
171
        $exception = new DateTimeZoneFactoryException($exceptionMessage, $exceptionCode);
172
173
        $this->dateTimeZoneFactory->expects($this->once())
174
            ->method('createDateTimeZone')
175
            ->with($timeZone)
176
            ->willThrowException($exception);
177
178
        $this->expectException(DateTimeFactoryException::class);
179
        $this->expectExceptionCode($exceptionCode);
180
        $this->expectExceptionMessage(
181
            sprintf('Failed to create date time zone: %s', $exceptionMessage),
182
        );
183
184
        $factory->createFromFormat($format, $spec, $timeZone);
185
    }
186
187
    /**
188
     * Assert that a DateTimeFactoryException will be thrown if providing an invalid $spec
189
     * argument to createFromFormat().
190
     *
191
     * @param string                    $format
192
     * @param string                    $spec
193
     * @param \DateTimeZone|string|null $timeZone
194
     *
195
     * @dataProvider getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData
196
     *
197
     * @throws DateTimeFactoryException
198
     */
199
    public function testCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(
200
        string $format,
201
        string $spec,
202
        $timeZone = null
203
    ): void {
204
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
205
206
        $this->expectException(DateTimeFactoryException::class);
207
        $this->expectExceptionMessage(
208
            sprintf(
209
                'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
210
                $spec,
211
                $format
212
            )
213
        );
214
215
        $factory->createFromFormat($format, $spec, $timeZone);
216
    }
217
218
    /**
219
     * @return array<int, mixed>
220
     */
221
    public function getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData(): array
222
    {
223
        return [
224
            [
225
                'test',
226
                'Y-m-d',
227
            ],
228
        ];
229
    }
230
231
    /**
232
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
233
     * createDateTime()
234
     *
235
     * @throws DateTimeFactoryException
236
     */
237
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeZone(): void
238
    {
239
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
240
241
        $spec = 'now';
242
        $timeZone = 'UTC';
243
244
        $exceptionMessage = 'This is a test exception';
245
        $exceptionCode = 123;
246
        $exception = new DateTimeZoneFactoryException($exceptionMessage, $exceptionCode);
247
248
        $this->dateTimeZoneFactory->expects($this->once())
249
            ->method('createDateTimeZone')
250
            ->with($timeZone)
251
            ->willThrowException($exception);
252
253
        $this->expectException(DateTimeFactoryException::class);
254
        $this->expectExceptionCode($exceptionCode);
255
        $this->expectExceptionMessage(
256
            sprintf('Failed to create date time zone: %s', $exceptionMessage),
257
        );
258
259
        $factory->createDateTime($spec, $timeZone);
260
    }
261
262
    /**
263
     * Ensure that a \DateTime instance can be created from the provided format
264
     *
265
     * @param string                    $format
266
     * @param string                    $spec
267
     * @param string|\DateTimeZone|null $timeZone
268
     *
269
     * @dataProvider getCreateFromFormatData
270
     *
271
     * @throws DateTimeFactoryException
272
     */
273
    public function testCreateFromFormat(string $format, string $spec, $timeZone = null): void
274
    {
275
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
276
277
        $dateTimeZone = is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone;
278
279
        if (is_string($timeZone)) {
280
            $dateTimeZone = new \DateTimeZone($timeZone);
281
            $this->dateTimeZoneFactory->expects($this->once())
282
                ->method('createDateTimeZone')
283
                ->with($timeZone)
284
                ->willReturn($dateTimeZone);
285
        }
286
287
        /** @var \DateTimeInterface $expectedDateTime */
288
        $expectedDateTime = \DateTime::createFromFormat(
289
            $format,
290
            $spec,
291
            $dateTimeZone
292
        );
293
294
        $this->assertDateTime($expectedDateTime, $factory->createFromFormat($format, $spec, $timeZone));
295
    }
296
297
    /**
298
     * @see https://www.php.net/manual/en/timezones.europe.php
299
     *
300
     * @return array<int, mixed>
301
     */
302
    public function getCreateFromFormatData(): array
303
    {
304
        return [
305
            [
306
                'Y-m-d',
307
                '2019-04-01',
308
            ],
309
            [
310
                'Y/m/d',
311
                '1976/01/14',
312
            ],
313
            [
314
                'Y-m-d H:i:s',
315
                '2019-08-14 17:34:55',
316
                'UTC',
317
            ],
318
            [
319
                'Y-m-d H:i:s',
320
                '2010-10-26 11:19:32',
321
                'Europe/London',
322
            ],
323
        ];
324
    }
325
326
    /**
327
     * @param \DateTimeInterface $expectedDateTime
328
     * @param \DateTimeInterface $dateTime
329
     */
330
    private function assertDateTime(\DateTimeInterface $expectedDateTime, \DateTimeInterface $dateTime): void
331
    {
332
        $this->assertSame($expectedDateTime->format('Y'), $dateTime->format('Y'), 'Years do not match');
333
        $this->assertSame($expectedDateTime->format('M'), $dateTime->format('M'), 'Months do not match');
334
        $this->assertSame($expectedDateTime->format('d'), $dateTime->format('d'), 'Days do not match');
335
        $this->assertSame($expectedDateTime->format('H'), $dateTime->format('H'), 'Hours do not match');
336
        $this->assertSame($expectedDateTime->format('i'), $dateTime->format('i'), 'Minuets do not match');
337
        $this->assertSame($expectedDateTime->format('s'), $dateTime->format('s'), 'Seconds do not match');
338
        $this->assertSame($expectedDateTime->format('f'), $dateTime->format('f'), 'Milliseconds do not match');
339
340
        $this->assertSame(
341
            $expectedDateTime->getTimezone()->getName(),
342
            $dateTime->getTimezone()->getName(),
343
            'Timezones do not match'
344
        );
345
    }
346
}
347