Completed
Push — master ( 4b2baa...411837 )
by Hugues
02:31
created

Interval::createFromSpec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4286
cc 1
eloc 3
nc 1
nop 1
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like (int) $val * \HMLB\Date\Date::DAYS_PER_WEEK can also be of type double. However, the property $d is declared as type integer. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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