Completed
Pull Request — master (#454)
by Jonas
05:25 queued 02:08
created

CalendarFactory::getLastTimestamp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
namespace CultuurNet\UDB3;
4
5
use Cake\Chronos\Chronos;
6
use CultureFeed_Cdb_Data_Calendar_Timestamp;
7
use CultuurNet\UDB3\Calendar\DayOfWeek;
8
use CultuurNet\UDB3\Calendar\DayOfWeekCollection;
9
use CultuurNet\UDB3\Calendar\OpeningHour;
10
use CultuurNet\UDB3\Calendar\OpeningTime;
11
use CultuurNet\UDB3\Cdb\DateTimeFactory;
12
use DateTimeInterface;
13
14
class CalendarFactory implements CalendarFactoryInterface
15
{
16
    /**
17
     * @inheritdoc
18
     */
19
    public function createFromCdbCalendar(\CultureFeed_Cdb_Data_Calendar $cdbCalendar)
20
    {
21
        //
22
        // Get the start day.
23
        //
24
        $cdbCalendar->rewind();
25
        $startDateString = '';
26
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
27
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
28
            $period = $cdbCalendar->current();
29
            $startDateString = $period->getDateFrom() . 'T00:00:00';
30
        } elseif ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
31
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
32
            $timestamp = $cdbCalendar->current();
33
            if ($timestamp->getStartTime()) {
34
                $startDateString = $timestamp->getDate() . 'T' . substr($timestamp->getStartTime(), 0, 5) . ':00';
35
            } else {
36
                $startDateString = $timestamp->getDate() . 'T00:00:00';
37
            }
38
        }
39
        $startDate = !empty($startDateString) ? DateTimeFactory::dateTimeFromDateString($startDateString) : null;
40
41
        //
42
        // Get the end day.
43
        //
44
        $cdbCalendar->rewind();
45
        $endDateString = '';
46
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
47
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
48
            $period = $cdbCalendar->current();
49
            $endDateString = $period->getDateTo() . 'T00:00:00';
50
        } elseif ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
51
            $firstTimestamp = $cdbCalendar->current();
52
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
53
            $cdbCalendarAsArray = iterator_to_array($cdbCalendar);
54
            $timestamp = $this->getLastTimestamp($cdbCalendarAsArray, $firstTimestamp);
55
            if ($timestamp->getEndTime()) {
56
                $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
57
            } else {
58
                $endTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
59
                $endDateString = $timestamp->getDate() . 'T' . $endTime;
60
            }
61
        }
62
        $endDate = !empty($endDateString) ? DateTimeFactory::dateTimeFromDateString($endDateString) : null;
63
64
        //
65
        // Get the time stamps.
66
        //
67
        $cdbCalendar->rewind();
68
        $timestamps = [];
69
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
70
            $splitPeriods = [];
71
            while ($cdbCalendar->valid()) {
72
                /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
73
                $timestamp = $cdbCalendar->current();
74
                $cdbCalendar->next();
75
76
                $startTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
77
                $startDateString = $timestamp->getDate() . 'T' . $startTime;
78
79
                if ($timestamp->getEndTime()) {
80
                    $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
81
                } else {
82
                    $endDateString = $timestamp->getDate() . 'T' . $startTime;
83
                }
84
85
                $timestamp = $this->createTimestamp(
86
                    $startDateString,
87
                    $endDateString
88
                );
89
90
                $index = intval($timestamp->getStartDate()->format('s'));
91
                if ($index > 0) {
92
                    $splitPeriods[$index][] = $timestamp;
93
                } else {
94
                    $timestamps[] = $timestamp;
95
                }
96
            }
97
98
            $periods = array_map(
99
                function (array $periodParts) {
100
                    $firstPart = array_shift($periodParts);
101
                    $lastPart = array_pop($periodParts);
102
                    return new Timestamp(
103
                        Chronos::instance($firstPart->getStartDate())->second(0),
104
                        $lastPart ? $lastPart->getEndDate() : $firstPart->getEndDate()
105
                    );
106
                },
107
                $splitPeriods
108
            );
