Completed
Push — master ( eb9b54...633023 )
by Karsten
02:46
created

LocalDate::getDstStartOfDayPlusHours()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
cc 1
eloc 6
nc 1
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 91
    public static function fromTimestamp($timestamp, $timezone)
28
    {
29 91
        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 183
    public function __construct($input, $timezone)
61
    {
62 183
        if (! $timezone instanceof \DateTimeZone) {
63 172
            $timezone = new \DateTimeZone((string) $timezone);
64 172
        }
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 52
            $date = (new \DateTime())
84 52
                ->setTimestamp($input->getTimestamp())
85 52
                ->setTimezone($timezone);
86 183
        } elseif (is_numeric($input)) {
87 100
            $date = (new \DateTime())
88 100
                ->setTimestamp($input)
89 100
                ->setTimezone($timezone);
90 100
        } 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 5
            }
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 102
    public function getTimestamp()
127
    {
128 102
        return $this->date->getTimestamp();
129
    }
130
131
    /**
132
     * @return \DateTimeZone
133
     */
134 97
    public function getTimezone()
135
    {
136 97
        return clone $this->timezone;
137
    }
138
139
    /**
140
     * @return int
141
     */
142 2
    public function getOffset()
143
    {
144 2
        return $this->date->getOffset();
145
    }
146
147
    /**
148
     * @return float
149
     */
150 1
    public function getOffsetInMinutes()
151
    {
152 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
153
154 1
        return $offset / 60;
155
    }
156
157
    /**
158
     * @return float
159
     */
160 1
    public function getOffsetInHours()
161
    {
162 1
        $offset = $this->getTimezone()->getOffset($this->getDate());
163
164 1
        return $offset / 3600;
165
    }
166
167
    /**
168
     * @param string $format
169
     *
170
     * @return string
171
     */
172 153
    public function format($format = 'c')
173
    {
174 153
        return $this->date->format($format);
175
    }
176
177
    /**
178
     * @return LocalDate
179
     */
180 1
    public function getClone()
181
    {
182 1
        return new LocalDate($this->date, $this->timezone);
183
    }
184
185
    /**
186
     * Get the time saving shift on this date in seconds
187
     *
188
     * @return int
189
     */
190 1
    public function getDaylightSavingShift()
191
    {
192 1
        $previousNoon = $this->getStartOfPreviousDay()->modifyByHours(12);
193
194 1
        return  $this->getOffset() - $previousNoon->getOffset();
195
    }
196
197
    //// Daylight saving time shift aware methods //////////////////////////////////////////////////////////////////////
198
199
    /**
200
     * Add hours to start of day while also respecting Daylight-saving-time shift
201
     *
202
     * E.g. on 2016-03-27T10:00:00 in Berlin is only 9 hours after the start of the day.
203
     *
204
     * @param $hours
205
     *
206
     * @return LocalDate
207
     */
208
    public function getDstStartOfDayPlusHours ($hours)
209
    {
210
        $startOfDay = $this->getStartOfDay();
211
        $startOfDayOffset = $startOfDay->getOffset();
212
213
        $result = $startOfDay->modifyByHours($hours);
214
        $resultOffset = $result->getOffset();
215
216
        return $result->modifyBySeconds($startOfDayOffset)->modifyBySeconds(0 - $resultOffset);
217
    }
218
219
    //// Modification methods //////////////////////////////////////////////////////////////////////////////////////////
220
221
    /**
222
     * @return LocalDate
223
     */
224 17
    public function getStartOfDay()
225
    {
226 17
        return $this->modifyTime(0, 0, 0);
227
    }
228
229
    /**
230
     * Get the nearest start of a day, either the current or the next day depending on the time in the day
231
     *
232
     * @return LocalDate
233
     */
234 1
    public function getNearestStartOfDay()
235
    {
236 1
        if ($this->getHours() < 12) {
237 1
            return $this->getStartOfDay();
238
        }
239
240 1
        return $this->getStartOfNextDay();
241
    }
242
243
    /**
244
     * @return LocalDate
245
     */
246 2 View Code Duplication
    public function getStartOfPreviousDay()
247
    {
248 2
        $dateCloned = clone $this->getDate();
249
250
        // take switches between summer and winter time into account
251 2
        if ($dateCloned->format('H') > 21) {
252 1
            $dateCloned->modify('-27 hours');
253 1
        } else {
254 2
            $dateCloned->modify('-24 hours');
255
        }
256
257 2
        $temp = new LocalDate($dateCloned, $this->timezone);
258
259 2
        return $temp->getStartOfDay();
260
    }
