GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

EventItemizer   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 248
Duplicated Lines 12.1 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 1
dl 30
loc 248
rs 9.84
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C itemizeEvent() 9 78 14
B createDayGranural() 21 52 6
C createHourlyGranular() 0 70 11

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * @file
5
 * Class EventItemizer
6
 */
7
8
namespace Roomify\Bat\Event;
9
10
use Roomify\Bat\Event\EventInterface;
11
use Roomify\Bat\Unit\Unit;
12
13
/**
14
 * The EventItemizer class does the hard work of splitting an event into discrete time
15
 * units in the following structure
16
 * [BAT_DAY][year][month][day][value]
17
 * [BAT_HOUR][year][month][day][hour][value]
18
 * [BAT_MINUTE][year][month][hour][minute][value]
19
 *
20
 * This data structure allows to quickly retrieve the state of a unit for a provided time
21
 * period. The value is either the default value of the unit or the value that the event
22
 * introduced. If the value in either BAT_DAY or BAT_HOUR is -1 it means that that specific
23
 * day or that specific hour are non-determinant. This means that in order to determine the
24
 * value of the event for that point in time we need to look at a lower level of granularity.
25
 *
26
 * Example - consider breaking up the following event
27
 *
28
 * start-date: 2016-01-01 1210
29
 * end-date: 2016-01-03 1210
30
 * value: 10
31
 *
32
 * [BAT_DAY][2016][01][d1][-1] - The first day starts at 1210 so the DAY array is not enough
33
 * [BAT_DAY][2016][01][d2][10] - The second day is a full day at the same value of 10
34
 * [BAT_DAY][2016][01][d3][-1] - The last day is no a full day so the day array in non-determinant
35
 * [BAT_HOUR][2016][01][d1][h12][-1] - The first hour of the event starts at 10 minutes so the hour is non-determinant
36
 * [BAT_HOUR][2016][01][d1][h13][10]
37
 * [BAT_HOUR][2016][01][d1][h14][10]
38
 * [BAT_HOUR][2016][01][d1][h15][10]
39
 * [BAT_HOUR][2016][01][d1][h16][10]
40
 * [BAT_HOUR][2016][01][d1][h17][10]
41
 * [BAT_HOUR][2016][01][d1][h18][10]
42
 * [BAT_HOUR][2016][01][d1][h19][10]
43
 * [BAT_HOUR][2016][01][d1][h20][10]
44
 * [BAT_HOUR][2016][01][d1][h21][10]
45
 * [BAT_HOUR][2016][01][d1][h22][10]
46
 * [BAT_HOUR][2016][01][d1][h23][10]
47
 *                                    - we don't need to state anything about hours on the 2nd of Jan since the day array is determinant
48
 * [BAT_HOUR][2016][01][d3][h01][10]
49
 * [BAT_HOUR][2016][01][d3][h02][10]
50
 * [BAT_HOUR][2016][01][d3][h03][10]
51
 * [BAT_HOUR][2016][01][d3][h04][10]
52
 * [BAT_HOUR][2016][01][d3][h05][10]
53
 * [BAT_HOUR][2016][01][d3][h06][10]
54
 * [BAT_HOUR][2016][01][d3][h07][10]
55
 * [BAT_HOUR][2016][01][d3][h08][10]
56
 * [BAT_HOUR][2016][01][d3][h09][10]
57
 * [BAT_HOUR][2016][01][d3][h10][10]
58
 * [BAT_HOUR][2016][01][d3][h11][10]
59
 * [BAT_HOUR][2016][01][d3][h12][-1] - The last hour of the event ends at the 10th minute so will need to minute array
60
 *
61
 * [BAT_MINUTE][2016][01][d1][h12][m00][10] - Minutes, which is the maximum granularity, are always determinant
62
 * [BAT_MINUTE][2016][01][d1][h12][m01][10]
63
 * [BAT_MINUTE][2016][01][d1][h12][m02][10]
64
 * [BAT_MINUTE][2016][01][d1][h12][m03][10]
65
 * [BAT_MINUTE][2016][01][d1][h12][m04][10]
66
 * [BAT_MINUTE][2016][01][d1][h12][m05][10]
67
 * [BAT_MINUTE][2016][01][d1][h12][m06][10]
68
 * [BAT_MINUTE][2016][01][d1][h12][m07][10]
69
 * [BAT_MINUTE][2016][01][d1][h12][m08][10]
70
 * [BAT_MINUTE][2016][01][d1][h12][m09][10]
71
 * [BAT_MINUTE][2016][01][d1][h12][m10][10]
72
 *
73
 * [BAT_MINUTE][2016][01][d3][h12][m00][10]
74
 * [BAT_MINUTE][2016][01][d3][h12][m01][10]
75
 * [BAT_MINUTE][2016][01][d3][h12][m02][10]
76
 * [BAT_MINUTE][2016][01][d3][h12][m03][10]
77
 * [BAT_MINUTE][2016][01][d3][h12][m04][10]
78
 * [BAT_MINUTE][2016][01][d3][h12][m05][10]
79
 * [BAT_MINUTE][2016][01][d3][h12][m06][10]
80
 * [BAT_MINUTE][2016][01][d3][h12][m07][10]
81
 * [BAT_MINUTE][2016][01][d3][h12][m08][10]
82
 * [BAT_MINUTE][2016][01][d3][h12][m09][10]
83
 * [BAT_MINUTE][2016][01][d3][h12][m10][10]
84
 *
85
 * Class EventItemizer
86
 * @package Roomify\Bat\Event
87
 */