109
110
            $timestamps = array_merge($timestamps, $periods);
111
        }
112
113
        //
114
        // Get the opening hours.
115
        //
116
        $cdbCalendar->rewind();
117
        $openingHours = [];
118
119
        $weekSchema = null;
120
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
121
            $period = $cdbCalendar->current();
122
            $weekSchema = $period->getWeekScheme();
123
        } elseif ($cdbCalendar instanceof  \CultureFeed_Cdb_Data_Calendar_Permanent) {
124
            $weekSchema = $cdbCalendar->getWeekScheme();
125
        }
126
127
        if ($weekSchema) {
128
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekSchema);
129
        }
130
131
        if (isset($startDate) && isset($endDate)) {
132
            $calendarTimeSpan = $this->createChronologicalTimestamp($startDate, $endDate);
133
        }
134
135
        //
136
        // Get the calendar type.
137
        //
138
        $calendarType = null;
139
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_Permanent) {
140
            $calendarType = CalendarType::PERMANENT();
141
        } elseif ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
142
            $calendarType = CalendarType::PERIODIC();
143
        } elseif ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
144
            $calendarType = CalendarType::SINGLE();
145
            if (count($timestamps) > 1) {
146
                $calendarType = CalendarType::MULTIPLE();
147
            }
148
        }
149
150
        //
151
        // Create the calendar value object.
152
        //
153
        return new Calendar(
154
            $calendarType,
0 ignored issues
show
Bug introduced by
It seems like $calendarType defined by null on line 138 can be null; however, CultuurNet\UDB3\Calendar::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
155
            isset($calendarTimeSpan) ? $calendarTimeSpan->getStartDate() : null,
156
            isset($calendarTimeSpan) ? $calendarTimeSpan->getEndDate() : null,
157
            $timestamps,
158
            $openingHours
159
        );
160
    }
161
162
    /**
163
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme|null $weekScheme
164
     * @return Calendar
165
     */
166
    public function createFromWeekScheme(
167
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme = null
168
    ) {
169
        $openingHours = [];
170
171
        if ($weekScheme) {
172
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekScheme);
173
        }
174
175
        return new Calendar(
176
            CalendarType::PERMANENT(),
177
            null,
178
            null,
179
            [],
180
            $openingHours
181
        );
182
    }
183
184
    /**
185
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
186
     * @return OpeningHour[]
187
     */
188
    private function createOpeningHoursFromWeekScheme(
189
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
190
    ) {
191
        $openingHours = [];
192
193
        foreach ($weekScheme->getDays() as $day) {
194
            if ($day->isOpen()) {
195
                /** @var \CultureFeed_Cdb_Data_Calendar_OpeningTime[] $openingTimes */
196
                $openingTimes = $day->getOpeningTimes();
197
198
                // A day could be marked as open but without any hours.
199
                // This means all day open but needs to be mapped to 00:00:00.
200
                if (count($openingTimes) === 0) {
201
                    $openingTimes[] = new \CultureFeed_Cdb_Data_Calendar_OpeningTime(
202
                        '00:00:00',
203
                        '00:00:00'
204
                    );
205
                }
206
207
                foreach ($openingTimes as $openingTime) {
208
                    $opens = \DateTime::createFromFormat(
209
                        'H:i:s',
210
                        $openingTime->getOpenFrom()
211
                    );
212
                    $closes = \DateTime::createFromFormat(
213
                        'H:i:s',
214
                        $openingTime->getOpenTill()
215
                    );
216
217
                    $openingHour = new OpeningHour(
218
                        OpeningTime::fromNativeDateTime($opens),
0 ignored issues
show
Security Bug introduced by
It seems like $opens defined by \DateTime::createFromFor...ingTime->getOpenFrom()) on line 208 can also be of type false; however, CultuurNet\UDB3\Calendar...e::fromNativeDateTime() does only seem to accept object<DateTimeInterface>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
219
                        $closes ? OpeningTime::fromNativeDateTime($closes) : OpeningTime::fromNativeDateTime($opens),
0 ignored issues
show
Security Bug introduced by
It seems like $opens defined by \DateTime::createFromFor...ingTime->getOpenFrom()) on line 208 can also be of type false; however, CultuurNet\UDB3\Calendar...e::fromNativeDateTime() does only seem to accept object<DateTimeInterface>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
220
                        new DayOfWeekCollection(DayOfWeek::fromNative($day->getDayName()))
221
                    );
222
223
                    $openingHours = $this->addToOpeningHours($openingHour, ...$openingHours);
224
                }
225
            }
