PeriodService::getWeeksInMonth()   B
last analyzed

Complexity

Conditions 5
Paths 10

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 35
rs 8.439
cc 5
eloc 21
nc 10
nop 1
1
<?php
2
3
namespace JhFlexiTime\Service;
4
5
use JhFlexiTime\Options\ModuleOptions;
6
use JhFlexiTime\DateTime\DateTime;
7
8
/**
9
 * Calculate hours in various periods
10
 *
11
 * Class PeriodService
12
 * @package JhFlexiTime\Service
13
 * @author Aydin Hassan <[email protected]>
14
 */
15
class PeriodService implements PeriodServiceInterface
16
{
17
18
    /**
19
     * @var \JhFlexiTime\Options\ModuleOptions
20
     */
21
    protected $options;
22
23
    /**
24
     * @param ModuleOptions $options
25
     */
26
    public function __construct(ModuleOptions $options)
27
    {
28
        $this->options = $options;
29
    }
30
31
    /**
32
     * Get total hours in period, using config
33
     * to determine how many hours to count in each day
34
     * exclude weekends
35
     *
36
     * @param \DatePeriod $period
37
     * @return int
38
     */
39
    protected function getTotalHoursInPeriod(\DatePeriod $period)
40
    {
41
        $count = 0;
42
        foreach ($period as $day) {
43
            //exclude weekends
44
            if ($day->format('N') < 6) {
45
                $count++;
46
            }
47
        }
48
49
        $monthTotalHours = $count * $this->options->getHoursInDay();
50
        //round to 2 decimal places
51
        return (float) number_format($monthTotalHours, 2, '.', '');
52
    }
53
54
    /**
55
     * @param DateTime $start
56
     * @param DateTime $end
57
     *
58
     * @return int
59
     */
60
    public function getTotalHoursBetweenDates(DateTime $start, DateTime $end)
61
    {
62
        $period = new \DatePeriod(
63
            new DateTime(sprintf('%s 00:00:00', $start->format('d-m-Y'))),
64
            new \DateInterval('P1D'),
65
            new DateTime(sprintf('%s 23:59:59', $end->format('d-m-Y')))
66
        );
67
        return $this->getTotalHoursInPeriod($period);
68
    }
69
70
    /**
71
     * Get total hours in a given month
72
     *
73
     * @param DateTime $month
74
     * @return float
75
     */
76
    public function getTotalHoursInMonth(DateTime $month)
77
    {
78
        $period = new \DatePeriod(
79
            new DateTime(sprintf('first day of %s 00:00:00', $month->format('F Y'))),
80
            new \DateInterval('P1D'),
81
            new DateTime(sprintf('last day of %s 23:59:59', $month->format('F Y')))
82
        );
83
        return $this->getTotalHoursInPeriod($period);
84
    }
85
86
    /**
87
     * Calculate the hours between to dates
88
     * return something like 7.5, 8.25
89
     *
90
     * Minus the lunch duration from the total,
91
     * so only return total working hours
92
     *
93
     * @param DateTime $start
94
     * @param DateTime $end
95
     * @throws \InvalidArgumentException
96
     * @return float hour diff
97
     */
98
    public function calculateHourDiff(DateTime $start, DateTime $end)
99
    {
100
        if ($end <= $start) {
101
            throw new \InvalidArgumentException("End time should be after start time");
102
        }
103
104
        $lunchDuration  = $this->options->getLunchDuration();
105
        $diff           = $start->diff($end);
106
        $hours          = $diff->format('%r%h');
107
        $minutes        = $diff->format('%i') / 60;
108
        $totalHours     = ($hours + $minutes) - $lunchDuration;
109
        //round to 2 decimal places
110
        return number_format($totalHours, 2, '.', '');
111
    }
112
113
    /**
114
     * Get the remaining hours in a month
115
     * using the config as a base for how many hours
116
     * should be worked per day
117
     *
118
     * @param DateTime $today
119
     * @return int
120
     */
121
    public function getRemainingHoursInMonth(DateTime $today)
122
    {
123
        $date       = clone $today;
124
        $lastDay    = clone $today;
125
        $date->modify("+1 day");
126
        $lastDay->modify('last day of this month');
127
        $lastDay->modify("+1 day"); //hack to include the last day in the period
128
129
        $period = new \DatePeriod($date, new \DateInterval('P1D'), $lastDay);
130
        return $this->getTotalHoursInPeriod($period);
131
    }
132
133
    /**
134
     * Get an array of all the dates of the week the given date
135
     * is in
136
     *
137
     * @param DateTime $date
138
     * @return DateTime[]
139
     * @throws \Exception
140
     */
141
    public function getDaysInWeek(DateTime $date)
142
    {
143
        $weeks = $this->getWeeksInMonth($date);
144
145
        foreach ($weeks as $week) {
146
            $firstDayOfWeek = reset($week);
147
            $lastDayOfWeek  = end($week);
148
149
            if ($date >= $firstDayOfWeek && $date <= $lastDayOfWeek) {
150
                return $week;
151
            }
152
        }
153
154
        throw new \Exception("Day is not present in returned month");
155
    }
156
157
    /**
158
     * Get an array of the first and last day of
159
     * the week the given day is in
160
     *
161
     * @param DateTime $date
162
     * @return array
163
     * @throws \Exception
164
     */
165
    public function getFirstAndLastDayOfWeek(DateTime $date)
166
    {
167
        $week = $this->getDaysInWeek($date);
168
169
        $firstDayOfWeek = reset($week);
170
        $lastDayOfWeek  = end($week);
171
172
        return ['firstDay' => $firstDayOfWeek, 'lastDay' => $lastDayOfWeek];
173
    }
174
175
    /**
176
     * Get the week which this date is in, and count the number
177
     * of non-working days
178
     *
179
     * @param DateTime $date
180
     * @return int
181
     */
182
    public function getNumWorkingDaysInWeek(DateTime $date)
183
    {
184
        $week = $this->getDaysInWeek($date);
185
        $week = $this->removeNonWorkingDays($week);
186
        return count($week);
187
    }
188
189
    /**
190
     * Remove any non-working days
191
     *
192
     * @param DateTime[] $dates
193
     * @return DateTime[]
194
     */
195
    public function removeNonWorkingDays(array $dates)
196
    {
197
        return array_filter(
198
            $dates,
199
            function (DateTime $day) {
200
                return $day->format('N') < 6;
201
            }
202
        );
203
    }
204
205
    /**
206
     * Get an array of weeks, with each day of the week in it
207
     *
208
     * @param DateTime $date
209
     * @return array
210
     */
211
    public function getWeeksInMonth(DateTime $date)
212
    {
213
        $tmpDatePeriod = new \DatePeriod(
214
            new DateTime(sprintf('first day of %s', $date->format('F Y'))),
215
            new \DateInterval('P1D'),
216
            new DateTime(sprintf('last day of %s 23:59:59', $date->format('F Y')))
217
        );
218
219
        //convert DateTime to JhDateTime
220
        $datePeriod = [];
221
        foreach ($tmpDatePeriod as $date) {
222
            $jhDate = new DateTime();
223
            $jhDate->setTimestamp($date->getTimestamp());
224
            $datePeriod[] = $jhDate;
225
        }
226
227
228
        $weeks = [];
229
        $weekCounter = 0;
230
        foreach ($datePeriod as $day) {
231
            $dayNum = $day->format('N');
232
233
            if (!isset($weeks[$weekCounter])) {
234
                $weeks[$weekCounter] = [$day];
235
            } else {
236
                $weeks[$weekCounter][] = $day;
237
            }
238
239
            if ($dayNum == 7) {
240
                $weekCounter++;
241
            }
242
        }
243
244
        return $weeks;
245
    }
246
247
    /**
248
     * @param DateTime $dateA
249
     * @param DateTime $dateB
250
     * @return bool
251
     */
252
    public function isDateAfterDay(DateTime $dateA, DateTime $dateB)
253
    {
254
        $date = clone $dateB;
255
        $date->modify("23:59:59");
256
        return $dateA > $date;
257
    }
258
}
259