Completed
Push — master ( dc6f43...d1eae8 )
by Luc
07:42 queued 03:02
created

CalendarFactory::createChronologicalTimestamp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
1
<?php
2
3
namespace CultuurNet\UDB3;
4
5
use Cake\Chronos\Chronos;
6
use CultuurNet\UDB3\Calendar\DayOfWeek;
7
use CultuurNet\UDB3\Calendar\DayOfWeekCollection;
8
use CultuurNet\UDB3\Calendar\OpeningHour;
9
use CultuurNet\UDB3\Calendar\OpeningTime;
10
use CultuurNet\UDB3\Cdb\DateTimeFactory;
11
use DateTimeInterface;
12
13
class CalendarFactory implements CalendarFactoryInterface
14
{
15
    /**
16
     * @inheritdoc
17
     */
18
    public function createFromCdbCalendar(\CultureFeed_Cdb_Data_Calendar $cdbCalendar)
19
    {
20
        //
21
        // Get the calendar type.
22
        //
23
        $calendarType = '';
24
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_Permanent) {
25
            $calendarType = 'permanent';
26
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
27
            $calendarType = 'periodic';
28
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
29
            $calendarType = 'single';
30
            if (iterator_count($cdbCalendar) > 1) {
31
                $calendarType = 'multiple';
32
            }
33
        }
34
35
        //
36
        // Get the start day.
37
        //
38
        $cdbCalendar->rewind();
39
        $startDateString = '';
40
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
41
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
42
            $period = $cdbCalendar->current();
43
            $startDateString = $period->getDateFrom() . 'T00:00:00';
44
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
45
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
46
            $timestamp = $cdbCalendar->current();
47
            if ($timestamp->getStartTime()) {
48
                $startDateString = $timestamp->getDate() . 'T' . substr($timestamp->getStartTime(), 0, 5) . ':00';
49
            } else {
50
                $startDateString = $timestamp->getDate() . 'T00:00:00';
51
            }
52
        }
53
        $startDate = !empty($startDateString) ? DateTimeFactory::dateTimeFromDateString($startDateString) : null;
54
55
        //
56
        // Get the end day.
57
        //
58
        $cdbCalendar->rewind();
59
        $endDateString = '';
60
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
61
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
62
            $period = $cdbCalendar->current();
63
            $endDateString = $period->getDateTo() . 'T00:00:00';
64
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
65
            $firstTimestamp = $cdbCalendar->current();
66
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
67
            $cdbCalendarAsArray = iterator_to_array($cdbCalendar);
68
            $timestamp = iterator_count($cdbCalendar) > 1 ? end($cdbCalendarAsArray) : $firstTimestamp;
69
            if ($timestamp->getEndTime()) {
70
                $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
71
            } else {
72
                $endTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
73
                $endDateString = $timestamp->getDate() . 'T' . $endTime;
74
            }
75
        }
76
        $endDate = !empty($endDateString) ? DateTimeFactory::dateTimeFromDateString($endDateString) : null;
77
78
        //
79
        // Get the time stamps.
80
        //
81
        $cdbCalendar->rewind();
82
        $timestamps = [];
83
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
84
            $splitPeriods = [];
85
            while ($cdbCalendar->valid()) {
86
                /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
87
                $timestamp = $cdbCalendar->current();
88
                $cdbCalendar->next();
89
90
                $startTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
91
                $startDateString = $timestamp->getDate() . 'T' . $startTime;
92
93
                if ($timestamp->getEndTime()) {
94
                    $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
95
                } else {
96
                    $endDateString = $timestamp->getDate() . 'T' . $startTime;
97
                }
98
99
                $timestamp = $this->createTimestamp(
100
                    $startDateString,
101
                    $endDateString
102
                );
103
104
                $index = intval($timestamp->getStartDate()->format('s'));
105
                if ($index > 0) {
106
                    $splitPeriods[$index][] = $timestamp;
107
                } else {
108
                    $timestamps[] = $timestamp;
109
                }
110
            }
111
112
            $periods = array_map(
113
                function (array $periodParts) {
114
                    $firstPart = array_shift($periodParts);
115
                    $lastPart = array_pop($periodParts);
116
                    return new Timestamp(
117
                        Chronos::instance($firstPart->getStartDate())->second(0),
118
                        $lastPart->getEndDate()
119
                    );
120
                },
121
                $splitPeriods
122
            );
123
124
            $timestamps = array_merge($timestamps, $periods);
125
        }
126
127
        //
128
        // Get the opening hours.
129
        //
130
        $cdbCalendar->rewind();
131
        $openingHours = [];
132
133
        $weekSchema = null;
134
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
135
            $period = $cdbCalendar->current();
136
            $weekSchema = $period->getWeekScheme();
137
        } else if ($cdbCalendar instanceof  \CultureFeed_Cdb_Data_Calendar_Permanent) {
138
            $weekSchema = $cdbCalendar->getWeekScheme();
139
        }
