Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
SmartDateTime
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
17 / 17
30
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 createFromDateTime
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createModify
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 smartModify
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 getEndOfMonth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStartOfMonth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStartOfWeek
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getEndOfWeek
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDaysOfMonth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFirstDayOfMonth
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getEndOfDay
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStartOfDay
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isLeapYear
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 leapYear
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 dayOfWeek
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getDayOfWeek
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMonthCalendar
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Stdlib\Base
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Stdlib\Base;
16
17use phpOMS\Math\Functions\Functions;
18
19/**
20 * SmartDateTime.
21 *
22 * Providing smarter datetimes
23 *
24 * @package phpOMS\Stdlib\Base
25 * @license OMS License 2.0
26 * @link    https://jingga.app
27 * @since   1.0.0
28 */
29class SmartDateTime extends \DateTime
30{
31    /**
32     * Default format
33     *
34     * @var string
35     * @since 1.0.0
36     */
37    public const FORMAT = 'Y-m-d hh:mm:ss';
38
39    /**
40     * Default timezone
41     *
42     * @var string
43     * @since 1.0.0
44     */
45    public const TIMEZONE = 'UTC';
46
47    /**
48     * Constructor.
49     *
50     * @param string             $datetime DateTime string
51     * @param null|\DateTimeZone $timezone Timezone
52     *
53     * @since 1.0.0
54     */
55    public function __construct(string $datetime = 'now', \DateTimeZone $timezone = null)
56    {
57        $parsed = \str_replace(
58            ['Y', 'm', 'd'],
59            [\date('Y'), \date('m'), \date('d')],
60            $datetime
61        );
62
63        parent::__construct($parsed, $timezone);
64    }
65
66    /**
67     * Create object from DateTime
68     *
69     * @param \DateTime $date DateTime to extend
70     *
71     * @return SmartDateTime
72     *
73     * @since 1.0.0
74     */
75    public static function createFromDateTime(\DateTime $date) : self
76    {
77        return new self($date->format('Y-m-d H:i:s'), $date->getTimezone());
78    }
79
80    /**
81     * Modify datetime in a smart way.
82     *
83     * @param int $y        Year
84     * @param int $m        Month
85     * @param int $d        Day
86     * @param int $calendar Calendar
87     *
88     * @return SmartDateTime
89     *
90     * @since 1.0.0
91     */
92    public function createModify(int $y = 0, int $m = 0, int $d = 0, int $calendar = \CAL_GREGORIAN) : self
93    {
94        $dt = clone $this;
95        $dt->smartModify($y, $m, $d, $calendar);
96
97        return $dt;
98    }
99
100    /**
101     * Modify datetime in a smart way.
102     *
103     * @param int $y        Year
104     * @param int $m        Month
105     * @param int $d        Day
106     * @param int $calendar Calendar
107     *
108     * @return SmartDateTime
109     *
110     * @since 1.0.0
111     */
112    public function smartModify(int $y = 0, int $m = 0, int $d = 0, int $calendar = \CAL_GREGORIAN) : self
113    {
114        $yearChange = (int) \floor(((int) $this->format('m') - 1 + $m) / 12);
115        $yearNew    = (int) $this->format('Y') + $y + $yearChange;
116
117        $monthNew = (int) $this->format('m') + $m;
118        $monthNew = $monthNew < 0
119            ? 12 + ($monthNew - 1) % 12 + 1
120            : ($monthNew - 1) % 12 + 1;
121
122        $dayMonthOld = \cal_days_in_month($calendar, (int) $this->format('m'), (int) $this->format('Y'));
123        $dayMonthNew = \cal_days_in_month($calendar, $monthNew, $yearNew);
124        $dayOld      = (int) $this->format('d');
125
126        $dayNew = $dayOld > $dayMonthNew || $dayOld === $dayMonthOld ? $dayMonthNew : $dayOld;
127
128        $this->setDate($yearNew, $monthNew, $dayNew);
129
130        if ($d !== 0) {
131            $this->modify($d . ' day');
132        }
133
134        return $this;
135    }
136
137    /**
138     * Get end of month object
139     *
140     * @return SmartDateTime
141     *
142     * @since 1.0.0
143     */
144    public function getEndOfMonth() : self
145    {
146        return new self($this->format('Y-m') . '-' . $this->getDaysOfMonth() . ' 23:59:59');
147    }
148
149    /**
150     * Get start of month object
151     *
152     * @return SmartDateTime
153     *
154     * @since 1.0.0
155     */
156    public function getStartOfMonth() : self
157    {
158        return new self($this->format('Y') . '-' . $this->format('m') . '-01');
159    }
160
161    /**
162     * Get start of the week
163     *
164     * @return SmartDateTime
165     *
166     * @since 1.0.0
167     */
168    public function getStartOfWeek() : self
169    {
170        $w = (int) \strtotime('-' . \date('w', $this->getTimestamp()) .' days', $this->getTimestamp());
171
172        return new self(\date('Y-m-d', $w));
173    }
174
175    /**
176     * Get end of the week
177     *
178     * @return SmartDateTime
179     *
180     * @since 1.0.0
181     */
182    public function getEndOfWeek() : self
183    {
184        $w = (int) \strtotime('+' . (6 - (int) \date('w', $this->getTimestamp())) .' days', $this->getTimestamp());
185
186        return new self(\date('Y-m-d', $w));
187    }
188
189    /**
190     * Get days of current month
191     *
192     * @return int
193     *
194     * @since 1.0.0
195     */
196    public function getDaysOfMonth() : int
197    {
198        return (int) $this->format('t');
199    }
200
201    /**
202     * Get first day of current month
203     *
204     * @return int
205     *
206     * @since 1.0.0
207     */
208    public function getFirstDayOfMonth() : int
209    {
210        $time = \mktime(0, 0, 0, (int) $this->format('m'), 1, (int) $this->format('Y'));
211
212        if ($time === false) {
213            return -1; // @codeCoverageIgnore
214        }
215
216        return \getdate($time)['wday'];
217    }
218
219    /**
220     * Get the end of the day
221     *
222     * @return SmartDateTime
223     *
224     * @since 1.0.0
225     */
226    public function getEndOfDay() : self
227    {
228        return new self(\date('Y-m-d', $this->getTimestamp()) . ' 23:59:59');
229    }
230
231    /**
232     * Get the start of the day
233     *
234     * @return SmartDateTime
235     *
236     * @since 1.0.0
237     */
238    public function getStartOfDay() : self
239    {
240        return new self(\date('Y-m-d', $this->getTimestamp()) . ' 00:00:00');
241    }
242
243    /**
244     * Is leap year in gregorian calendar
245     *
246     * @return bool
247     *
248     * @since 1.0.0
249     */
250    public function isLeapYear() : bool
251    {
252        return self::leapYear((int) $this->format('Y'));
253    }
254
255    /**
256     * Test year if leap year in gregorian calendar
257     *
258     * @param int $year Year to check
259     *
260     * @return bool
261     *
262     * @since 1.0.0
263     */
264    public static function leapYear(int $year) : bool
265    {
266        $isLeap = false;
267
268        if ($year % 4 === 0) {
269            $isLeap = true;
270        }
271
272        if ($year % 100 === 0) {
273            $isLeap = false;
274        }
275
276        if ($year % 400 === 0) {
277            $isLeap = true;
278        }
279
280        return $isLeap;
281    }
282
283    /**
284     * Get day of week
285     *
286     * @param int $y Year
287     * @param int $m Month
288     * @param int $d Day
289     *
290     * @return int
291     *
292     * @since 1.0.0
293     */
294    public static function dayOfWeek(int $y, int $m, int $d) : int
295    {
296        $time = \strtotime($d . '-' . $m . '-' . $y);
297
298        if ($time === false) {
299            return -1;
300        }
301
302        return (int) \date('w', $time);
303    }
304
305    /**
306     * Get day of week
307     *
308     * @return int
309     *
310     * @since 1.0.0
311     */
312    public function getDayOfWeek() : int
313    {
314        return self::dayOfWeek((int) $this->format('Y'), (int) $this->format('m'), (int) $this->format('d'));
315    }
316
317    /**
318     * Create calendar array
319     *
320     * @param int $weekStartsWith Day of the week start (0 = Sunday)
321     *
322     * @return \DateTime[]
323     *
324     * @since 1.0.0
325     */
326    public function getMonthCalendar(int $weekStartsWith = 0) : array
327    {
328        $days = [];
329
330        // get day of first day in month
331        $firstDay = $this->getFirstDayOfMonth();
332
333        // calculate difference to $weekStartsWith
334        $diffToWeekStart = Functions::mod($firstDay - $weekStartsWith, 7);
335        $diffToWeekStart = $diffToWeekStart === 0 ? 7 : $diffToWeekStart;
336
337        // get days of previous month
338        $previousMonth     = $this->createModify(0, -1);
339        $daysPreviousMonth = $previousMonth->getDaysOfMonth();
340
341        // add difference to $weekStartsWith counting backwards from days of previous month (reorder so that lowest value first)
342        for ($i = $daysPreviousMonth - $diffToWeekStart; $i < $daysPreviousMonth; ++$i) {
343            $days[] = new \DateTime($previousMonth->format('Y') . '-' . $previousMonth->format('m') . '-' . ($i + 1));
344        }
345
346        // add normal count of current days
347        $daysMonth = $this->getDaysOfMonth();
348        for ($i = 1; $i <= $daysMonth; ++$i) {
349            $days[] = new \DateTime($this->format('Y') . '-' . $this->format('m') . '-' . ($i));
350        }
351
352        // add remaining days to next month (7*6 - difference+count of current month)
353        $remainingDays = 42 - $diffToWeekStart - $daysMonth;
354        $nextMonth     = $this->createModify(0, 1);
355
356        for ($i = 1; $i <= $remainingDays; ++$i) {
357            $days[] = new \DateTime($nextMonth->format('Y') . '-' . $nextMonth->format('m') . '-' . ($i));
358        }
359
360        return $days;
361    }
362}