Interval   C
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 414
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 79
c 3
b 0
f 0
lcom 1
cbo 2
dl 0
loc 414
rs 5.442

13 Methods

Rating   Name   Duplication   Size   Complexity  
A wasCreatedFromDiff() 0 4 2
F __construct() 0 34 13
A create() 0 11 1
D __callStatic() 0 37 17
A instance() 0 12 2
A createFromSpec() 0 6 1
D __get() 0 32 10
C __set() 0 32 8
A weeksAndDays() 0 6 1
D __call() 0 44 17
A forHumans() 0 21 3
A __toString() 0 4 1
A add() 0 17 3

How to fix   Complexity   

Complex Class

Complex classes like Interval often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Interval, and based on these observations, apply Extract Interface, too.

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 (int) $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) round($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