140
141
        if ($weekSchema) {
142
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekSchema);
143
        }
144
145
        if (isset($startDate) && isset($endDate)) {
146
            $calendarTimeSpan = $this->createChronologicalTimestamp($startDate, $endDate);
147
        }
148
149
        //
150
        // Create the calendar value object.
151
        //
152
        return new Calendar(
153
            CalendarType::fromNative($calendarType),
154
            isset($calendarTimeSpan) ? $calendarTimeSpan->getStartDate() : null,
155
            isset($calendarTimeSpan) ? $calendarTimeSpan->getEndDate() : null,
156
            $timestamps,
157
            $openingHours
158
        );
159
    }
160
161
    /**
162
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme|null $weekScheme
163
     * @return Calendar
164
     */
165
    public function createFromWeekScheme(
166
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme = null
167
    ) {
168
        $openingHours = [];
169
170
        if ($weekScheme) {
171
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekScheme);
172
        }
173
174
        return new Calendar(
175
            CalendarType::PERMANENT(),
176
            null,
177
            null,
178
            [],
179
            $openingHours
180
        );
181
    }
182
183
    /**
184
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
185
     * @return OpeningHour[]
186
     */
187
    private function createOpeningHoursFromWeekScheme(
188
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
189
    ) {
190
        $openingHours = [];
191
192
        foreach ($weekScheme->getDays() as $day) {
193
            if ($day->isOpen()) {
194
                /** @var \CultureFeed_Cdb_Data_Calendar_OpeningTime[] $openingTimes */
195
                $openingTimes = $day->getOpeningTimes();
196
197
                // A day could be marked as open but without any hours.
198
                // This means all day open but needs to be mapped to 00:00:00.
199
                if (count($openingTimes) === 0) {
200
                    $openingTimes[] = new \CultureFeed_Cdb_Data_Calendar_OpeningTime(
201
                        '00:00:00',
202
                        '00:00:00'
203
                    );
204
                }
205
206
                foreach ($openingTimes as $openingTime) {
207
                    $opens = \DateTime::createFromFormat(
208
                        'H:i:s',
209
                        $openingTime->getOpenFrom()
210
                    );
211
                    $closes = \DateTime::createFromFormat(
212
                        'H:i:s',
213
                        $openingTime->getOpenTill()
214
                    );
215
216
                    $openingHour = new OpeningHour(
217
                        OpeningTime::fromNativeDateTime($opens),
0 ignored issues
show
Security Bug introduced by
It seems like $opens defined by \DateTime::createFromFor...ingTime->getOpenFrom()) on line 207 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...
218
                        $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 207 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
                        new DayOfWeekCollection(DayOfWeek::fromNative($day->getDayName()))
220
                    );
221
222
                    $openingHours = $this->addToOpeningHours($openingHour, ...$openingHours);
223
                }
224
            }
225
        }
226
227
        return $openingHours;
228
    }
229
230
    /**
231
     * @param OpeningHour $newOpeningHour
232
     * @param OpeningHour[] ...$openingHours
233
     * @return OpeningHour[]
234
     */
235
    private function addToOpeningHours(
236
        OpeningHour $newOpeningHour,
237
        OpeningHour ...$openingHours
238
    ) {
239
        foreach ($openingHours as $openingHour) {
240
            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...
241
                $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...
242
                    $newOpeningHour->getDayOfWeekCollection()
243
                );
244
                return $openingHours;
245
            }
246
        }
247
248
        $openingHours[] = $newOpeningHour;
249
        return $openingHours;
250
    }
251
252
    /**
253
     * @param string $startDateString
254
     * @param string $endDateString
255
     * @return Timestamp
256
     */
257
    private function createTimestamp(
258
        $startDateString,
259
        $endDateString
260
    ) {
261
        $startDate = DateTimeFactory::dateTimeFromDateString($startDateString);
262
        $endDate = DateTimeFactory::dateTimeFromDateString($endDateString);
263
264
        return $this->createChronologicalTimestamp($startDate, $endDate);
265
    }
266
267
    /**
268
     * End date might be before start date in cdbxml when event takes place
269
     * between e.g. 9 PM and 3 AM (the next day). To keep the dates chronological we push the end to the next day.
270
     *
271
     * If the end dates does not make any sense at all, it is forced to the start date.
272
     *
273
     * @param DateTimeInterface $start
274
     * @param DateTimeInterface $end
275
     *
276
     * @return Timestamp
277
     */
278
    private function createChronologicalTimestamp(DateTimeInterface $start, DateTimeInterface $end)
279
    {
280
        $startDate = Chronos::instance($start);
281
        $endDate = Chronos::instance($end);
282
283
        if ($startDate->isSameDay($endDate) && $endDate->lt($startDate)) {
284
            $endDate = $endDate->addDay();
285
        }
286
287
        if ($endDate->lt($startDate)) {
288
            $endDate = $startDate;
289
        }
290
291
        return new Timestamp($startDate, $endDate);
292
    }
293
}
294