261
262
    /**
263
     * @return LocalDate
264
     */
265 2 View Code Duplication
    public function getStartOfNextDay()
266
    {
267 2
        $dateCloned = clone $this->getDate();
268
269
        // take switches between summer and winter time into account
270 2
        if ($dateCloned->format('H') < 3) {
271 1
            $dateCloned->modify('+27 hours');
272 1
        } else {
273 2
            $dateCloned->modify('+24 hours');
274
        }
275
276 2
        $temp = new LocalDate($dateCloned, $this->timezone);
277
278 2
        return $temp->getStartOfDay();
279
    }
280
281
    /**
282
     * @return LocalDate
283
     */
284 1
    public function getEndOfDay()
285
    {
286 1
        return $this->getStartOfDay()->modifyByDays(1);
287
    }
288
289
    /**
290
     * @param int   $hour
291
     * @param int   $minute
292
     * @param int   $second
293
     *
294
     * @return LocalDate
295
     */
296 17
    public function modifyTime($hour, $minute, $second)
297
    {
298 17
        $hour   = (int) $hour;
299 17
        $minute = (int) $minute;
300 17
        $second = (int) $second;
301
302 17
        $clonedDate = clone $this->date;
303
304 17
        $clonedDate->setTime((int) $hour, (int) $minute, (int) $second);
305
306 17
        return new LocalDate($clonedDate, $this->timezone);
307
    }
308
309
    /**
310
     * @param int $numSeconds
311
     *
312
     * @return LocalDate
313
     */
314 84
    public function modifyBySeconds($numSeconds)
315
    {
316 84
        return LocalDate::fromTimestamp(
317 84
            $this->getTimestamp() + (int) $numSeconds,
318 84
            $this->getTimezone()
319 84
        );
320
    }
321
322
    /**
323
     * @param float $numMinutes
324
     *
325
     * @return LocalDate
326
     */
327 18
    public function modifyByMinutes($numMinutes)
328
    {
329 18
        return $this->modifyBySeconds(((double) $numMinutes) * 60);
330
    }
331
332
    /**
333
     * @param float $numHours
334
     *
335
     * @return LocalDate
336
     */
337 19
    public function modifyByHours($numHours)
338
    {
339 19
        return $this->modifyBySeconds(((double) $numHours) * 3600);
340
    }
341
342
    /**
343
     * @param float $numDays
344
     *
345
     * @return LocalDate
346
     */
347 29
    public function modifyByDays($numDays)
348
    {
349 29
        return $this->modifyBySeconds(((double) $numDays) * 86400);
350
    }
351
352
    /**
353
     * @param \DateInterval|string $interval
354
     *
355
     * @deprecated use addInterval or subInterval
356
     *
357
     * @return LocalDate
358
     */
359
    public function modifyByInterval($interval)
360
    {
361
        return $this->addInterval($interval);
362
    }
363
364
    /**
365
     * @param \DateInterval|string $interval
366
     *
367
     * @return LocalDate
368
     */
369 6 View Code Duplication
    public function addInterval($interval)
370
    {
371 6
        if (!$interval instanceof \DateInterval) {
372 6
            $interval = new \DateInterval($interval);
373 6
        }
374
375 6
        $dateCloned = clone $this->date;
376 6
        $dateCloned->add($interval);
377
378 6
        return new LocalDate($dateCloned, $this->timezone);
379
    }
380
381
    /**
382
     * @param \DateInterval|string $interval
383
     *
384
     * @return LocalDate
385
     */
386 6 View Code Duplication
    public function subInterval($interval)
387
    {
388 6
        if (!$interval instanceof \DateInterval) {
389 6
            $interval = new \DateInterval($interval);
390 6
        }
391
392 6
        $dateCloned = clone $this->date;
393 6
        $dateCloned->sub($interval);
394
395 6
        return new LocalDate($dateCloned, $this->timezone);
396
    }
397
398
    /**
399
     * @param $minutesInterval
400
     *
401
     * @return LocalDate
402
     */
403 1
    public function alignToMinutesInterval($minutesInterval)
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
        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
     * @return int
438
     */