226
        }
227
228
        return $openingHours;
229
    }
230
231
    /**
232
     * @param OpeningHour $newOpeningHour
233
     * @param OpeningHour[] ...$openingHours
234
     * @return OpeningHour[]
235
     */
236
    private function addToOpeningHours(
237
        OpeningHour $newOpeningHour,
238
        OpeningHour ...$openingHours
239
    ) {
240
        foreach ($openingHours as $openingHour) {
241
            if ($openingHour->hasEqualHours($newOpeningHour)) {
0 ignored issues
show
Bug introduced by
The method hasEqualHours cannot be called on $openingHour (of type array<integer,object<Cul...\Calendar\OpeningHour>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
242
                $openingHour->addDayOfWeekCollection(
0 ignored issues
show
Bug introduced by
The method addDayOfWeekCollection cannot be called on $openingHour (of type array<integer,object<Cul...\Calendar\OpeningHour>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
243
                    $newOpeningHour->getDayOfWeekCollection()
244
                );
245
                return $openingHours;
246
            }
247
        }
248
249
        $openingHours[] = $newOpeningHour;
250
        return $openingHours;
251
    }
252
253
    /**
254
     * @param string $startDateString
255
     * @param string $endDateString
256
     * @return Timestamp
257
     */
258
    private function createTimestamp(
259
        $startDateString,
260
        $endDateString
261
    ) {
262
        $startDate = DateTimeFactory::dateTimeFromDateString($startDateString);
263
        $endDate = DateTimeFactory::dateTimeFromDateString($endDateString);
264
265
        return $this->createChronologicalTimestamp($startDate, $endDate);
266
    }
267
268
    /**
269
     * End date might be before start date in cdbxml when event takes place
270
     * between e.g. 9 PM and 3 AM (the next day). To keep the dates chronological we push the end to the next day.
271
     *
272
     * If the end dates does not make any sense at all, it is forced to the start date.
273
     *
274
     * @param DateTimeInterface $start
275
     * @param DateTimeInterface $end
276
     *
277
     * @return Timestamp
278
     */
279
    private function createChronologicalTimestamp(DateTimeInterface $start, DateTimeInterface $end)
280
    {
281
        $startDate = Chronos::instance($start);
282
        $endDate = Chronos::instance($end);
283
284
        if ($startDate->isSameDay($endDate) && $endDate->lt($startDate)) {
285
            $endDate = $endDate->addDay();
286
        }
287
288
        if ($endDate->lt($startDate)) {
289
            $endDate = $startDate;
290
        }
291
292
        return new Timestamp($startDate, $endDate);
293
    }
294
295
    /**
296
     * @param CultureFeed_Cdb_Data_Calendar_Timestamp[] $timestampList
297
     * @return CultureFeed_Cdb_Data_Calendar_Timestamp
298
     */
299
    private function getLastTimestamp(array $timestampList, CultureFeed_Cdb_Data_Calendar_Timestamp $default)
300
    {
301
        $lastTimestamp = $default;
302
        foreach ($timestampList as $timestamp) {
303
            $currentEndDate = Chronos::parse($lastTimestamp->getEndDate());
304
            $endDate = Chronos::parse($timestamp->getEndDate());
305
            if ($currentEndDate < $endDate) {
306
                $lastTimestamp = $timestamp;
307
            }
308
        }
309
310
        return $lastTimestamp;
311
    }
312
}
313