Completed
Push — master ( 0c937b...60a69c )
by Karsten
02:39
created

LocalDate   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 612
Duplicated Lines 8.5 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 95.91%

Importance

Changes 12
Bugs 2 Features 6
Metric Value
wmc 59
c 12
b 2
f 6
lcom 1
cbo 0
dl 52
loc 612
ccs 164
cts 171
cp 0.9591
rs 6.0784

44 Methods

Rating   Name   Duplication   Size   Complexity  
A fromTimestamp() 0 4 1
A now() 0 4 1
A raw() 0 11 2
A getMinutesIntoDay() 0 7 1
A getHours() 0 4 1
B __construct() 0 42 4
A __toString() 0 4 1
A getDate() 0 4 1
A getTimestamp() 0 4 1
A getTimezone() 0 4 1
A getOffset() 0 4 1
A getOffsetInMinutes() 0 6 1
A getOffsetInHours() 0 6 1
A format() 0 4 1
A getClone() 0 4 1
A getDaylightSavingShift() 0 6 1
A getDstStartOfDayPlusHours() 0 10 1
A getStartOfDay() 0 4 1
A getNearestStartOfDay() 0 8 2
A getStartOfPreviousDay() 15 15 2
A getStartOfNextDay() 15 15 2
A getEndOfDay() 0 4 1
A modifyTime() 0 12 1
A modifyBySeconds() 0 7 1
A modifyByMinutes() 0 4 1
A modifyByHours() 0 4 1
A modifyByDays() 0 4 1
A modifyByInterval() 0 4 1
A addInterval() 11 11 2
A subInterval() 11 11 2
A alignToMinutesInterval() 0 15 2
A getWeekday() 0 10 2
A min() 0 6 2
A max() 0 6 2
A diffInSeconds() 0 6 1
A diffInMinutes() 0 6 1
A diffInHours() 0 6 1
A diffInDays() 0 6 1
A isBefore() 0 6 1
A isBeforeOrEqual() 0 6 1
A isEqual() 0 6 1
A isAfter() 0 6 1
A isAfterOrEqual() 0 6 1
A ensure() 0 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like LocalDate 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 LocalDate, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * File was created 11.03.2015 14:51
4
 *
5
 * @author Karsten J. Gerber <[email protected]>
6
 */
7
namespace PeekAndPoke\Horizons\DateAndTime;
8
9
/**
10
 * Date
11
 *
12
 * @author Karsten J. Gerber <[email protected]>
13
 */