439 1
    public function getWeekday()
440
    {
441 1
        return (int) $this->format('N');
442
    }
443
444
    ////  COMPARISON METHODS  //////////////////////////////////////////////////////////////////////////////////////////
445
446
    /**
447
     * Returns the data that is earlier, either the current or the other
448
     *
449
     * @param LocalDate|\DateTime $other
450
     *
451
     * @return LocalDate
452
     */
453 1
    public function min($other)
454
    {
455 1
        $other = $this->ensure($other);
456
457 1
        return $this->isBefore($other) ? $this : $other;
458
    }
459
460
    /**
461
     * Returns the data that is later, either the current or the other
462
     *
463
     * @param LocalDate|\DateTime $other
464
     *
465
     * @return LocalDate
466
     */
467 1
    public function max($other)
468
    {
469 1
        $other = $this->ensure($other);
470
471 1
        return $this->isAfter($other) ? $this : $other;
472
    }
473
474
    /**
475
     * Get the number of seconds between this and the other.
476
     *
477
     * The result will be negative when this is after the other
478
     *
479
     * @param LocalDate|\DateTime $other
480
     *
481
     * @return float
482
     */
483 82
    public function diffInSeconds($other)
484
    {
485 82
        $other = $this->ensure($other);
486
487 82
        return ($other->getTimestamp() - $this->getTimestamp());
488
    }
489
490
    /**
491
     * Get the number of minutes between this and the other.
492
     *
493
     * The result will be negative when this is after the other
494
     *
495
     * @param LocalDate|\DateTime $other
496
     *
497
     * @return float
498
     */
499 18
    public function diffInMinutes($other)
500
    {
501 18
        $other = $this->ensure($other);
502
503 18
        return $this->diffInSeconds($other) / 60;
504
    }
505
506
    /**
507
     * Get the number of minutes between this and the other.
508
     *
509
     * The result will be negative when this is after the other
510
     *
511
     * @param LocalDate|\DateTime $other
512
     *
513
     * @return float
514
     */
515 18
    public function diffInHours($other)
516
    {
517 18
        $other = $this->ensure($other);
518
519 18
        return $this->diffInSeconds($other) / 3600;
520
    }
521
522
    /**
523
     * Get the number of minutes between this and the other.
524
     *
525
     * The result will be negative when this is after the other
526
     *
527
     * @param LocalDate|\DateTime $other
528
     *
529
     * @return float
530
     */
531 28
    public function diffInDays($other)
532
    {
533 28
        $other = $this->ensure($other);
534
535 28
        return $this->diffInSeconds($other) / 86400;
536
    }
537
538
    /**
539
     * @param LocalDate|\DateTime $other
540
     *
541
     * @return bool
542
     */
543 1
    public function isBefore($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 1
    public function isBeforeOrEqual($other)
556
    {
557 1
        $other = $this->ensure($other);
558
559 1
        return $this->date <= $other->date;
560
    }
561
562
    /**
563
     * @param LocalDate|\DateTime $other
564
     *
565
     * @return bool
566
     */
567 10
    public function isEqual($other)
568
    {
569 10
        $other = $this->ensure($other);
570
571 10
        return $this->date->getTimestamp() === $other->date->getTimestamp();
572
    }
573
574
    /**
575
     * @param LocalDate|\DateTime $other
576
     *
577
     * @return bool
578
     */
579 1
    public function isAfter($other)
580
    {
581 1
        $other = $this->ensure($other);
582
583 1
        return $this->date > $other->date;
584
    }
585
586
    /**
587
     * @param LocalDate|\DateTime $other
588
     *
589
     * @return bool
590
     */
591 1
    public function isAfterOrEqual($other)
592
    {
593 1
        $other = $this->ensure($other);
594
595 1
        return $this->date >= $other->date;
596
    }
597
598
    ////  PRIVATE HELPER  //////////////////////////////////////////////////////////////////////////////////////////////
599
600
    /**
601
     * @param mixed $input
602
     *
603
     * @return LocalDate
604
     */
605 96
    private function ensure($input)
606
    {
607 96
        if ($input instanceof LocalDate) {
608 96
            return $input;
609
        }
610
611 2
        if ($input instanceof \DateTime) {
612 2
            return new LocalDate($input, $this->timezone);
613
        }
614
615
        return new LocalDate($input, $this->timezone);
616
    }
617
}
618