Issues (15)

test/unit/DateTimeFactoryTest.php (1 issue)

Labels
Severity
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
final class DateTimeFactoryTest extends TestCase
19
{
20
    /**
21
     * @var DateTimeZoneFactoryInterface&MockObject
22
     */
23
    private DateTimeZoneFactoryInterface $dateTimeZoneFactory;
24
25
    public function setUp(): void
26
    {
27
        $this->dateTimeZoneFactory = $this->createMock(DateTimeZoneFactoryInterface::class);
28
    }
29
30
    /**
31
     * Ensure that the factory implements DateTimeFactoryInterface
32
     */
33
    public function testImplementsDateTimeFactoryInterface(): void
34
    {
35
        $factory = new DateTimeFactory();
36
37
        $this->assertInstanceOf(DateTimeFactoryInterface::class, $factory);
38
    }
39
40
    /**
41
     * Assert that a DateTimeFactoryException is thrown if trying to create the class without a valid
42
     * $dateTimeClassName constructor argument
43
     *
44
     * @throws DateTimeFactoryException
45
     */
46
    public function testDateTimeFactoryExceptionIsThrownWhenProvidingInvalidDateTimeClassName(): void
47
    {
48
        $dateTimeClassName = \stdClass::class;
49
50
        $this->expectException(DateTimeFactoryException::class);
51
        $this->expectExceptionMessage(
52
            sprintf(
53
                'The \'dateTimeClassName\' parameter must be a class that implements \'%s\'',
54
                \DateTimeInterface::class
55
            )
56
        );
57
58
        /* @phpstan-ignore-next-line */
59
        new DateTimeFactory($this->dateTimeZoneFactory, $dateTimeClassName);
60
    }
61
62
    /**
63
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
64
     *
65
     * @dataProvider getCreateDateTimeData
66
     *
67
     * @throws DateTimeFactoryException
68
     * @throws \Exception
69
     */
70
    public function testCreateDateTime(?string $spec, string|\DateTimeZone|null $timeZone = null): void
71
    {
72
        $factory = new DateTimeFactory();
73
74
        $expectedDateTime = new \DateTime(
75
            $spec ?? 'now',
76
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
77
        );
78
79
        $dateTime = $factory->createDateTime($spec, $timeZone);
80
81
        $this->assertDateTime($expectedDateTime, $dateTime);
82
    }
83
84
    /**
85
     * @return array<int, mixed>
86
     */
87
    public function getCreateDateTimeData(): array
88
    {
89
        return [
90
            [
91
                null,
92
            ],
93
94
            [
95
                'now',
96
            ],
97
98
            [
99
                'now',
100
                'Europe/London',
101
            ],
102
103
            [
104
                '2019-05-14 12:33:00',
105
            ],
106
107
            [
108
                '2019-08-14 17:34:55',
109
                'UTC',
110
            ],
111
112
            [
113
                '2020-08-22 14:43:12',
114
                null,
115
            ],
116
117
            [
118
                '2020-08-22 14:44:37',
119
                new \DateTimeZone('Europe/London'),
120
            ],
121
        ];
122
    }
123
124
    /**
125
     * Ensure that if the DateTime cannot be created because the provided $spec is invalid, a new
126
     * DateTimeFactoryException will be thrown
127
     *
128
     * @throws DateTimeFactoryException
129
     */
130
    public function testCreateDateTimeWillThrowDateTimeFactoryExceptionForInvalidDateTimeSpec(): void
131
    {
132
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
133
134
        $spec = 'foo'; // invalid argument
135
136
        $this->expectException(DateTimeFactoryException::class);
137
        $this->expectExceptionMessage(
138
            sprintf('Failed to create a valid \DateTime instance using \'%s\'', $spec),
139
        );
140
141
        $factory->createDateTime($spec);
142
    }
143
144
    /**
145
     * Assert that a DateTimeFactoryException will be thrown when providing an invalid \DateTimeZone object to
146
     * createFromFormat()
147
     *
148
     * @throws DateTimeFactoryException
149
     */
150
    public function testCreateFromFormatWillCatchAndReThrowException(): void
151
    {
152
        $factory = new DateTimeFactory($this->dateTimeZoneFactory);
153
154
        $spec = '2021-05-01 23:38:12';
155
        $format = 'Y-m-d H:i:s';
156
        $timeZone = 'UTC';
157
158
        $exceptionCode = 123;
159
        $exception = new DateTimeZoneFactoryException('Test exception message', $exceptionCode);
160
161
        $this->dateTimeZoneFactory->expects($this->once())
0 ignored issues
show
The method expects() does not exist on Arp\DateTime\DateTimeZoneFactoryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
        $this->dateTimeZoneFactory->/** @scrutinizer ignore-call */ 
162
                                    expects($this->once())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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