CalendarConverter::toCdbCalendar()   B
last analyzed

Complexity

Conditions 10
Paths 7

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 55
rs 7.1151
c 0
b 0
f 0
cc 10
nc 7
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace CultuurNet\UDB3\Calendar;
4
5
use Cake\Chronos\Chronos;
6
use CultureFeed_Cdb_Data_Calendar_OpeningTime;
7
use CultureFeed_Cdb_Data_Calendar_Period;
8
use CultureFeed_Cdb_Data_Calendar_PeriodList;
9
use CultureFeed_Cdb_Data_Calendar_Permanent;
10
use CultureFeed_Cdb_Data_Calendar_SchemeDay;
11
use CultureFeed_Cdb_Data_Calendar_Timestamp;
12
use CultureFeed_Cdb_Data_Calendar_TimestampList;
13
use CultureFeed_Cdb_Data_Calendar_Weekscheme;
14
use CultuurNet\UDB3\CalendarInterface;
15
use CultuurNet\UDB3\CalendarType;
16
use DateInterval;
17
use DateTimeInterface;
18
use InvalidArgumentException;
19
use League\Period\Period;
20
21
class CalendarConverter implements CalendarConverterInterface
22
{
23
    /**
24
     * @var \DateTimeZone
25
     */
26
    private $cdbTimezone;
27
28
    public function __construct(\DateTimeZone $cdbTimezone = null)
29
    {
30
        if (is_null($cdbTimezone)) {
31
            $cdbTimezone = new \DateTimeZone('Europe/Brussels');
32
        }
33
34
        $this->cdbTimezone = $cdbTimezone;
35
    }
36
37
    /**
38
     * @inheritdoc
39
     */
40
    public function toCdbCalendar(CalendarInterface $calendar)
41
    {
42
        $weekScheme = $this->getWeekScheme($calendar);
43
        $calendarType = (string) $calendar->getType();
44
45
        switch ($calendarType) {
46
            case CalendarType::MULTIPLE:
47
                $cdbCalendar = new CultureFeed_Cdb_Data_Calendar_TimestampList();
48
                $index = 1;
49
                foreach ($calendar->getTimestamps() as $timestamp) {
50
                    $currentCount = $this->countTimestamps($cdbCalendar);
51
                    $cdbCalendar = $this->createTimestampCalendar(
52
                        $this->configureCdbTimezone($timestamp->getStartDate()),
53
                        $this->configureCdbTimezone($timestamp->getEndDate()),
54
                        $cdbCalendar,
55
                        $index
56
                    );
57
                    $newCount = $this->countTimestamps($cdbCalendar);
58
                    if ($currentCount - $newCount !== -1) {
59
                        $index++;
60
                    }
61
                }
62
                break;
63
            case CalendarType::SINGLE:
64
                $cdbCalendar = $this->createTimestampCalendar(
65
                    $this->configureCdbTimezone($calendar->getStartDate()),
66
                    $this->configureCdbTimezone($calendar->getEndDate()),
67
                    new CultureFeed_Cdb_Data_Calendar_TimestampList(),
68
                    1
69
                );
70
                break;
71
            case CalendarType::PERIODIC:
72
                $cdbCalendar = new CultureFeed_Cdb_Data_Calendar_PeriodList();
73
74
                $startDate = $this->configureCdbTimezone($calendar->getStartDate())->format('Y-m-d');
75
                $endDate = $this->configureCdbTimezone($calendar->getEndDate())->format('Y-m-d');
76
77
                $period = new CultureFeed_Cdb_Data_Calendar_Period($startDate, $endDate);
78
                if (!empty($weekScheme) && !empty($weekScheme->getDays())) {
79
                    $period->setWeekScheme($weekScheme);
80
                }
81
                $cdbCalendar->add($period);
82
                break;
83
            case CalendarType::PERMANENT:
84
                $cdbCalendar = new CultureFeed_Cdb_Data_Calendar_Permanent();
85
                if (!empty($weekScheme)) {
86
                    $cdbCalendar->setWeekScheme($weekScheme);
87
                }
88
                break;
89
            default:
90
                $cdbCalendar = new CultureFeed_Cdb_Data_Calendar_Permanent();
91
        }
92
93
        return $cdbCalendar;
94
    }
95
96
    /**
97
     * @param CultureFeed_Cdb_Data_Calendar_TimestampList $timestamps
98
     * @return int
99
     */
100
    private function countTimestamps(CultureFeed_Cdb_Data_Calendar_TimestampList $timestamps)
101
    {
102
        $numberOfTimestamps =  iterator_count($timestamps);
103
        $timestamps->rewind();
104
105
        return $numberOfTimestamps;
106
    }
107
108
    /**
109
     * @param \CultuurNet\UDB3\CalendarInterface $itemCalendar
110
     * @return CultureFeed_Cdb_Data_Calendar_Weekscheme|null
111
     * @throws \Exception
112
     */
113
    private function getWeekScheme(CalendarInterface $itemCalendar)
114
    {
115
        // Store opening hours.
116
        $openingHours = $itemCalendar->getOpeningHours();
117
        $weekScheme = null;
118
119
        if (!empty($openingHours)) {
120
            $weekScheme = new CultureFeed_Cdb_Data_Calendar_Weekscheme();
121
122
            // Multiple opening times can happen on same day. Store them in array.
123
            $openingTimesPerDay = array(
124
                'monday' => array(),
125
                'tuesday' => array(),
126
                'wednesday' => array(),
127
                'thursday' => array(),
128
                'friday' => array(),
129
                'saturday' => array(),
130
                'sunday' => array(),
131
            );
132
133
            foreach ($openingHours as $openingHour) {
134
                // In CDB2 every day needs to be a seperate entry.
135
                if (is_array($openingHour)) {
136
                    $openingHour = (object) $openingHour;
137
                }
138
                foreach ($openingHour->getDayOfWeekCollection()->getDaysOfWeek() as $day) {
139
                    $openingTimesPerDay[$day->toNative()][] = new CultureFeed_Cdb_Data_Calendar_OpeningTime(
140
                        $openingHour->getOpens()->toNativeString() . ':00',
141
                        $openingHour->getCloses()->toNativeString() . ':00'
142
                    );
143
                }
144
            }
145
146
            // Create the opening times correctly
147
            foreach ($openingTimesPerDay as $day => $openingTimes) {
148
                // Empty == closed.
149
                if (empty($openingTimes)) {
150
                    $openingInfo = new CultureFeed_Cdb_Data_Calendar_SchemeDay(
151
                        $day,
152
                        CultureFeed_Cdb_Data_Calendar_SchemeDay::SCHEMEDAY_OPEN_TYPE_CLOSED
153
                    );
154
                } else {
155
                    // Add all opening times.
156
                    $openingInfo = new CultureFeed_Cdb_Data_Calendar_SchemeDay(
157
                        $day,
158
                        CultureFeed_Cdb_Data_Calendar_SchemeDay::SCHEMEDAY_OPEN_TYPE_OPEN
159
                    );
160
                    foreach ($openingTimes as $openingTime) {
161
                        $openingInfo->addOpeningTime($openingTime);
162
                    }
163
                }
164
                $weekScheme->setDay($day, $openingInfo);
165
            }
166
        }
167
168
        return $weekScheme;
169
    }
170
171
    /**
172
     * @param DateTimeInterface $startDate
173
     * @param DateTimeInterface $endDate
174
     * @param CultureFeed_Cdb_Data_Calendar_TimestampList $calendar
175
     * @param Integer|null $index
176
     *
177
     * @return CultureFeed_Cdb_Data_Calendar_TimestampList
178
     */
179
    private function createTimestampCalendar(
180
        DateTimeInterface $startDate,
181
        DateTimeInterface $endDate,
182
        CultureFeed_Cdb_Data_Calendar_TimestampList $calendar,
183
        $index = null
184
    ) {
185
        // Make a clone of the original calendar to avoid updating input param.
186
        $newCalendar = clone $calendar;
187
188
        $first24Hours = Period::createFromDuration($startDate, new DateInterval('P1D'));
189
190
        // Easy case an no seconds needed for indexing.
191
        if ($first24Hours->contains($endDate)) {
192
            $newCalendar->add(
193
                new CultureFeed_Cdb_Data_Calendar_Timestamp(
194
                    $startDate->format('Y-m-d'),
195
                    $this->formatDateTimeAsCdbTime($startDate),
196
                    $this->formatDateTimeAsCdbTime($endDate)
197
                )
198
            );
199
        } elseif (is_int($index)) {
200
            // Complex case and seconds needed for indexing.
201
            $period = new Period($startDate, $endDate);
202
203
            $startTimestamp = new CultureFeed_Cdb_Data_Calendar_Timestamp(
204
                $startDate->format('Y-m-d'),
205
                $this->formatDateTimeAsCdbTime($startDate, $index)
206
            );
207
208
            $endTimestamp = new CultureFeed_Cdb_Data_Calendar_Timestamp(
209
                $endDate->format('Y-m-d'),
210
                $this->createIndexedTimeString($index),
211
                $this->formatDateTimeAsCdbTime($endDate)
212
            );
213
214
            $untilEndOfSecondDay = new Period(
215
                $startDate,
216
                Chronos::instance($startDate)->addDay()->endOfDay()
217
            );
218
219
            if ($untilEndOfSecondDay->contains($endDate)) {
220
                $fillerTimestamps = [];
221
            } else {
222
                $days = iterator_to_array($period->getDatePeriod('1 DAY'));
223
                $fillerTimestamps = array_map(
224
                    function (DateTimeInterface $dateTime) use ($index) {
225
                        return new CultureFeed_Cdb_Data_Calendar_Timestamp(
226
                            $dateTime->format('Y-m-d'),
227
                            $this->createIndexedTimeString($index)
228
                        );
229
                    },
230
                    array_slice($days, 1, count($days) === 2 ? 2 : -1)
231
                );
232
            }
233
234
            $newCalendar = array_reduce(
235
                array_merge([$startTimestamp], $fillerTimestamps, [$endTimestamp]),
236
                function (CultureFeed_Cdb_Data_Calendar_TimestampList $calendar, $timestamp) {
237
                    $calendar->add($timestamp);
238
                    return $calendar;
239
                },
240
                $newCalendar
241
            );
242
        }
243
244
        return $newCalendar;
245
    }
246
247
    /**
248
     * @param DateTimeInterface $timestamp
249
     * @param integer|null $index
250
     * @return null|string
251
     */
252
    private function formatDateTimeAsCdbTime(DateTimeInterface $timestamp, $index = null)
253
    {
254
        if (is_int($index) && $index > 59) {
255
            throw new InvalidArgumentException('The CDB time index should not be higher than 59!');
256
        }
257
258
        $time = is_int($index)
259
            ? $timestamp->format('H:i') . ':' . str_pad((string) $index, 2, '0', STR_PAD_LEFT)
260
            : $timestamp->format('H:i:s');
261
262
        return $time;
263
    }
264
265
    /**
266
     * @param int $index
267
     * @return string
268
     */
269
    private function createIndexedTimeString($index)
270
    {
271
        return '00:00:' . str_pad((string) $index, 2, '0', STR_PAD_LEFT);
272
    }
273
274
    /**
275
     * DateTimeInterface has no setTimezone() method, so we need to convert it to a DateTimeImmutable object
276
     * first using Chronos.
277
     *
278
     * @param DateTimeInterface $dateTime
279
     * @return DateTimeInterface
280
     */
281
    private function configureCdbTimezone(\DateTimeInterface $dateTime)
282
    {
283
        return Chronos::instance($dateTime)->setTimezone($this->cdbTimezone);
284
    }
285
}
286