Passed
Pull Request — master (#7)
by Alex
07:24
created

testImplementsDateTimeFactoryInterface()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 2
c 1
b 1
f 0
dl 0
loc 5
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\Exception\DateTimeFactoryException;
10
use PHPUnit\Framework\TestCase;
11
12
/**
13
 * @covers  \Arp\DateTime\DateTimeFactory
14
 *
15
 * @author  Alex Patterson <[email protected]>
16
 * @package ArpTest\DateTime
17
 */
18
final class DateTimeFactoryTest extends TestCase
19
{
20
    /**
21
     * Ensure that the factory implements DateTimeFactoryInterface
22
     */
23
    public function testImplementsDateTimeFactoryInterface(): void
24
    {
25
        $factory = new DateTimeFactory();
26
27
        $this->assertInstanceOf(DateTimeFactoryInterface::class, $factory);
28
    }
29
30
    /**
31
     * Assert that a DateTimeFactoryException is thrown if trying to create the class without a valid
32
     * $dateTimeClassName constructor argument
33
     *
34
     * @throws DateTimeFactoryException
35
     */
36
    public function testDateTimeFactoryExceptionIsThrownWhenProvidingInvalidDateTimeClassName(): void
37
    {
38
        $dateTimeClassName = \stdClass::class;
39
40
        $this->expectException(DateTimeFactoryException::class);
41
        $this->expectExceptionMessage(
42
            sprintf(
43
                'The \'dateTimeClassName\' must the fully qualified class name'
44
                . ' of a class that implements \'%s\'; \'%s\' provided',
45
                \DateTimeInterface::class,
46
                $dateTimeClassName
47
            )
48
        );
49
50
        new DateTimeFactory($dateTimeClassName);
51
    }
52
53
    /**
54
     * Assert that a DateTimeFactoryException is thrown if trying to create the class without a valid
55
     * $dateTimeZoneClassName constructor argument
56
     *
57
     * @throws DateTimeFactoryException
58
     */
59
    public function testDateTimeFactoryExceptionIsThrownWhenProvidingInvalidDateTimeZoneClassName(): void
60
    {
61
        $dateTimeZoneClassName = \stdClass::class;
62
63
        $this->expectException(DateTimeFactoryException::class);
64
        $this->expectExceptionMessage(
65
            sprintf(
66
                'The \'dateTimeZoneClassName\' must the fully qualified class name'
67
                . ' of a class that implements \'%s\'; \'%s\' provided',
68
                \DateTimeZone::class,
69
                $dateTimeZoneClassName
70
            )
71
        );
72
73
        new DateTimeFactory(null, $dateTimeZoneClassName);
74
    }
75
76
    /**
77
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
78
     *
79
     * @param string|null               $spec     The date and time specification.
80
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test.
81
     *
82
     * @dataProvider getCreateDateTimeData
83
     *
84
     * @throws DateTimeFactoryException
85
     */
86
    public function testCreateDateTime(?string $spec, $timeZone = null): void
87
    {
88
        $factory = new DateTimeFactory();
89
90
        if (is_string($timeZone)) {
91
            $timeZone ??= new \DateTimeZone($timeZone);
92
        }
93
94
        $expectedDateTime = new \DateTime(
95
            $spec ?? 'now',
96
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
97
        );
98
99
        $dateTime = $factory->createDateTime($spec, $timeZone);
100
101
        $this->assertSame($expectedDateTime->format('Y'), $dateTime->format('Y'), 'Years do not match');
102
        $this->assertSame($expectedDateTime->format('M'), $dateTime->format('M'), 'Months do not match');
103
        $this->assertSame($expectedDateTime->format('d'), $dateTime->format('d'), 'Days do not match');
104
        $this->assertSame($expectedDateTime->format('H'), $dateTime->format('H'), 'Hours do not match');
105
        $this->assertSame($expectedDateTime->format('i'), $dateTime->format('i'), 'Minuets do not match');
106
107
        if (null === $spec || 'now' === $spec) {
108
            // Assert with 1 second delta
109
            $this->assertEqualsWithDelta($expectedDateTime->format('s'), $dateTime->format('s'), 1.0);
110
            $this->assertEqualsWithDelta($expectedDateTime->format('f'), $dateTime->format('f'), 1000.0);
111
        } else {
112
            $this->assertSame($expectedDateTime->format('s'), $dateTime->format('s'));
113
            $this->assertSame($expectedDateTime->format('f'), $dateTime->format('f'));
114
        }
115
116
        $this->assertSame($expectedDateTime->getTimezone()->getName(), $dateTime->getTimezone()->getName());
117
    }
118
119
    /**
120
     * @return array
121
     */
122
    public function getCreateDateTimeData(): array
123
    {
124
        return [
125
            [
126
                null,
127
            ],
128
129
            [
130
                'now',
131
            ],
132
133
            [
134
                'now',
135
                'Europe/London'
136
            ],
137
138
            [
139
                '2019-05-14 12:33:00',
140
            ],
141
142
            [
143
                '2019-08-14 17:34:55',
144
                'UTC',
145
            ],
146
147
            [
148
                '2020-08-22 14:43:12',
149
                null,
150
            ],
151
152
            [
153
                '2020-08-22 14:44:37',
154
                new \DateTimeZone('Europe/London'),
155
            ],
156
        ];
157
    }
158
159
    /**
160
     * Ensure that if the DateTime cannot be created because the provided $spec is invalid, a new
161
     * DateTimeFactoryException will be thrown.
162
     *
163
     * @throws DateTimeFactoryException
164
     */
165
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(): void
166
    {
167
        $factory = new DateTimeFactory();
168
169
        $spec = 'foo'; // invalid argument
170
171
        $exceptionMessage = sprintf(
172
            'DateTime::__construct(): Failed to parse time string (%s) at position 0 (%s)',
173
            $spec,
174
            $spec[0]
175
        );
176
177
        $this->expectException(DateTimeFactoryException::class);
178
        $this->expectExceptionMessage(
179
            sprintf(
180
                'Failed to create a valid \DateTime instance using \'%s\': %s',
181
                $spec,
182
                $exceptionMessage
183
            )
184
        );
185
186
        $factory->createDateTime($spec);
187
    }
188
189
    /**
190
     * Assert that a DateTimeFactoryException will be thrown if providing an invalid $spec
191
     * argument to createFromFormat().
192
     *
193
     * @param string                    $spec
194
     * @param string                    $format
195
     * @param \DateTimeZone|string|null $timeZone
196
     *
197
     * @dataProvider getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData
198
     *
199
     * @throws DateTimeFactoryException
200
     */
201
    public function testCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(
202
        string $spec,
203
        string $format,
204
        $timeZone = null
205
    ): void {
206
        $factory = new DateTimeFactory();
207
208
        $this->expectException(DateTimeFactoryException::class);
209
        $this->expectExceptionMessage(
210
            sprintf(
211
                'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
212
                $spec,
213
                $format
214
            )
215
        );
216
217
        $factory->createFromFormat($spec, $format, $timeZone);
218
    }
219
220
    /**
221
     * @return array
222
     */
223
    public function getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData(): array
224
    {
225
        return [
226
            [
227
                'test',
228
                'Y-m-d',
229
            ],
230
        ];
231
    }
232
233
    /**
234
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
235
     * createDateTime()
236
     *
237
     * @throws DateTimeFactoryException
238
     */
239
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeZone(): void
240
    {
241
        $factory = new DateTimeFactory();
242
243
        $spec = 'now';
244
        $timeZone = new \stdClass();
245
246
        $errorMessage = sprintf(
247
            'The \'timeZone\' argument must be a \'string\''
248
            . 'or an object of type \'%s\'; \'%s\' provided in \'%s\'',
249
            \DateTimeZone::class,
250
            is_object($timeZone) ? get_class($timeZone) : gettype($timeZone),
251
            'resolveDateTimeZone'
252
        );
253
254
        $this->expectException(DateTimeFactoryException::class);
255
        $this->expectExceptionMessage($errorMessage);
256
257
        $factory->createDateTime($spec, /** @scrutinizer ignore-type */ $timeZone);
258
    }
259
260
    /**
261
     * Ensure that a \DateTime instance can be created from the provided format
262
     *
263
     * @param string                    $spec
264
     * @param string                    $format
265
     * @param string|\DateTimeZone|null $timeZone
266
     *
267
     * @dataProvider getCreateFromFormatData
268
     *
269
     * @throws DateTimeFactoryException
270
     */
271
    public function testCreateFromFormat(string $spec, string $format, $timeZone = null): void
272
    {
273
        $factory = new DateTimeFactory();
274
275
        $expectedDateTime = \DateTime::createFromFormat(
276
            $format,
277
            $spec ?? 'now',
278
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
279
        );
280
281
        $dateTime = $factory->createFromFormat($spec, $format, $timeZone);
282
283
        $this->assertSame($expectedDateTime->format('Y'), $dateTime->format('Y'), 'Years do not match');
284
        $this->assertSame($expectedDateTime->format('M'), $dateTime->format('M'), 'Months do not match');
285
        $this->assertSame($expectedDateTime->format('d'), $dateTime->format('d'), 'Days do not match');
286
        $this->assertSame($expectedDateTime->format('H'), $dateTime->format('H'), 'Hours do not match');
287
        $this->assertSame($expectedDateTime->format('i'), $dateTime->format('i'), 'Minuets do not match');
288
289
        if (null === $spec || 'now' === $spec) {
290
            $this->assertEqualsWithDelta(
291
                $expectedDateTime->format('f'),
292
                $dateTime->format('f'),
293
                1.0,
294
                'Milliseconds exceed acceptable delta',
295
            );
296
297
            $this->assertEqualsWithDelta(
298
                $expectedDateTime->format('sf'),
299
                $dateTime->format('f'),
300
                1000.0,
301
                'Milliseconds exceed acceptable delta'
302
            );
303
        } else {
304
            $this->assertSame($expectedDateTime->format('s'), $dateTime->format('s'), 'Seconds do not match');
305
            $this->assertSame($expectedDateTime->format('f'), $dateTime->format('f'), 'Milliseconds do not match');
306
        }
307
308
        $this->assertSame(
309
            $expectedDateTime->getTimezone()->getName(),
310
            $dateTime->getTimezone()->getName(),
311
            'Timezones do not match'
312
        );
313
    }
314
315
    /**
316
     * @see https://www.php.net/manual/en/timezones.europe.php
317
     *
318
     * @return array
319
     */
320
    public function getCreateFromFormatData(): array
321
    {
322
        return [
323
            [
324
                '2019-04-01',
325
                'Y-m-d',
326
            ],
327
            [
328
                '1976/01/14',
329
                'Y/m/d',
330
            ],
331
            [
332
                '2019-08-14 17:34:55',
333
                'Y-m-d H:i:s',
334
                'UTC',
335
            ],
336
            [
337
                '2010-10-26 11:19:32',
338
                'Y-m-d H:i:s',
339
                'Europe/London',
340
            ],
341
        ];
342
    }
343
344
    /**
345
     * Ensure a \DateTimeZone instance is returned according to the provided $spec and $options
346
     *
347
     * @param string $spec
348
     *
349
     * @dataProvider getCreateDateTimeZoneData
350
     *
351
     * @throws DateTimeFactoryException
352
     */
353
    public function testCreateDateTimeZone(string $spec): void
354
    {
355
        $factory = new DateTimeFactory();
356
357
        $dateTimeZone = $factory->createDateTimeZone($spec);
358
359
        $this->assertSame($spec, $dateTimeZone->getName());
360
    }
361
362
    /**
363
     * @see https://www.php.net/manual/en/timezones.europe.php
364
     *
365
     * @return array
366
     */
367
    public function getCreateDateTimeZoneData(): array
368
    {
369
        return [
370
            [
371
                'UTC',
372
            ],
373
            [
374
                'Europe/London',
375
            ],
376
            [
377
                'Europe/Amsterdam',
378
            ],
379
            [
380
                'Europe/Rome',
381
            ],
382
            [
383
                'Atlantic/Bermuda',
384
            ],
385
            [
386
                'Atlantic/Azores',
387
            ],
388
            [
389
                'Antarctica/DumontDUrville',
390
            ],
391
        ];
392
    }
393
394
    /**
395
     * Ensure that if providing an invalid $spec argument to createDateTimeZone() a new DateTimeFactoryException
396
     * is thrown
397
     *
398
     * @param string $spec The invalid timezone specification
399
     *
400
     * @dataProvider getCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalidData
401
     *
402
     * @throws DateTimeFactoryException
403
     */
404
    public function testCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalid(string $spec): void
405
    {
406
        $factory = new DateTimeFactory();
407
408
        $exceptionMessage = sprintf('DateTimeZone::__construct(): Unknown or bad timezone (%s)', $spec);
409
410
        $this->expectException(DateTimeFactoryException::class);
411
        $this->expectExceptionMessage(
412
            sprintf(
413
                'Failed to create a valid \DateTimeZone instance using \'%s\': %s',
414
                $spec,
415
                $exceptionMessage
416
            )
417
        );
418
419
        $factory->createDateTimeZone($spec);
420
    }
421
422
    /**
423
     * @return array
424
     */
425
    public function getCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalidData(): array
426
    {
427
        return [
428
            [
429
                'skjdvbnksd',
430
            ],
431
            [
432
                '2345234',
433
            ],
434
            [
435
                'Europe/MyEmpire',
436
            ],
437
        ];
438
    }
439
}
440