88
class EventItemizer {
89
90
  const BAT_DAY = 'bat_day';
91
  const BAT_HOUR = 'bat_hour';
92
  const BAT_MINUTE = 'bat_minute';
93
  const BAT_HOURLY = 'bat_hourly';
94
  const BAT_DAILY = 'bat_daily';
95
96
  /**
97
   * @var \Roomify\Bat\Event\EventInterface
98
   */
99
  protected $event;
100
101
  /**
102
   * @var string
103
   */
104
  protected $granularity;
105
106
  public function __construct(EventInterface $event, $granularity = EventItemizer::BAT_HOURLY) {
107
    $this->event = $event;
108
    $this->granularity = $granularity;
109
  }
110
111
  /**
112
   * Transforms the event in a breakdown of days, hours and minutes with associated states.
113
   *
114
   * @return array
115
   */
116
  public function itemizeEvent() {
117
    // In order to itemize the event we cycle through each day of the event and determine
118
    // what should go in the DAY array to start with. While we could use P1M this created
119
    // problems with months like February (because the period is 30 days) so stepping through
120
    // each day is safer.
121
    $interval = new \DateInterval('P1D');
122
123
    // Set the end date to the last day of the month so that we are sure to get that last month unless
124
    // we are already dealing with the last day of the month
125
    if ($this->event->getEndDate()->format('d') != $this->event->getEndDate()->format('t')) {
126
      $adjusted_end_day = new \DateTime($this->event->getEndDate()->format('Y-n-t'));
127
    }
128
    // Deal with the special case of last day of month and daily granularity where the DatePeriod will not indicate one day unless the time is slightly different
129
    // We add a minute to compensate
130
    elseif (($this->event->getStartDate()->format('Y-m-d H:i') == $this->event->getEndDate()->format('Y-m-d H:i')) && $this->granularity == EventItemizer::BAT_DAILY) {
131
      $adjusted_end_day = new \DateTime($this->event->getEndDate()->add(new \DateInterval('PT1M'))->format('Y-m-d H:i'));
132
    }
133
    else {
134
      $adjusted_end_day = new \DateTime($this->event->getEndDate()->format('Y-m-d H:i'));
135
    }
136
137
    $daterange = new \DatePeriod($this->event->getStartDate(), $interval, $adjusted_end_day);
138
139
    $itemized = array();
140
141
    $old_month = $this->event->getStartDate()->format('Y-n');
142
143
    $start = TRUE;
144
145
    // Cycle through each month
146
    foreach ($daterange as $date) {
147
148
      // Check if we have
149
      if (($date->format('Y-n') != $old_month) || ($start)) {
150
151
        $year = $date->format("Y");
152
        $dayinterval = new \DateInterval('P1D');
153
154
        // Handle the first month
155
        if ($this->event->isFirstMonth($date)) {
156
          // If we are in the same month the end date is the end date of the event
157
          if ($this->event->isSameMonth()) {
158
            $dayrange = new \DatePeriod($this->event->getStartDate(), $dayinterval, new \DateTime($this->event->getEndDate()->format("Y-n-j 23:59:59")));
159
          } else { // alternatively it is the last day of the start month
160
            $dayrange = new \DatePeriod($this->event->getStartDate(), $dayinterval, $this->event->endMonthDate($this->event->getStartDate()));
161
          }
162 View Code Duplication
          foreach ($dayrange as $day) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
            $itemized[EventItemizer::BAT_DAY][$year][$day->format('n')]['d' . $day->format('j')] = $this->event->getValue();
164
          }
165
        }
166
167
        // Handle the last month (will be skipped if event is same month)
168
        elseif ($this->event->isLastMonth($date)) {
169
          $dayrange = new \DatePeriod(new \DateTime($date->format("Y-n-1")), $dayinterval, $this->event->getEndDate());
170 View Code Duplication
          foreach ($dayrange as $day) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
            $itemized[EventItemizer::BAT_DAY][$year][$day->format('n')]['d' . $day->format('j')] = $this->event->getValue();
172
          }
173
        }
174
175
        // We are in an in-between month - just cycle through and set dates (time on end date set to ensure it is included)
176
        else {
177
          $dayrange = new \DatePeriod(new \DateTime($date->format("Y-n-1")), $dayinterval, new \DateTime($date->format("Y-n-t 23:59:59")));
178 View Code Duplication
          foreach ($dayrange as $day) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
            $itemized[EventItemizer::BAT_DAY][$year][$day->format('n')]['d' . $day->format('j')] = $this->event->getValue();
180
          }
181
        }
182
      }
