1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace HMLB\Date; |
4
|
|
|
|
5
|
|
|
use DateInterval; |
6
|
|
|
use HMLB\Date\Translation\DateLocalizationCapabilities; |
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* A simple API extension for Interval. |
11
|
|
|
* The implemenation provides helpers to handle weeks but only days are saved. |
12
|
|
|
* Weeks are calculated based on the total days of the current instance. |
13
|
|
|
* |
14
|
|
|
* @property int $years Total years of the current interval. |
15
|
|
|
* @property int $months Total months of the current interval. |
16
|
|
|
* @property int $weeks Total weeks of the current interval calculated from the days. |
17
|
|
|
* @property int $dayz Total days of the current interval (weeks * 7 + days). |
18
|
|
|
* @property int $hours Total hours of the current interval. |
19
|
|
|
* @property int $minutes Total minutes of the current interval. |
20
|
|
|
* @property int $seconds Total seconds of the current interval. |
21
|
|
|
* |
22
|
|
|
* @property-read integer $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). |
23
|
|
|
* @property-read integer $daysExcludeWeeks alias of dayzExcludeWeeks |
24
|
|
|
* |
25
|
|
|
* @method static years() years($years = 1) Set the years portion of the current interval. |
26
|
|
|
* @method static year() year($years = 1) Alias for years(). |
27
|
|
|
* @method static months() months($months = 1) Set the months portion of the current interval. |
28
|
|
|
* @method static month() month($months = 1) Alias for months(). |
29
|
|
|
* @method static weeks() weeks($weeks = 1) Set the weeks portion of the current interval. Will overwrite dayz value. |
30
|
|
|
* @method static week() week($weeks = 1) Alias for weeks(). |
31
|
|
|
* @method static days() days($days = 1) Set the days portion of the current interval. |
32
|
|
|
* @method static dayz() dayz($days = 1) Alias for days(). |
33
|
|
|
* @method static day() day($days = 1) Alias for days(). |
34
|
|
|
* @method static hours() hours($hours = 1) Set the hours portion of the current interval. |
35
|
|
|
* @method static hour() hour($hours = 1) Alias for hours(). |
36
|
|
|
* @method static minutes() minutes($minutes = 1) Set the minutes portion of the current interval. |
37
|
|
|
* @method static minute() minute($minutes = 1) Alias for minutes(). |
38
|
|
|
* @method static seconds() seconds($seconds = 1) Set the seconds portion of the current interval. |
39
|
|
|
* @method static second() second($seconds = 1) Alias for seconds(). |
40
|
|
|
* @method Interval years() years($years = 1) Set the years portion of the current interval. |
41
|
|
|
* @method Interval year() year($years = 1) Alias for years(). |
42
|
|
|
* @method Interval months() months($months = 1) Set the months portion of the current interval. |
43
|
|
|
* @method Interval month() month($months = 1) Alias for months(). |
44
|
|
|
* @method Interval weeks() weeks($weeks = 1) Set the weeks portion of the current interval. Will overwrite dayz |
45
|
|
|
* value. |
46
|
|
|
* @method Interval week() week($weeks = 1) Alias for weeks(). |
47
|
|
|
* @method Interval days() days($days = 1) Set the days portion of the current interval. |
48
|
|
|
* @method Interval dayz() dayz($days = 1) Alias for days(). |
49
|
|
|
* @method Interval day() day($days = 1) Alias for days(). |
50
|
|
|
* @method Interval hours() hours($hours = 1) Set the hours portion of the current interval. |
51
|
|
|
* @method Interval hour() hour($hours = 1) Alias for hours(). |
52
|
|
|
* @method Interval minutes() minutes($minutes = 1) Set the minutes portion of the current interval. |
53
|
|
|
* @method Interval minute() minute($minutes = 1) Alias for minutes(). |
54
|
|
|
* @method Interval seconds() seconds($seconds = 1) Set the seconds portion of the current interval. |
55
|
|
|
* @method Interval second() second($seconds = 1) Alias for seconds(). |
56
|
|
|
*/ |
57
|
|
|
class Interval extends DateInterval |
58
|
|
|
{ |
59
|
|
|
use DateLocalizationCapabilities; |
60
|
|
|
/** |
61
|
|
|
* Interval spec period designators. |
62
|
|
|
*/ |
63
|
|
|
const PERIOD_PREFIX = 'P'; |
64
|
|
|
const PERIOD_YEARS = 'Y'; |
65
|
|
|
const PERIOD_MONTHS = 'M'; |
66
|
|
|
const PERIOD_DAYS = 'D'; |
67
|
|
|
const PERIOD_TIME_PREFIX = 'T'; |
68
|
|
|
const PERIOD_HOURS = 'H'; |
69
|
|
|
const PERIOD_MINUTES = 'M'; |
70
|
|
|
const PERIOD_SECONDS = 'S'; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Before PHP 5.4.20/5.5.4 instead of FALSE days will be set to -99999 when the interval instance |
74
|
|
|
* was created by DateTime:diff(). |
75
|
|
|
*/ |
76
|
|
|
const PHP_DAYS_FALSE = -99999; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Determine if the interval was created via DateTime:diff() or not. |
80
|
|
|
* |
81
|
|
|
* @param DateInterval $interval |
82
|
|
|
* |
83
|
|
|
* @return bool |
84
|
|
|
*/ |
85
|
|
|
private static function wasCreatedFromDiff(DateInterval $interval) |
86
|
|
|
{ |
87
|
|
|
return $interval->days !== false && $interval->days !== static::PHP_DAYS_FALSE; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/////////////////////////////////////////////////////////////////// |
91
|
|
|
//////////////////////////// CONSTRUCTORS ///////////////////////// |
92
|
|
|
/////////////////////////////////////////////////////////////////// |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Create a new Interval instance. |
96
|
|
|
* |
97
|
|
|
* @param int $years |
98
|
|
|
* @param int $months |
99
|
|
|
* @param int $weeks |
100
|
|
|
* @param int $days |
101
|
|
|
* @param int $hours |
102
|
|
|
* @param int $minutes |
103
|
|
|
* @param int $seconds |
104
|
|
|
*/ |
105
|
|
|
public function __construct( |
106
|
|
|
$years = 1, |
107
|
|
|
$months = null, |
108
|
|
|
$weeks = null, |
109
|
|
|
$days = null, |
110
|
|
|
$hours = null, |
111
|
|
|
$minutes = null, |
112
|
|
|
$seconds = null |
113
|
|
|
) { |
114
|
|
|
$spec = static::PERIOD_PREFIX; |
115
|
|
|
|
116
|
|
|
$spec .= $years > 0 ? $years.static::PERIOD_YEARS : ''; |
117
|
|
|
$spec .= $months > 0 ? $months.static::PERIOD_MONTHS : ''; |
118
|
|
|
|
119
|
|
|
$specDays = 0; |
120
|
|
|
$specDays += $weeks > 0 ? $weeks * Date::DAYS_PER_WEEK : 0; |
121
|
|
|
$specDays += $days > 0 ? $days : 0; |
122
|
|
|
|
123
|
|
|
$spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : ''; |
124
|
|
|
|
125
|
|
|
if ($hours > 0 || $minutes > 0 || $seconds > 0) { |
126
|
|
|
$spec .= static::PERIOD_TIME_PREFIX; |
127
|
|
|
$spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : ''; |
128
|
|
|
$spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : ''; |
129
|
|
|
$spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : ''; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
if ($spec === static::PERIOD_PREFIX) { |
133
|
|
|
// Allow the zero interval. |
134
|
|
|
$spec .= '0'.static::PERIOD_YEARS; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
parent::__construct($spec); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Create a new Interval instance from specific values. |
142
|
|
|
* This is an alias for the constructor that allows better fluent |
143
|
|
|
* syntax as it allows you to do Interval::create(1)->fn() rather than |
144
|
|
|
* (new Interval(1))->fn(). |
145
|
|
|
* |
146
|
|
|
* @param int $years |
147
|
|
|
* @param int $months |
148
|
|
|
* @param int $weeks |
149
|
|
|
* @param int $days |
150
|
|
|
* @param int $hours |
151
|
|
|
* @param int $minutes |
152
|
|
|
* @param int $seconds |
153
|
|
|
* |
154
|
|
|
* @return static |
155
|
|
|
*/ |
156
|
|
|
public static function create( |
157
|
|
|
$years = 1, |
158
|
|
|
$months = null, |
159
|
|
|
$weeks = null, |
160
|
|
|
$days = null, |
161
|
|
|
$hours = null, |
162
|
|
|
$minutes = null, |
163
|
|
|
$seconds = null |
164
|
|
|
) { |
165
|
|
|
return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Provide static helpers to create instances. Allows Interval::years(3). |
170
|
|
|
* |
171
|
|
|
* Note: This is done using the magic method to allow static and instance methods to |
172
|
|
|
* have the same names. |
173
|
|
|
* |
174
|
|
|
* @param string $name |
175
|
|
|
* @param array $args |
176
|
|
|
* |
177
|
|
|
* @return static |
178
|
|
|
*/ |
179
|
|
|
public static function __callStatic($name, $args) |
180
|
|
|
{ |
181
|
|
|
$arg = count($args) === 0 ? 1 : $args[0]; |
182
|
|
|
|
183
|
|
|
switch ($name) { |
184
|
|
|
case 'years': |
185
|
|
|
case 'year': |
186
|
|
|
return new static($arg); |
187
|
|
|
|
188
|
|
|
case 'months': |
189
|
|
|
case 'month': |
190
|
|
|
return new static(null, $arg); |
191
|
|
|
|
192
|
|
|
case 'weeks': |
193
|
|
|
case 'week': |
194
|
|
|
return new static(null, null, $arg); |
195
|
|
|
|
196
|
|
|
case 'days': |
197
|
|
|
case 'dayz': |
198
|
|
|
case 'day': |
199
|
|
|
return new static(null, null, null, $arg); |
200
|
|
|
|
201
|
|
|
case 'hours': |
202
|
|
|
case 'hour': |
203
|
|
|
return new static(null, null, null, null, $arg); |
204
|
|
|
|
205
|
|
|
case 'minutes': |
206
|
|
|
case 'minute': |
207
|
|
|
return new static(null, null, null, null, null, $arg); |
208
|
|
|
|
209
|
|
|
case 'seconds': |
210
|
|
|
case 'second': |
211
|
|
|
return new static(null, null, null, null, null, null, $arg); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
throw new InvalidArgumentException(); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Create a Interval instance from a DateInterval one. Can not instance |
219
|
|
|
* Interval objects created from DateTime::diff() as you can't externally |
220
|
|
|
* set the $days field. |
221
|
|
|
* |
222
|
|
|
* @param DateInterval|Interval $di |
223
|
|
|
* |
224
|
|
|
* @return static |
225
|
|
|
*/ |
226
|
|
|
public static function instance(DateInterval $di) |
227
|
|
|
{ |
228
|
|
|
if (self::wasCreatedFromDiff($di)) { |
229
|
|
|
throw new InvalidArgumentException('Can not instance a Interval object created from DateTime::diff().'); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$instance = new static($di->y, $di->m, 0, $di->d, $di->h, $di->i, $di->s); |
233
|
|
|
$instance->invert = $di->invert; |
234
|
|
|
$instance->days = $di->days; |
235
|
|
|
|
236
|
|
|
return $instance; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param string $intervalSpec |
241
|
|
|
* |
242
|
|
|
* @return Interval |
243
|
|
|
*/ |
244
|
|
|
public static function createFromSpec($intervalSpec) |
245
|
|
|
{ |
246
|
|
|
$interval = new DateInterval($intervalSpec); |
247
|
|
|
|
248
|
|
|
return static::instance($interval); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/////////////////////////////////////////////////////////////////// |
252
|
|
|
///////////////////////// GETTERS AND SETTERS ///////////////////// |
253
|
|
|
/////////////////////////////////////////////////////////////////// |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Get a part of the Interval object. |
257
|
|
|
* |
258
|
|
|
* @param string $name |
259
|
|
|
* |
260
|
|
|
* @throws InvalidArgumentException |
261
|
|
|
* |
262
|
|
|
* @return int |
263
|
|
|
*/ |
264
|
|
|
public function __get($name) |
265
|
|
|
{ |
266
|
|
|
switch ($name) { |
267
|
|
|
case 'years': |
268
|
|
|
return $this->y; |
269
|
|
|
|
270
|
|
|
case 'months': |
271
|
|
|
return $this->m; |
272
|
|
|
|
273
|
|
|
case 'dayz': |
274
|
|
|
return $this->d; |
275
|
|
|
|
276
|
|
|
case 'hours': |
277
|
|
|
return $this->h; |
278
|
|
|
|
279
|
|
|
case 'minutes': |
280
|
|
|
return $this->i; |
281
|
|
|
|
282
|
|
|
case 'seconds': |
283
|
|
|
return $this->s; |
284
|
|
|
|
285
|
|
|
case 'weeks': |
286
|
|
|
return (int) floor($this->d / Date::DAYS_PER_WEEK); |
287
|
|
|
|
288
|
|
|
case 'daysExcludeWeeks': |
289
|
|
|
case 'dayzExcludeWeeks': |
290
|
|
|
return $this->d % Date::DAYS_PER_WEEK; |
291
|
|
|
|
292
|
|
|
default: |
293
|
|
|
throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name)); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Set a part of the Interval object. |
299
|
|
|
* |
300
|
|
|
* @param string $name |
301
|
|
|
* @param int $val |
302
|
|
|
* |
303
|
|
|
* @throws InvalidArgumentException |
304
|
|
|
*/ |
305
|
|
|
public function __set($name, $val) |
306
|
|
|
{ |
307
|
|
|
switch ($name) { |
308
|
|
|
case 'years': |
309
|
|
|
$this->y = $val; |
310
|
|
|
break; |
311
|
|
|
|
312
|
|
|
case 'months': |
313
|
|
|
$this->m = $val; |
314
|
|
|
break; |
315
|
|
|
|
316
|
|
|
case 'weeks': |
317
|
|
|
$this->d = (int) $val * Date::DAYS_PER_WEEK; |
|
|
|
|
318
|
|
|
break; |
319
|
|
|
|
320
|
|
|
case 'dayz': |
321
|
|
|
$this->d = $val; |
322
|
|
|
break; |
323
|
|
|
|
324
|
|
|
case 'hours': |
325
|
|
|
$this->h = $val; |
326
|
|
|
break; |
327
|
|
|
|
328
|
|
|
case 'minutes': |
329
|
|
|
$this->i = $val; |
330
|
|
|
break; |
331
|
|
|
|
332
|
|
|
case 'seconds': |
333
|
|
|
$this->s = $val; |
334
|
|
|
break; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Allow setting of weeks and days to be cumulative. |
340
|
|
|
* |
341
|
|
|
* @param int $weeks Number of weeks to set |
342
|
|
|
* @param int $days Number of days to set |
343
|
|
|
* |
344
|
|
|
* @return static |
345
|
|
|
*/ |
346
|
|
|
public function weeksAndDays($weeks, $days) |
347
|
|
|
{ |
348
|
|
|
$this->dayz = ($weeks * Date::DAYS_PER_WEEK) + $days; |
349
|
|
|
|
350
|
|
|
return $this; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Allow fluent calls on the setters... Interval::years(3)->months(5)->day(). |
355
|
|
|
* |
356
|
|
|
* Note: This is done using the magic method to allow static and instance methods to |
357
|
|
|
* have the same names. |
358
|
|
|
* |
359
|
|
|
* @param string $name |
360
|
|
|
* @param array $args |
361
|
|
|
* |
362
|
|
|
* @return static |
363
|
|
|
*/ |
364
|
|
|
public function __call($name, $args) |
365
|
|
|
{ |
366
|
|
|
$arg = count($args) === 0 ? 1 : $args[0]; |
367
|
|
|
|
368
|
|
|
switch ($name) { |
369
|
|
|
case 'years': |
370
|
|
|
case 'year': |
371
|
|
|
$this->years = $arg; |
372
|
|
|
break; |
373
|
|
|
|
374
|
|
|
case 'months': |
375
|
|
|
case 'month': |
376
|
|
|
$this->months = $arg; |
377
|
|
|
break; |
378
|
|
|
|
379
|
|
|
case 'weeks': |
380
|
|
|
case 'week': |
381
|
|
|
$this->dayz = $arg * Date::DAYS_PER_WEEK; |
382
|
|
|
break; |
383
|
|
|
|
384
|
|
|
case 'days': |
385
|
|
|
case 'dayz': |
386
|
|
|
case 'day': |
387
|
|
|
$this->dayz = $arg; |
388
|
|
|
break; |
389
|
|
|
|
390
|
|
|
case 'hours': |
391
|
|
|
case 'hour': |
392
|
|
|
$this->hours = $arg; |
393
|
|
|
break; |
394
|
|
|
|
395
|
|
|
case 'minutes': |
396
|
|
|
case 'minute': |
397
|
|
|
$this->minutes = $arg; |
398
|
|
|
break; |
399
|
|
|
|
400
|
|
|
case 'seconds': |
401
|
|
|
case 'second': |
402
|
|
|
$this->seconds = $arg; |
403
|
|
|
break; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
return $this; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Get the current interval in a human readable format in the current locale. |
411
|
|
|
* |
412
|
|
|
* @return string |
413
|
|
|
*/ |
414
|
|
|
public function forHumans() |
415
|
|
|
{ |
416
|
|
|
$periods = [ |
417
|
|
|
'year' => $this->years, |
418
|
|
|
'month' => $this->months, |
419
|
|
|
'week' => $this->weeks, |
420
|
|
|
'day' => $this->daysExcludeWeeks, |
421
|
|
|
'hour' => $this->hours, |
422
|
|
|
'minute' => $this->minutes, |
423
|
|
|
'second' => $this->seconds, |
424
|
|
|
]; |
425
|
|
|
|
426
|
|
|
$parts = []; |
427
|
|
|
foreach ($periods as $unit => $count) { |
428
|
|
|
if ($count > 0) { |
429
|
|
|
array_push($parts, static::translator()->transChoice($unit, $count, [':count' => $count])); |
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
return implode(' ', $parts); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Format the instance as a string using the forHumans() function. |
438
|
|
|
* |
439
|
|
|
* @return string |
440
|
|
|
*/ |
441
|
|
|
public function __toString() |
442
|
|
|
{ |
443
|
|
|
return $this->forHumans(); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* Add the passed interval to the current instance. |
448
|
|
|
* |
449
|
|
|
* @param DateInterval $interval |
450
|
|
|
* |
451
|
|
|
* @return static |
452
|
|
|
*/ |
453
|
|
|
public function add(DateInterval $interval) |
454
|
|
|
{ |
455
|
|
|
$sign = $interval->invert === 1 ? -1 : 1; |
456
|
|
|
|
457
|
|
|
if (self::wasCreatedFromDiff($interval)) { |
458
|
|
|
$this->dayz = $this->dayz + $interval->days * $sign; |
459
|
|
|
} else { |
460
|
|
|
$this->years = $this->years + $interval->y * $sign; |
461
|
|
|
$this->months = $this->months + $interval->m * $sign; |
462
|
|
|
$this->dayz = $this->dayz + $interval->d * $sign; |
463
|
|
|
$this->hours = $this->hours + $interval->h * $sign; |
464
|
|
|
$this->minutes = $this->minutes + $interval->i * $sign; |
465
|
|
|
$this->seconds = $this->seconds + $interval->s * $sign; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
return $this; |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.