Completed
Branch master (25a17b)
by Karsten
02:55
created

LocalDate::subInterval()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 11
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
crap 2
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
        }
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 183
    public function __construct($input, $timezone)
61
    {
62 183
        if (! $timezone instanceof \DateTimeZone) {
63 172
            $timezone = new \DateTimeZone((string) $timezone);
64
        }
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 183
        if ($input instanceof \DateTime) {
83 53
            $date = (new \DateTime())
84 53
                ->setTimestamp($input->getTimestamp())
85 53
                ->setTimezone($timezone);
86 157
        } elseif (is_numeric($input)) {
87 101
            $date = (new \DateTime())
88 101
                ->setTimestamp($input)
89 101
                ->setTimezone($timezone);
90
        } else {
91
            // when we have string input, we immediately use the timezone
92 141
            $date = new \DateTime($input, $timezone);
93
94
            // since the given 2nd parameter is ignored for dates like '...+02:00' we check if we need to convert the
95
            // timezone
96 141
            if ($date->getTimezone()->getOffset($date) !== $timezone->getOffset($date)) {
97 5
                $date = (new \DateTime())
98 5
                    ->setTimestamp($date->getTimestamp())
99 5
                    ->setTimezone($timezone);
100
            }
101
        }
102
103 183
        $this->date     = $date;
104 183
        $this->timezone = $timezone;
105 183
    }
106
107
    /**
108
     * @return string
109
     */
110 1
    public function __toString()
111
    {
112 1
        return $this->format();
113
    }
114
115
    /**
116
     * @return \DateTime
117
     */
118 8
    public function getDate()
119
    {
120 8
        return clone $this->date;
121
    }
122
123
    /**
124
     * @return int
125
     */
126 103
    public function getTimestamp()
127
    {
128 103
        return $this->date->getTimestamp();
129
    }
130
131
    /**
132
     * @return \DateTimeZone
133
     */
134 98
    public function getTimezone()
135
    {
136 98
        return clone $this->timezone;
137
    }
138
139
140
    /**
141
     * Get the offset of the timezone in seconds
142
     *
143
     * @return int
144
     *
145
     * @see DateTime::getOffset
146
     */
147 2
    public function getOffset()
148
    {
149 2
        return $this->date->getOffset();
150
    }
151
152
    /**
153
     * @return float
154
     */
155 1
    public function getOffsetInMinutes()
156
    {
157 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
158
159 1
        return $offset / 60;
160
    }
161
162
    /**
163
     * @return float
164
     */
165 1
    public function getOffsetInHours()
166
    {
167 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
168
169 1
        return $offset / 3600;
170
    }
171
172
    /**
173
     * @param string $format
174
     *
175
     * @return string
176
     */
177 153
    public function format($format = 'c')
178
    {
179 153
        return $this->date->format($format);
180
    }
181
182
    /**
183
     * @return LocalDate
184
     */
185 2
    public function getClone()
186
    {
187 2
        return new LocalDate($this->date, $this->timezone);
188
    }
189
190
    /**
191
     * Get the time saving shift on this date in seconds
192
     *
193
     * @return int
194
     */
195 1
    public function getDaylightSavingShift()
196
    {
197 1
        $previousNoon = $this->getStartOfPreviousDay()->modifyByHours(12);
198
199 1
        return  $this->getOffset() - $previousNoon->getOffset();
200
    }
201
202
    /**
203
     * @return LocalDate
204
     */
205 18
    public function getStartOfDay()
206
    {
207 18
        return $this->modifyTime(0, 0, 0);
208
    }
209
210
    /**
211
     * Get the nearest start of a day, either the current or the next day depending on the time in the day
212
     *
213
     * Every time BEFORE 12:00 will result in the start of that day.
214
     * Every time AFTER and INCLUDING 12:00 will result in the start of the next day.
215
     *
216
     * @return LocalDate
217
     */
218 1
    public function getNearestStartOfDay()
219
    {
220 1
        if ($this->getHours() < 12) {
221 1
            return $this->getStartOfDay();
222
        }
223
224 1
        return $this->getStartOfNextDay();
225
    }
226
227
    /**
228
     * @return LocalDate
229
     */
230 2 View Code Duplication
    public function getStartOfPreviousDay()
231
    {
232 2
        $dateCloned = clone $this->getDate();
233
234
        // take switches between summer and winter time into account
235 2
        if ($dateCloned->format('H') > 21) {
236 1
            $dateCloned->modify('-27 hours');
237
        } else {
238 2
            $dateCloned->modify('-24 hours');
239
        }
240
241 2
        $temp = new LocalDate($dateCloned, $this->timezone);
242
243 2
        return $temp->getStartOfDay();
244
    }
245
246
    /**
247
     * @return LocalDate
248
     */
249 2 View Code Duplication
    public function getStartOfNextDay()