183
      $start = FALSE;
184
      $old_month = $date->format('Y-n');
185
    }
186
187
    if ($this->granularity == EventItemizer::BAT_HOURLY) {
188
      // Add granural info in
189
      $itemized = $this->createDayGranural($itemized);
190
    }
191
192
    return $itemized;
193
  }
194
195
  /**
196
   * Based on the start and end dates of the event it creates the appropriate granular events
197
   * and adds them to an array suitable for manipulating easily or storing in the database.
198
   *
199
   * @param array $itemized
200
   * @return array
201
   */
202
  private function createDayGranural($itemized = array()) {
203
    $interval = new \DateInterval('PT1M');
204
205
    $sy = $this->event->getStartDate()->format('Y');
206
    $sm = $this->event->getStartDate()->format('n');
207
    $sd = $this->event->getStartDate()->format('j');
208
209
    $ey = $this->event->getEndDate()->format('Y');
210
    $em = $this->event->getEndDate()->format('n');
211
    $ed = $this->event->getEndDate()->format('j');
212
213
    // Clone the dates otherwise changes will change the event dates themselves
214
    $start_date = clone($this->event->getStartDate());
215
    $end_date = clone($this->event->getEndDate());
216
217
    if ($this->event->isSameDay()) {
218
      if (!($this->event->getStartDate()->format('H:i') == '00:00' && $this->event->getEndDate()->format('H:i') == '23:59')) {
219
        $itemized_same_day = $this->createHourlyGranular($start_date, $end_date->add(new \DateInterval('PT1M')), $interval);
220
        $itemized[EventItemizer::BAT_DAY][$sy][$sm]['d' . $sd] = -1;
221
        $itemized[EventItemizer::BAT_HOUR][$sy][$sm]['d' . $sd] = $itemized_same_day[EventItemizer::BAT_HOUR][$sy][$sm]['d' . $sd];
222
        $itemized[EventItemizer::BAT_MINUTE][$sy][$sm]['d' . $sd] = $itemized_same_day[EventItemizer::BAT_MINUTE][$sy][$sm]['d' . $sd];
223
      }
224
    }
225
    else {
226
      // Deal with the start day unless it starts on midnight precisely at which point the whole day is booked
227 View Code Duplication
      if (!($this->event->getStartDate()->format('H:i') == '00:00')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
228
        $itemized_start = $this->createHourlyGranular($start_date, new \DateTime($start_date->format("Y-n-j 23:59:59")), $interval);
229
        $itemized[EventItemizer::BAT_DAY][$sy][$sm]['d' . $sd] = -1;
230
        $itemized[EventItemizer::BAT_HOUR][$sy][$sm]['d' . $sd] = $itemized_start[EventItemizer::BAT_HOUR][$sy][$sm]['d' . $sd];
231
        $itemized[EventItemizer::BAT_MINUTE][$sy][$sm]['d' . $sd] = $itemized_start[EventItemizer::BAT_MINUTE][$sy][$sm]['d' . $sd];
232
      }
233
      else {
234
        // Just set an empty hour and minute
235
        $itemized[EventItemizer::BAT_HOUR][$sy][$sm]['d' . $sd] = array();
236
        $itemized[EventItemizer::BAT_MINUTE][$sy][$sm]['d' . $sd] = array();
237
      }
238
239
      // Deal with the end date unless it ends just before midnight at which point we don't need to go further
240 View Code Duplication
      if ($this->event->getEndDate()->format('H:i') == '23:59') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
        $itemized[EventItemizer::BAT_HOUR][$ey][$em]['d' . $ed] = array();
242
        $itemized[EventItemizer::BAT_MINUTE][$ey][$em]['d' . $ed] = array();
243
      }
244
      else {
245
        $itemized_end = $this->createHourlyGranular(new \DateTime($end_date->format("Y-n-j 00:00:00")), $end_date->add(new \DateInterval('PT1M')), $interval);
246
        $itemized[EventItemizer::BAT_DAY][$ey][$em]['d' . $ed] = -1;
247
        $itemized[EventItemizer::BAT_HOUR][$ey][$em]['d' . $ed] = $itemized_end[EventItemizer::BAT_HOUR][$ey][$em]['d' . $ed];
248
        $itemized[EventItemizer::BAT_MINUTE][$ey][$em]['d' . $ed] = $itemized_end[EventItemizer::BAT_MINUTE][$ey][$em]['d' . $ed];
249
      }
250
    }
