Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
66 / 66 |
|
100.00% |
17 / 17 |
CRAP | |
100.00% |
1 / 1 |
SmartDateTime | |
100.00% |
66 / 66 |
|
100.00% |
17 / 17 |
30 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
createFromDateTime | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createModify | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
smartModify | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
5 | |||
getEndOfMonth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStartOfMonth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStartOfWeek | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getEndOfWeek | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDaysOfMonth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFirstDayOfMonth | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getEndOfDay | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStartOfDay | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isLeapYear | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
leapYear | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
dayOfWeek | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getDayOfWeek | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMonthCalendar | |
100.00% |
16 / 16 |
|
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 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Stdlib\Base; |
16 | |
17 | use 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 | */ |
29 | class 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 | } |