250
    {
251 2
        $dateCloned = clone $this->getDate();
252
253
        // take switches between summer and winter time into account
254 2
        if ($dateCloned->format('H') < 3) {
255 1
            $dateCloned->modify('+27 hours');
256
        } else {
257 2
            $dateCloned->modify('+24 hours');
258
        }
259
260 2
        $temp = new LocalDate($dateCloned, $this->timezone);
261
262 2
        return $temp->getStartOfDay();
263
    }
264
265
    /**
266
     * @return LocalDate
267
     */
268 1
    public function getEndOfDay()
269
    {
270 1
        return $this->getStartOfDay()->modifyByDays(1);
271
    }
272
273
    /**
274
     * @param int   $hour
275
     * @param int   $minute
276
     * @param int   $second
277
     *
278
     * @return LocalDate
279
     */
280 18
    public function modifyTime($hour, $minute, $second)
281
    {
282 18
        $hour   = (int) $hour;
283 18
        $minute = (int) $minute;
284 18
        $second = (int) $second;
285
286 18
        $clonedDate = clone $this->date;
287
288 18
        $clonedDate->setTime((int) $hour, (int) $minute, (int) $second);
289
290 18
        return new LocalDate($clonedDate, $this->timezone);
291
    }
292
293
    /**
294
     * @param int $numSeconds
295
     *
296
     * @return LocalDate
297
     */
298 85
    public function modifyBySeconds($numSeconds)
299
    {
300 85
        return LocalDate::fromTimestamp(
301 85
            $this->getTimestamp() + (int) $numSeconds,
302 85
            $this->getTimezone()
303
        );
304
    }
305
306
    /**
307
     * @param float $numMinutes
308
     *
309
     * @return LocalDate
310
     */
311 19
    public function modifyByMinutes($numMinutes)
312
    {
313 19
        return $this->modifyBySeconds(((double) $numMinutes) * 60);
314
    }
315
316
    /**
317
     * @param float $numHours
318
     *
319
     * @return LocalDate
320
     */
321 19
    public function modifyByHours($numHours)
322
    {
323 19
        return $this->modifyBySeconds(((double) $numHours) * 3600);
324
    }
325
326
    /**
327
     * @param float $numDays
328
     *
329
     * @return LocalDate
330
     */
331 29
    public function modifyByDays($numDays)
332
    {
333 29
        return $this->modifyBySeconds(((double) $numDays) * 86400);
334
    }
335
336
    /**
337
     * @param \DateInterval|string $interval
338
     *
339
     * @deprecated use addInterval or subInterval
340
     *
341
     * @return LocalDate
342
     */
343
    public function modifyByInterval($interval)
344
    {
345
        return $this->addInterval($interval);
346
    }
347
348
    /**
349
     * @param \DateInterval|string $interval
350
     *
351
     * @return LocalDate
352
     */
353 6 View Code Duplication
    public function addInterval($interval)
354
    {
355 6
        if (!$interval instanceof \DateInterval) {
356 6
            $interval = new \DateInterval($interval);
357
        }
358
359 6
        $dateCloned = clone $this->date;
360 6
        $dateCloned->add($interval);
361
362 6
        return new LocalDate($dateCloned, $this->timezone);
363
    }
364
365
    /**
366
     * @param \DateInterval|string $interval
367
     *
368
     * @return LocalDate
369
     */
370 6 View Code Duplication
    public function subInterval($interval)
371
    {
372 6
        if (!$interval instanceof \DateInterval) {
373 6
            $interval = new \DateInterval($interval);
374
        }
375
376 6
        $dateCloned = clone $this->date;
377 6
        $dateCloned->sub($interval);
378
379 6
        return new LocalDate($dateCloned, $this->timezone);
380
    }
381
382
    /**
383
     * @param $minutesInterval
384
     *
385
     * @return LocalDate
386
     */
387 1
    public function alignToMinutesInterval($minutesInterval)
388
    {
389 1
        if ($minutesInterval <= 0) {
390 1
            return $this->getClone();
391
        }
392
393
        // This is a work-around for the day-light-saving shift days
394
        // If we would use minutesIntoDay and then add those to startOfDay, we loose one hour.
395
        // Example would be '2015-03-29 11:20' with tz 'Europe/Berlin' would result in '2015-03-29 10:00'
396 1
        $minutesIntoDay = ((int)$this->date->format('H') * 60) + ((int)$this->date->format('i'));
397
        // cut off partial intervals
398 1
        $corrected = ((int)($minutesIntoDay / $minutesInterval)) * $minutesInterval;
399
400 1
        return $this->getStartOfDay()->modifyByMinutes($corrected);
401
    }
402
403
    /**
404
     * @return int
405
     */
406 1
    public function getMinutesIntoDay()