251
252
    return $itemized;
253
  }
254
255
  /**
256
   * Given a DatePeriod it transforms it in hours and minutes. Used to break the first and
257
   * last days of an event into more granular events.
258
   *
259
   * @param \DateTime $start_date
260
   * @param \DateTime $end_date
261
   * @param \DateInterval $interval
262
   * @return array
263
   */
264
  public function createHourlyGranular(\DateTime $start_date, \DateTime $end_date, \DateInterval $interval) {
265
    $period = new \DatePeriod($start_date, $interval, $end_date);
266
267
    $itemized = array();
268
269
    $start_minute = (int)$start_date->format('i');
270
271
    $event_value = $this->event->getValue();
272
273
    $year = $start_date->format('Y');
274
    $month = $start_date->format('n');
275
    $day = $start_date->format('j');
276
    $hour = $start_date->format('G');
277
    $min = $start_date->format('i');
278
279
    foreach ($period as $minute) {
280
      // Re-calculate if we're at a day boundary.
281
      if ($hour == 24) {
282
        $year = $minute->format('Y');
283
        $month = $minute->format('n');
284
        $day = $minute->format('j');
285
        $hour = $minute->format('G');
286
        $min = $minute->format('i');
287
      }
288
289
      // Doing minutes so set the values in the minute array
290
      $itemized[EventItemizer::BAT_MINUTE][$year][$month]['d' . $day]['h' . $hour]['m' . $min] = $event_value;
291
      // Let the hours know that it cannot determine availability
292
      $itemized[EventItemizer::BAT_HOUR][$year][$month]['d' . $day]['h' . $hour] = -1;
293
      $min++;
294
295
      if ($min == 60 && $start_minute !== 0) {
296
        // Not a real hour - leave as is and move on
297
        $min = 0;
298
        $hour++;
299
        $start_minute = 0;
300
      }
301
      elseif ($min == 60 && $start_minute == 0) {
302
        // Did a real whole hour so initialize the hour
303
        $itemized[EventItemizer::BAT_HOUR][$year][$month]['d' . $day]['h' . $hour] = $event_value;
304
305
        $min = 0;
306
        $hour++;
307
        $start_minute = 0;
308
      }
309
310
      $min = str_pad($min, 2, 0, STR_PAD_LEFT);
311
    }
312
313
    // Daylight Saving Time
314
    $timezone = new \DateTimeZone(date_default_timezone_get());
315
    $transitions = $timezone->getTransitions($start_date->getTimestamp(), $end_date->getTimestamp());
316
317
    unset($transitions[0]);
318
    foreach ($transitions as $transition) {
319
      if ($transition['isdst']) {
320
        $date = new \DateTime();
321
        $date->setTimestamp($transition['ts']);
322
323
        $hour = $date->format('G');
324
        for ($i = 0; $i < 60; $i++) {
325
          $minute = ($i < 10) ? '0' . $i : $i;
326
327
          $itemized[EventItemizer::BAT_MINUTE][$date->format('Y')][$date->format('n')]['d' . $date->format('j')]['h' . $hour]['m' . $minute] = $this->event->getValue();
328
        }
329
      }
330
    }
331
332
    return $itemized;
333
  }
334
335
}
336