14
class LocalDate
15
{
16
    /** @var \DateTime */
17
    private $date;
18
    /** @var \DateTimeZone */
19
    private $timezone;
20
21
    /**
22
     * @param int                  $timestamp The unix timestamp
23
     * @param \DateTimeZone|string $timezone  The timezone or a string the DateTimeZone c'tor can understand
24
     *
25
     * @return LocalDate
26
     */
27 92
    public static function fromTimestamp($timestamp, $timezone)
28
    {
29 92
        return new LocalDate($timestamp, $timezone);
30
    }
31
32
    /**
33
     * @return LocalDate
34
     */
35 1
    public static function now()
36
    {
37 1
        return self::raw(new \DateTime());
38
    }
39
40
    /**
41
     * @param \DateTime $dateTime
42
     * @return LocalDate
43
     */
44 11
    public static function raw(\DateTime $dateTime)
45
    {
46 11
        $tz = $dateTime->getTimezone();
47
48
        // normalize input coming from Javascript or Java etc...
49 11
        if ($tz->getName() === 'Z') {
50 2
            $tz = new \DateTimeZone('Etc/UTC');
51 2
        }
52
53 11
        return new LocalDate($dateTime, $tz);
54
    }
55
56
    /**
57
     * @param \DateTime|string|float $input    The date or a string the DateTime c'tor can understand or a timestamp
58
     * @param \DateTimeZone|string   $timezone The timezone or a string the DateTimeZone c'tor can understand
59
     */
60 190
    public function __construct($input, $timezone)
61
    {
62 190
        if (! $timezone instanceof \DateTimeZone) {
63 179
            $timezone = new \DateTimeZone((string) $timezone);
64 179
        }
65
66
        /////
67
        // We have to trick PHP a bit here, but why?
68
        //
69
        // Apparently things behave different, when setting a timezone like
70
        // a) 'Europe/Berlin'
71
        // b) '+02:00'
72
        //
73
        // When the date already has a timezone set then
74
        // a) will only set the timezone
75
        // b) will set the timezone and will also change the timestamp by 2 hours
76
        //
77
        // Therefore we create a fresh date, that does not have a timezone yet, set the timestamp, and then apply the
78
        // timezone
79
        //
80
        // See the unit tests for more as well
81
        ////
82 190
        if ($input instanceof \DateTime) {
83 60
            $date = (new \DateTime())
84 60
                ->setTimestamp($input->getTimestamp())
85 60
                ->setTimezone($timezone);
86 190
        } elseif (is_numeric($input)) {
87 101
            $date = (new \DateTime())
88 101
                ->setTimestamp($input)
89 101
                ->setTimezone($timezone);
90 101
        } else {
91
            // when we have string input, we immediately use the timezone
92 145
            $tmp = new \DateTime($input, $timezone);
93
            // we reconstruct the date time again in order to set the timezone on the inner one
94 145
            $date = (new \DateTime())
95 145
                ->setTimestamp($tmp->getTimestamp())
96 145
                ->setTimezone($timezone);
97
        }
98
99 190
        $this->date     = $date;
100 190
        $this->timezone = $timezone;
101 190
    }
102
103
    /**
104
     * @return string
105
     */
106 1
    public function __toString()
107
    {
108 1
        return $this->format();
109
    }
110
111
    /**
112
     * @return \DateTime
113
     */
114 57
    public function getDate()
115
    {
116 57
        return clone $this->date;
117
    }
118
119
    /**
120
     * @return int
121
     */
122 103
    public function getTimestamp()
123
    {
124 103
        return $this->date->getTimestamp();
125
    }
126
127
    /**
128
     * @return \DateTimeZone
129
     */
130 138
    public function getTimezone()
131
    {
132 138
        return clone $this->timezone;
133
    }
134
135
    /**
136
     * @return int
137
     */
138 2
    public function getOffset()
139
    {
140 2
        return $this->date->getOffset();
141
    }
142
143
    /**
144
     * @return float
145
     */
146 1
    public function getOffsetInMinutes()
147
    {
148 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
149
150 1
        return $offset / 60;
151
    }
152
153
    /**
154
     * @return float
155
     */
156 1
    public function getOffsetInHours()
157
    {
158 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
159
160 1
        return $offset / 3600;
161
    }
162
163
    /**
164
     * @param string $format
165
     *
166
     * @return string
167
     */
168 160
    public function format($format = 'c')
169
    {
170 160
        return $this->date->format($format);
171
    }
172
173
    /**
174
     * @return LocalDate
175
     */
176 1
    public function getClone()
177
    {
178 1
        return new LocalDate($this->date, $this->timezone);
179
    }
180
181
    /**
182
     * Get the time saving shift on this date in seconds
183
     *
184
     * @return int
185
     */
186 1
    public function getDaylightSavingShift()
187
    {
188 1
        $previousNoon = $this->getStartOfPreviousDay()->modifyByHours(12);
189
190 1
        return  $this->getOffset() - $previousNoon->getOffset();
191
    }
192
193
    //// Daylight saving time shift aware methods //////////////////////////////////////////////////////////////////////
194
195
    /**
196
     * Add hours to start of day while also respecting Daylight-saving-time shift
197
     *
198
     * E.g. on 2016-03-27T10:00:00 in Berlin is only 9 hours after the start of the day.
199
     *
200
     * @param $hours
201
     *
202
     * @return LocalDate
203
     */
204
    public function getDstStartOfDayPlusHours ($hours)
205
    {
206
        $startOfDay = $this->getStartOfDay();
207
        $startOfDayOffset = $startOfDay->getOffset();
208
209
        $result = $startOfDay->modifyByHours($hours);
210
        $resultOffset = $result->getOffset();
211
212
        return $result->modifyBySeconds($startOfDayOffset)->modifyBySeconds(0 - $resultOffset);
213
    }
214
215
    //// Modification methods //////////////////////////////////////////////////////////////////////////////////////////
216
217
    /**
218
     * @return LocalDate
219
     */
220 18
    public function getStartOfDay()
221
    {
222 18
        return $this->modifyTime(0, 0, 0);
223
    }
224
225
    /**
226
     * Get the nearest start of a day, either the current or the next day depending on the time in the day
227
     *
228
     * @return LocalDate
229
     */
230 1
    public function getNearestStartOfDay()
231
    {
232 1
        if ($this->getHours() < 12) {
233 1
            return $this->getStartOfDay();
234
        }
235
236 1
        return $this->getStartOfNextDay();
237
    }
238
239
    /**
240
     * @return LocalDate
241
     */
242 2 View Code Duplication
    public function getStartOfPreviousDay()
243
    {
244 2
        $dateCloned = clone $this->getDate();
245
246
        // take switches between summer and winter time into account
247 2
        if ($dateCloned->format('H') > 21) {
248 1
            $dateCloned->modify('-27 hours');
249 1
        } else {
250 2
            $dateCloned->modify('-24 hours');
251
        }
252
253 2
        $temp = new LocalDate($dateCloned, $this->timezone);
254
255 2
        return $temp->getStartOfDay();
256
    }
257
258
    /**
259
     * @return LocalDate
260
     */
261 2 View Code Duplication
    public function getStartOfNextDay()
262
    {
263 2
        $dateCloned = clone $this->getDate();
264
265
        // take switches between summer and winter time into account
266 2
        if ($dateCloned->format('H') < 3) {
267 1
            $dateCloned->modify('+27 hours');
268 1
        } else {
269 2
            $dateCloned->modify('+24 hours');
270
        }
271
272 2
        $temp = new LocalDate($dateCloned, $this->timezone);
273
274 2
        return $temp->getStartOfDay();
275
    }
276
277
    /**
278
     * @return LocalDate
279
     */
280 1
    public function getEndOfDay()
281
    {
282 1
        return $this->getStartOfDay()->modifyByDays(1);
283
    }
284
285
    /**
286
     * @param int   $hour
287
     * @param int   $minute
288
     * @param int   $second
289
     *
290
     * @return LocalDate
291
     */
292 18
    public function modifyTime($hour, $minute, $second)
293
    {
294 18
        $hour   = (int) $hour;
295 18
        $minute = (int) $minute;
296 18
        $second = (int) $second;
297
298 18
        $clonedDate = clone $this->date;
299
300 18
        $clonedDate->setTime((int) $hour, (int) $minute, (int) $second);
301
302 18
        return new LocalDate($clonedDate, $this->timezone);
303
    }
304
305
    /**
306
     * @param int $numSeconds
307
     *
308
     * @return LocalDate
309
     */
310 85
    public function modifyBySeconds($numSeconds)
311
    {
312 85
        return LocalDate::fromTimestamp(
313 85
            $this->getTimestamp() + (int) $numSeconds,
314 85
            $this->getTimezone()
315 85
        );
316
    }
317
318
    /**
319
     * @param float $numMinutes
320
     *
321
     * @return LocalDate
322
     */
323 19
    public function modifyByMinutes($numMinutes)
324
    {
325 19
        return $this->modifyBySeconds(((double) $numMinutes) * 60);
326
    }
327
328
    /**
329
     * @param float $numHours
330
     *
331
     * @return LocalDate
332
     */
333 19
    public function modifyByHours($numHours)
334
    {
335 19
        return $this->modifyBySeconds(((double) $numHours) * 3600);
336
    }
337
338
    /**
339
     * @param float $numDays
340
     *
341
     * @return LocalDate
342
     */
343 29
    public function modifyByDays($numDays)
344
    {
345 29
        return $this->modifyBySeconds(((double) $numDays) * 86400);
346
    }
347
348
    /**
349
     * @param \DateInterval|string $interval
350
     *
351
     * @deprecated use addInterval or subInterval
352
     *
353
     * @return LocalDate
354
     */
355
    public function modifyByInterval($interval)
356
    {
357
        return $this->addInterval($interval);
358
    }
359
360
    /**
361
     * @param \DateInterval|string $interval
362
     *
363
     * @return LocalDate
364
     */
365 10 View Code Duplication
    public function addInterval($interval)
366
    {
367 10
        if (!$interval instanceof \DateInterval) {
368 10
            $interval = new \DateInterval($interval);
369 10
        }
370
371 10
        $dateCloned = clone $this->date;
372 10
        $dateCloned->add($interval);
373
374 10
        return new LocalDate($dateCloned, $this->timezone);
375
    }
376
377
    /**
378
     * @param \DateInterval|string $interval
379
     *
380
     * @return LocalDate
381
     */
382 10 View Code Duplication
    public function subInterval($interval)
383
    {
384 10
        if (!$interval instanceof \DateInterval) {
385 10
            $interval = new \DateInterval($interval);
386 10
        }
387
388 10
        $dateCloned = clone $this->date;
389 10
        $dateCloned->sub($interval);
390
391 10
        return new LocalDate($dateCloned, $this->timezone);
392
    }
393
394
    /**
395
     * @param $minutesInterval
396
     *
397
     * @return LocalDate
398
     */
399 1
    public function alignToMinutesInterval($minutesInterval)
400
    {
401 1
        if ($minutesInterval <= 0) {
402 1
            return $this;
403
        }
404
405
        // This is a work-around for the day-light-saving shift days
406
        // If we would use minutesIntoDay and then add those to startOfDay, we loose one hour.
407
        // Example would be '2015-03-29 11:20' with tz 'Europe/Berlin' would result in '2015-03-29 10:00'
408 1
        $minutesIntoDay = ((int)$this->date->format('H') * 60) + ((int)$this->date->format('i'));
409
        // cut off partial intervals
410 1
        $corrected = ((int)($minutesIntoDay / $minutesInterval)) * $minutesInterval;
411
412 1
        return $this->getStartOfDay()->modifyByMinutes($corrected);
413
    }
414
415
    /**
416
     * @return int
417
     */
418 1
    public function getMinutesIntoDay()
419
    {
420 1
        $day  = $this->getStartOfDay();
421 1
        $diff = $this->getTimestamp() - $day->getTimestamp();
422
423 1
        return (int) ($diff / 60);
424
    }
425
426
    /**
427
     * Get the hours portion of the current time
428
     *
429
     * @return int
430
     */
431 1
    public function getHours()
432
    {
433 1
        return (int) $this->format('H');
434
    }
435
436
    /**
437
     * Get the weekday with Sun = 0, Mon = 1, ... Sat = 6
438
     *
439
     * @return int
440
     */
441 1
    public function getWeekday()
442
    {
443 1
        $day = (int) $this->format('N');
444
445 1
        if ($day === 7) {
446 1
            return 0;
447
        }
448
449 1
        return $day;
450
    }
451
452
    ////  COMPARISON METHODS  //////////////////////////////////////////////////////////////////////////////////////////
453
454
    /**
455
     * Returns the data that is earlier, either the current or the other
456
     *
457
     * @param LocalDate|\DateTime $other
458
     *
459
     * @return LocalDate
460
     */
461 1
    public function min($other)
462
    {
463 1
        $other = $this->ensure($other);
464
465 1
        return $this->isBefore($other) ? $this : $other;
466
    }
467
468
    /**
469
     * Returns the data that is later, either the current or the other
470
     *
471
     * @param LocalDate|\DateTime $other
472
     *
473
     * @return LocalDate
474
     */
475 1
    public function max($other)
476
    {
477 1
        $other = $this->ensure($other);
478
479 1
        return $this->isAfter($other) ? $this : $other;
480
    }
481
482
    /**
483
     * Get the number of seconds between this and the other.
484
     *
485
     * The result will be negative when this is after the other
486
     *
487
     * @param LocalDate|\DateTime $other
488
     *
489
     * @return float
490
     */
491 82
    public function diffInSeconds($other)
492
    {
493 82
        $other = $this->ensure($other);
494
495 82
        return ($other->getTimestamp() - $this->getTimestamp());
496
    }
497
498
    /**
499
     * Get the number of minutes between this and the other.
500
     *
501
     * The result will be negative when this is after the other
502
     *
503
     * @param LocalDate|\DateTime $other
504
     *
505
     * @return float
506
     */
507 18
    public function diffInMinutes($other)
508
    {
509 18
        $other = $this->ensure($other);
510
511 18
        return $this->diffInSeconds($other) / 60;
512
    }
513
514
    /**
515
     * Get the number of minutes between this and the other.
516
     *
517
     * The result will be negative when this is after the other
518
     *
519
     * @param LocalDate|\DateTime $other
520
     *
521
     * @return float
522
     */
523 18
    public function diffInHours($other)
524
    {
525 18
        $other = $this->ensure($other);
526
527 18
        return $this->diffInSeconds($other) / 3600;
528
    }
529
530
    /**
531
     * Get the number of minutes between this and the other.
532
     *
533
     * The result will be negative when this is after the other
534
     *
535
     * @param LocalDate|\DateTime $other
536
     *
537
     * @return float
538
     */
539 28
    public function diffInDays($other)
540
    {
541 28
        $other = $this->ensure($other);
542
543 28
        return $this->diffInSeconds($other) / 86400;
544
    }
545
546
    /**
547
     * @param LocalDate|\DateTime $other
548
     *
549
     * @return bool
550
     */
551 1
    public function isBefore($other)
552
    {
553 1
        $other = $this->ensure($other);
554
555 1
        return $this->date < $other->date;
556
    }
557
558
    /**
559
     * @param LocalDate|\DateTime $other
560
     *
561
     * @return bool
562
     */
563 1
    public function isBeforeOrEqual($other)
564
    {
565 1
        $other = $this->ensure($other);
566
567 1
        return $this->date <= $other->date;
568
    }
569
570
    /**
571
     * @param LocalDate|\DateTime $other
572
     *
573
     * @return bool
574
     */
575 10
    public function isEqual($other)
576
    {
577 10
        $other = $this->ensure($other);
578
579 10
        return $this->date->getTimestamp() === $other->date->getTimestamp();
580
    }
581
582
    /**
583
     * @param LocalDate|\DateTime $other
584
     *
585
     * @return bool
586
     */
587 1
    public function isAfter($other)
588
    {
589 1
        $other = $this->ensure($other);
590
591 1
        return $this->date > $other->date;
592
    }
593
594
    /**
595
     * @param LocalDate|\DateTime $other
596
     *
597
     * @return bool
598
     */
599 1
    public function isAfterOrEqual($other)
600
    {
601 1
        $other = $this->ensure($other);
602
603 1
        return $this->date >= $other->date;
604
    }
605
606
    ////  PRIVATE HELPER  //////////////////////////////////////////////////////////////////////////////////////////////
607
608
    /**
609
     * @param mixed $input
610
     *
611
     * @return LocalDate
612
     */
613 96
    private function ensure($input)
614
    {
615 96
        if ($input instanceof LocalDate) {
616 96
            return $input;
617
        }
618
619 2
        if ($input instanceof \DateTime) {
620 2
            return new LocalDate($input, $this->timezone);
621
        }
622
623
        return new LocalDate($input, $this->timezone);
624
    }
625
}
626