407
    {
408 1
        $day  = $this->getStartOfDay();
409 1
        $diff = $this->getTimestamp() - $day->getTimestamp();
410
411 1
        return (int) ($diff / 60);
412
    }
413
414
    /**
415
     * Get the hours portion of the current time
416
     *
417
     * @return int
418
     */
419 1
    public function getHours()
420
    {
421 1
        return (int) $this->format('H');
422
    }
423
424
    /**
425
     * @return int
426
     */
427 1
    public function getWeekday()
428
    {
429 1
        return (int) $this->format('N');
430
    }
431
432
    ////  COMPARISON METHODS  //////////////////////////////////////////////////////////////////////////////////////////
433
434
    /**
435
     * Returns the data that is earlier, either the current or the other
436
     *
437
     * @param LocalDate|\DateTime $other
438
     *
439
     * @return LocalDate
440
     */
441 1
    public function min($other)
442
    {
443 1
        $other = $this->ensure($other);
444
445 1
        return $this->isBefore($other) ? $this : $other;
446
    }
447
448
    /**
449
     * Returns the data that is later, either the current or the other
450
     *
451
     * @param LocalDate|\DateTime $other
452
     *
453
     * @return LocalDate
454
     */
455 1
    public function max($other)
456
    {
457 1
        $other = $this->ensure($other);
458
459 1
        return $this->isAfter($other) ? $this : $other;
460
    }
461
462
    /**
463
     * Get the number of seconds between this and the other.
464
     *
465
     * The result will be negative when this is after the other
466
     *
467
     * @param LocalDate|\DateTime $other
468
     *
469
     * @return float
470
     */
471 82
    public function diffInSeconds($other)
472
    {
473 82
        $other = $this->ensure($other);
474
475 82
        return ($other->getTimestamp() - $this->getTimestamp());
476
    }
477
478
    /**
479
     * Get the number of minutes between this and the other.
480
     *
481
     * The result will be negative when this is after the other
482
     *
483
     * @param LocalDate|\DateTime $other
484
     *
485
     * @return float
486
     */
487 18
    public function diffInMinutes($other)
488
    {
489 18
        $other = $this->ensure($other);
490
491 18
        return $this->diffInSeconds($other) / 60;
492
    }
493
494
    /**
495
     * Get the number of minutes between this and the other.
496
     *
497
     * The result will be negative when this is after the other
498
     *
499
     * @param LocalDate|\DateTime $other
500
     *
501
     * @return float
502
     */
503 18
    public function diffInHours($other)
504
    {
505 18
        $other = $this->ensure($other);
506
507 18
        return $this->diffInSeconds($other) / 3600;
508
    }
509
510
    /**
511
     * Get the number of minutes between this and the other.
512
     *
513
     * The result will be negative when this is after the other
514
     *
515
     * @param LocalDate|\DateTime $other
516
     *
517
     * @return float
518
     */
519 28
    public function diffInDays($other)
520
    {
521 28
        $other = $this->ensure($other);
522
523 28
        return $this->diffInSeconds($other) / 86400;
524
    }
525
526
    /**
527
     * @param LocalDate|\DateTime $other
528
     *
529
     * @return bool
530
     */
531 1
    public function isBefore($other)
532
    {
533 1
        $other = $this->ensure($other);
534
535 1
        return $this->date < $other->date;
536
    }
537
538
    /**
539
     * @param LocalDate|\DateTime $other
540
     *
541
     * @return bool
542
     */
543 1
    public function isBeforeOrEqual($other)
544
    {
545 1
        $other = $this->ensure($other);
546
547 1
        return $this->date <= $other->date;
548
    }
549
550
    /**
551
     * @param LocalDate|\DateTime $other
552
     *
553
     * @return bool
554
     */
555 10
    public function isEqual($other)
556
    {
557 10
        $other = $this->ensure($other);
558
559 10
        return $this->date->getTimestamp() === $other->date->getTimestamp();
560
    }
561
562
    /**
563
     * @param LocalDate|\DateTime $other
564
     *
565
     * @return bool
566
     */
567 1
    public function isAfter($other)
568
    {
569 1
        $other = $this->ensure($other);
570
571 1
        return $this->date > $other->date;
572
    }
573
574
    /**
575
     * @param LocalDate|\DateTime $other
576
     *
577
     * @return bool
578
     */
579 1
    public function isAfterOrEqual($other)
580
    {
581 1
        $other = $this->ensure($other);
582
583 1
        return $this->date >= $other->date;
584
    }
585
586
    ////  PRIVATE HELPER  //////////////////////////////////////////////////////////////////////////////////////////////
587
588
    /**
589
     * @param mixed $input
590
     *
591
     * @return LocalDate
592
     */
593 96
    private function ensure($input)
594
    {
595 96
        if ($input instanceof LocalDate) {
596 96
            return $input;
597
        }
598
599 2
        return new LocalDate($input, $this->timezone);
600
    }
601
}
602