Passed
Pull Request — master (#7)
by Alex
01:41
created

testDateIntervalWillThrowDateTimeFactoryExceptionIfUnableToCreateADateInterval()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 16
c 1
b 1
f 0
dl 0
loc 25
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\DateFactoryInterface;
8
use Arp\DateTime\DateIntervalFactoryInterface;
9
use Arp\DateTime\DateTimeFactory;
10
use Arp\DateTime\DateTimeFactoryInterface;
11
use Arp\DateTime\Exception\DateIntervalFactoryException;
12
use Arp\DateTime\Exception\DateTimeFactoryException;
13
use PHPUnit\Framework\MockObject\MockObject;
14
use PHPUnit\Framework\TestCase;
15
16
/**
17
 * @author  Alex Patterson <[email protected]>
18
 * @package ArpTest\DateTime
19
 */
20
final class DateTimeFactoryTest extends TestCase
21
{
22
    /**
23
     * @var DateIntervalFactoryInterface|MockObject
24
     */
25
    private $dateIntervalFactory;
26
27
    /**
28
     * Prepare the test case dependencies
29
     */
30
    public function setUp(): void
31
    {
32
        $this->dateIntervalFactory = $this->getMockForAbstractClass(DateIntervalFactoryInterface::class);
33
    }
34
35
    /**
36
     * Ensure that the factory implements DateFactoryInterface.
37
     *
38
     * @covers \Arp\DateTime\DateTimeFactory
39
     */
40
    public function testImplementsDateFactoryInterface(): void
41
    {
42
        $factory = new DateTimeFactory($this->dateIntervalFactory);
43
44
        $this->assertInstanceOf(DateFactoryInterface::class, $factory);
45
    }
46
47
    /**
48
     * Ensure that the factory implements DateTimeFactoryInterface.
49
     *
50
     * @covers \Arp\DateTime\DateTimeFactory
51
     */
52
    public function testImplementsDateTimeFactoryInterface(): void
53
    {
54
        $factory = new DateTimeFactory($this->dateIntervalFactory);
55
56
        $this->assertInstanceOf(DateTimeFactoryInterface::class, $factory);
57
    }
58
59
    /**
60
     * Ensure that the factory implements DateIntervalFactoryInterface.
61
     *
62
     * @covers \Arp\DateTime\DateTimeFactory
63
     */
64
    public function testImplementsDateIntervalFactoryInterface(): void
65
    {
66
        $factory = new DateTimeFactory($this->dateIntervalFactory);
67
68
        $this->assertInstanceOf(DateIntervalFactoryInterface::class, $factory);
69
    }
70
71
    /**
72
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
73
     *
74
     * @param string                    $spec     The date and time specification.
75
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test.
76
     *
77
     * @dataProvider getCreateDateTimeData
78
     *
79
     * @covers       \Arp\DateTime\DateTimeFactory::createDateTime
80
     * @covers       \Arp\DateTime\DateTimeFactory::resolveDateTimeZone
81
     *
82
     * @throws DateTimeFactoryException
83
     */
84
    public function testCreateDateTime(string $spec, $timeZone = null): void
85
    {
86
        $factory = new DateTimeFactory($this->dateIntervalFactory);
87
88
        $dateTime = $factory->createDateTime($spec, $timeZone);
89
90
        $this->assertSame($spec, $dateTime->format('Y-m-d H:i:s'));
91
    }
92
93
    /**
94
     * @return array
95
     */
96
    public function getCreateDateTimeData(): array
97
    {
98
        return [
99
            [
100
                '2019-05-14 12:33:00',
101
            ],
102
103
            [
104
                '2019-08-14 17:34:55',
105
                'UTC',
106
            ],
107
108
            [
109
                '2020-08-22 14:43:12',
110
                null,
111
            ],
112
113
            [
114
                '2020-08-22 14:44:37',
115
                new \DateTimeZone('Europe/London'),
116
            ],
117
        ];
118
    }
119
120
    /**
121
     * Ensure that if the DateTime cannot be created because the provided $spec is invalid, a new
122
     * DateTimeFactoryException will be thrown.
123
     *
124
     * @covers \Arp\DateTime\DateTimeFactory::createDateTime
125
     *
126
     * @throws DateTimeFactoryException
127
     */
128
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(): void
129
    {
130
        $factory = new DateTimeFactory($this->dateIntervalFactory);
131
132
        $spec = 'foo'; // invalid argument
133
134
        $exceptionMessage = sprintf(
135
            'DateTime::__construct(): Failed to parse time string (%s) at position 0 (%s)',
136
            $spec,
137
            $spec[0]
138
        );
139
140
        $this->expectException(DateTimeFactoryException::class);
141
        $this->expectExceptionMessage(
142
            sprintf(
143
                'Failed to create a valid \DateTime instance using \'%s\': %s',
144
                $spec,
145
                $exceptionMessage
146
            )
147
        );
148
149
        $factory->createDateTime($spec);
150
    }
151
152
    /**
153
     * Assert that a DateTimeFactoryException will be thrown if providing an invalid $spec
154
     * argument to createFromFormat().
155
     *
156
     * @param string                    $spec
157
     * @param string                    $format
158
     * @param \DateTimeZone|string|null $timeZone
159
     *
160
     * @dataProvider getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData
161
     *
162
     * @covers       \Arp\DateTime\DateTimeFactory::createFromFormat
163
     *
164
     * @throws DateTimeFactoryException
165
     */
166
    public function testCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(
167
        string $spec,
168
        string $format,
169
        $timeZone = null
170
    ): void {
171
        $factory = new DateTimeFactory($this->dateIntervalFactory);
172
173
        $this->expectException(DateTimeFactoryException::class);
174
        $this->expectExceptionMessage(
175
            sprintf(
176
                'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
177
                $spec,
178
                $format
179
            )
180
        );
181
182
        $factory->createFromFormat($spec, $format, $timeZone);
183
    }
184
185
    /**
186
     * @return array
187
     */
188
    public function getCreateFromFormatWillThrowDateTimeFactoryExceptionForInvalidDateTimeData(): array
189
    {
190
        return [
191
            [
192
                'test',
193
                'Y-m-d',
194
            ],
195
        ];
196
    }
197
198
    /**
199
     * Assert that a DateTimeFactoryException will be thrown when providing a invalid \DateTimeZone object to
200
     * createDateTime().
201
     *
202
     * @covers \Arp\DateTime\DateTimeFactory::createDateTime
203
     * @covers \Arp\DateTime\DateTimeFactory::resolveDateTimeZone
204
     *
205
     * @throws DateTimeFactoryException
206
     */
207
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeZone(): void
208
    {
209
        $factory = new DateTimeFactory($this->dateIntervalFactory);
210
211
        $spec = 'now';
212
        $timeZone = new \stdClass();
213
214
        $errorMessage = sprintf(
215
            'The \'timeZone\' argument must be a \'string\''
216
            . 'or an object of type \'%s\'; \'%s\' provided in \'%s\'',
217
            \DateTimeZone::class,
218
            is_object($timeZone) ? get_class($timeZone) : gettype($timeZone),
219
            'resolveDateTimeZone'
220
        );
221
222
        $this->expectException(DateTimeFactoryException::class);
223
        $this->expectExceptionMessage($errorMessage);
224
225
        $factory->createDateTime($spec, /** @scrutinizer ignore-type */$timeZone);
226
    }
227
228
    /**
229
     * Ensure that a \DateTime instance can be created from the provided format.
230
     *
231
     * @param string                    $spec
232
     * @param string                    $format
233
     * @param string|\DateTimeZone|null $timeZone
234
     *
235
     * @dataProvider getCreateFromFormatData
236
     *
237
     * @covers       \Arp\DateTime\DateTimeFactory::createFromFormat
238
     * @covers       \Arp\DateTime\DateTimeFactory::resolveDateTimeZone
239
     *
240
     * @throws DateTimeFactoryException
241
     */
242
    public function testCreateFromFormat(string $spec, string $format, $timeZone = null): void
243
    {
244
        $factory = new DateTimeFactory($this->dateIntervalFactory);
245
246
        $dateTime = $factory->createFromFormat($spec, $format, $timeZone);
247
248
        $this->assertSame($spec, $dateTime->format($format));
249
250
        if (null !== $timeZone) {
251
            $this->assertSame($timeZone, $dateTime->getTimezone()->getName());
252
        }
253
    }
254
255
    /**
256
     * @see https://www.php.net/manual/en/timezones.europe.php
257
     *
258
     * @return array
259
     */
260
    public function getCreateFromFormatData(): array
261
    {
262
        return [
263
            [
264
                '2019-04-01',
265
                'Y-m-d',
266
            ],
267
            [
268
                '1976/01/14',
269
                'Y/m/d',
270
            ],
271
            [
272
                '2019-08-14 17:34:55',
273
                'Y-m-d H:i:s',
274
                'UTC',
275
            ],
276
            [
277
                '2010-10-26 11:19:32',
278
                'Y-m-d H:i:s',
279
                'Europe/London',
280
            ],
281
        ];
282
    }
283
284
    /**
285
     * Ensure a \DateTimeZone instance is returned according to the provided $spec and $options.
286
     *
287
     * @param string $spec
288
     *
289
     * @dataProvider getCreateDateTimeZoneData
290
     *
291
     * @covers       \Arp\DateTime\DateTimeFactory::createDateTimeZone
292
     *
293
     * @throws DateTimeFactoryException
294
     */
295
    public function testCreateDateTimeZone(string $spec): void
296
    {
297
        $factory = new DateTimeFactory($this->dateIntervalFactory);
298
299
        $dateTimeZone = $factory->createDateTimeZone($spec);
300
301
        $this->assertSame($spec, $dateTimeZone->getName());
302
    }
303
304
    /**
305
     * @see https://www.php.net/manual/en/timezones.europe.php
306
     *
307
     * @return array
308
     */
309
    public function getCreateDateTimeZoneData(): array
310
    {
311
        return [
312
            [
313
                'Europe/London',
314
            ],
315
            [
316
                'Europe/Amsterdam',
317
            ],
318
            [
319
                'Europe/Rome',
320
            ],
321
            [
322
                'Atlantic/Bermuda',
323
            ],
324
            [
325
                'Atlantic/Azores',
326
            ],
327
            [
328
                'Antarctica/DumontDUrville',
329
            ],
330
        ];
331
    }
332
333
    /**
334
     * Ensure that if providing an invalid $spec argument to createDateTimeZone() a new DateTimeFactoryException
335
     * is thrown.
336
     *
337
     * @param string $spec The invalid timezone specification.
338
     *
339
     * @throws DateTimeFactoryException
340
     *
341
     * @dataProvider getCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalidData
342
     *
343
     * @covers       \Arp\DateTime\DateTimeFactory::createDateTimeZone
344
     */
345
    public function testCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalid(string $spec): void
346
    {
347
        $factory = new DateTimeFactory($this->dateIntervalFactory);
348
349
        $exceptionMessage = sprintf('DateTimeZone::__construct(): Unknown or bad timezone (%s)', $spec);
350
351
        $this->expectException(DateTimeFactoryException::class);
352
        $this->expectExceptionMessage(
353
            sprintf(
354
                'Failed to create a valid \DateTimeZone instance using \'%s\': %s',
355
                $spec,
356
                $exceptionMessage
357
            )
358
        );
359
360
        $factory->createDateTimeZone($spec);
361
    }
362
363
    /**
364
     * @return array
365
     */
366
    public function getCreateDateTimeZoneWillThrowDateTimeFactoryExceptionIfSpecIsInvalidData(): array
367
    {
368
        return [
369
            [
370
                'skjdvbnksd',
371
            ],
372
            [
373
                '2345234',
374
            ],
375
            [
376
                'Europe/MyEmpire',
377
            ],
378
        ];
379
    }
380
381
    /**
382
     * Assert that calls to creatDateInterval() will return the expected DateInterval instance.
383
     *
384
     * @param string $spec
385
     *
386
     * @covers       \Arp\DateTime\DateTimeFactory::createDateInterval
387
     * @dataProvider getCreateDateIntervalWillReturnANewDateIntervalToSpecData
388
     *
389
     * @throws DateTimeFactoryException
390
     */
391
    public function testCreateDateIntervalWillReturnANewDateIntervalToSpec(string $spec): void
392
    {
393
        $factory = new DateTimeFactory($this->dateIntervalFactory);
394
395
        /** @var \DateInterval|MockObject $dateInterval */
396
        $dateInterval = $this->createMock(\DateInterval::class);
397
398
        $this->dateIntervalFactory->expects($this->once())
399
            ->method('createDateInterval')
400
            ->willReturn($dateInterval);
401
402
        $this->assertSame($dateInterval, $factory->createDateInterval($spec));
403
    }
404
405
    /**
406
     * @return array
407
     */
408
    public function getCreateDateIntervalWillReturnANewDateIntervalToSpecData(): array
409
    {
410
        return [
411
            ['P100D'],
412
            ['P4Y1DT9H11M3S'],
413
            ['P2Y4DT6H8M'],
414
            ['P7Y8M'],
415
        ];
416
    }
417
418
    /**
419
     * Assert that an invalid $spec passed to createDateInterval() will raise a DateTimeFactoryException.
420
     *
421
     * @covers \Arp\DateTime\DateTimeFactory::createDateInterval
422
     *
423
     * @throws DateTimeFactoryException
424
     */
425
    public function testDateIntervalWillThrowDateTimeFactoryExceptionIfUnableToCreateADateInterval(): void
426
    {
427
        $spec = 'Hello';
428
429
        $factory = new DateTimeFactory($this->dateIntervalFactory);
430
431
        $exceptionCode = 123;
432
        $exceptionMessage = 'This is a test exception message';
433
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
434
435
        $this->dateIntervalFactory->expects($this->once())
436
            ->method('createDateInterval')
437
            ->willThrowException($exception);
438
439
        $errorMessage = sprintf(
440
            'Failed to create date interval \'%s\': %s',
441
            $spec,
442
            $exceptionMessage
443
        );
444
445
        $this->expectDeprecationMessage(DateTimeFactoryException::class);
446
        $this->expectExceptionMessage($errorMessage);
447
        $this->expectExceptionCode($exceptionCode);
448
449
        $factory->createDateInterval($spec);
450
    }
451
452
    /**
453
     * Assert that the calls to diff will return a valid DateInterval.
454
     *
455
     * @covers \Arp\DateTime\DateTimeFactory::diff
456
     *
457
     * @throws DateTimeFactoryException
458
     */
459
    public function testDiffWillReturnDateInterval(): void
460
    {
461
        // @todo Data provider
462
        $origin = new \DateTime();
463
        $target = new \DateTime();
464
        $absolute = false;
465
466
        $dateInterval = $origin->diff($target);
467
468
        $factory = new DateTimeFactory($this->dateIntervalFactory);
469
470
        $this->dateIntervalFactory->expects($this->once())
471
            ->method('diff')
472
            ->with($origin, $target, $absolute)
473
            ->willReturn($dateInterval);
474
475
        $this->assertSame($dateInterval, $factory->diff($origin, $target, $absolute));
476
    }
477
478
    /**
479
     * Assert that a DateTimeFactoryException is thrown when unable to diff the provided dates.
480
     *
481
     * @covers \Arp\DateTime\DateTimeFactory::diff
482
     *
483
     * @throws DateTimeFactoryException
484
     */
485
    public function testDateTimeFactoryExceptionWillBeThrownIfDiffFails(): void
486
    {
487
        $factory = new DateTimeFactory($this->dateIntervalFactory);
488
489
        $origin = new \DateTime();
490
        $target = new \DateTime();
491
492
        $exceptionCode = 123;
493
        $exceptionMessage = 'This is a test exception message';
494
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
495
496
        $this->dateIntervalFactory->expects($this->once())
497
            ->method('diff')
498
            ->with($origin, $target, false)
499
            ->willThrowException($exception);
500
501
        $this->expectException(DateTimeFactoryException::class);
502
        $this->expectExceptionMessage(sprintf('Failed to perform date diff: %s', $exceptionMessage));
503
        $this->expectExceptionCode($exceptionCode);
504
505
        $factory->diff($origin, $target);
506
    }
507
}
508