Passed
Push — master ( c327c2...0adcf0 )
by Ondřej
02:45
created

TimestampBase::assertRanges()   B

Complexity

Conditions 15
Paths 7

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 12
nc 7
nop 6
dl 0
loc 19
rs 7.3427
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
namespace Ivory\Value;
4
5
/**
6
 * @internal Only for the purpose of Ivory itself.
7
 */
8
abstract class TimestampBase extends DateBase
9
{
10
    /**
11
     * @param int|float $timestamp the UNIX timestamp;
12
     *                       besides an integer, a float is also accepted, which may contain fractional seconds;
13
     *                       note that a UNIX timestamp represents the number of seconds since 1970-01-01 UTC, i.e., it
14
     *                         corresponds to usage of PHP functions {@link gmmktime()} and {@link gmdate()} rather than
15
     *                         {@link mktime()} or {@link date()}
16
     * @return static
17
     */
18
    public static function fromUnixTimestamp($timestamp)
19
    {
20
        $str = gmdate('Y-m-d H:i:s', (int)$timestamp);
21
        $dt = new static(0, new \DateTimeImmutable($str, self::getUTCTimeZone()));
22
23
        // gmdate() only accepts an integer - add the fractional part separately
24
        $frac = $timestamp - (int)$timestamp;
25
        if ($frac) {
26
            $dt = $dt->addSecond($frac);
27
        }
28
        return $dt;
29
    }
30
31
    /**
32
     * @param string $isoDateTimeString
33
     * @param \DateTimeZone $forcedTimezone
34
     * @return \DateTimeImmutable
35
     */
36
    protected static function isoStringToDateTime(
37
        string $isoDateTimeString,
38
        ?\DateTimeZone $forcedTimezone = null
39
    ): \DateTimeImmutable {
40
        // check out for more than 4 digits for the year - something date_create_immutable() does not handle properly
41
        $addYears = 0;
42
        $dateCreateInput = preg_replace_callback(
43
            '~\d{5,}(?=(?:-\d+-\d+|\d{4})(?:\s+|T)\d)~', // supports both dash-separated date/time parts and also the form without dash separators
44
            function ($y) use (&$addYears) {
45
                $res = $y[0] % 10000;
46
                $addYears = $y[0] - $res;
47
                return $res;
48
            },
49
            $isoDateTimeString,
50
            1
51
        );
52
53
        if ($forcedTimezone !== null) {
54
            // the date_create_immutable() prefers the timezone (if any) in the input string - which we must cut off
55
            $dateCreateInput = preg_replace('~Z|[-+]\d{2}(?::?\d{2})?$~', '', $dateCreateInput, 1);
56
        }
57
58
        if ($addYears) {
59
            $sgn = ($dateCreateInput[0] == '-' ? '-' : '+');
60
            $dateCreateInput .= " $sgn$addYears years";
61
        }
62
63
        // using the procedural style as it does not throw the generic \Exception
64
        $dt = date_create_immutable($dateCreateInput, $forcedTimezone);
65
        if ($dt === false) {
66
            throw new \InvalidArgumentException('$isoDateString');
67
        }
68
69
        return $dt;
70
    }
71
72
    protected static function floatToTwoPlaces($float): string
73
    {
74
        if ($float >= 10) {
75
            return (string)$float;
76
        } else {
77
            return '0' . (float)$float;
78
        }
79
    }
80
81
    protected static function inRanges(int $month, int $day, int $hour, int $minute, $second): bool
82
    {
83
        return (
84
            $month >= 1 && $month <= 12 &&
85
            $day >= 1 && $day <= 31 &&
86
            (
87
                ($hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59 && $second >= 0 && $second < 61)
88
                ||
89
                ($hour == 24 && $minute == 0 && $second == 0)
90
            )
91
        );
92
    }
93
94
    protected static function assertRanges(int $year, int $month, int $day, int $hour, int $minute, $second): void
95
    {
96
        if ($year == 0) {
97
            throw new \InvalidArgumentException('$year zero is undefined');
98
        }
99
        if ($month < 1 || $month > 12) {
100
            throw new \OutOfRangeException('$month out of range');
101
        }
102
        if ($day < 1 || $day > 31) { // days in the month shall be verified ex post
103
            throw new \OutOfRangeException('$day out of range');
104
        }
105
        if ($hour < 0 || $hour > 24 || ($hour == 24 && $minute > 0 && $second > 0)) {
106
            throw new \OutOfRangeException('$hour out of range');
107
        }
108
        if ($minute < 0 || $minute > 60) {
109
            throw new \OutOfRangeException('$minute out of range');
110
        }
111
        if ($second < 0 || $second >= 61) {
112
            throw new \OutOfRangeException('$second out of range');
113
        }
114
    }
115
116
117
    /**
118
     * @return int|null the hour part of the date/time;
119
     *                  <tt>null</tt> iff the date/time is not finite
120
     */
121
    final public function getHour(): ?int
122
    {
123
        return ($this->inf ? null : (int)$this->dt->format('G'));
124
    }
125
126
    /**
127
     * @return int|null the minute part of the date/time;
128
     *                  <tt>null</tt> iff the date/time is not finite
129
     */
130
    final public function getMinute(): ?int
131
    {
132
        return ($this->inf ? null : (int)$this->dt->format('i'));
133
    }
134
135
    /**
136
     * @return int|float|null the second part of the date/time, including the fractional seconds;
137
     *                        <tt>null</tt> iff the date/time is not finite
138
     */
139
    final public function getSecond()
140
    {
141
        if ($this->inf) {
142
            return null;
143
        } elseif ($this->dt->format('u')) {
144
            return (float)$this->dt->format('s.u');
145
        } else {
146
            return (int)$this->dt->format('s');
147
        }
148
    }
149
150
    /**
151
     * Adds a given number of years, months, days, hours, minutes, and seconds to this date/time and returns the result.
152
     *
153
     * Only affects finite date/times - an infinite date/time is returned as is.
154
     *
155
     * Note that addition of months respects the month days, and might actually change the day part. Example:
156
     * - adding 1 month to `2015-05-31` results in `2015-07-01` (June only has 30 days).
157
     *
158
     * Addition of years and months is done prior to addition of days, so the actual number of days in month is
159
     * evaluated with respect to the new month. Examples:
160
     * - adding 2 months and 1 day to `2015-05-31` results in `2015-08-01`,
161
     * - adding 1 month and 1 day to `2015-05-31` results in `2015-07-02` (June only has 30 days),
162
     * - adding 1 month and 1 day to `2015-02-28` results in `2015-03-29`,
163
     * - adding 1 year and 1 day to `2015-02-28` results in `2016-02-29`,
164
     * - adding 1 year and 1 day to `2016-02-28` results in `2017-03-01`.
165
     *
166
     * @param int $years
167
     * @param int $months
168
     * @param int $days
169
     * @param int $hours
170
     * @param int $minutes
171
     * @param int|float $seconds
172
     * @return static the date/time <tt>$years</tt> years, <tt>$months</tt> months, <tt>$days</tt> days,
173
     *                  <tt>$hours</tt> hours, <tt>$minutes</tt> minutes, and <tt>$seconds</tt> seconds after this
174
     *                  date/time
175
     */
176
    public function addParts(int $years, int $months, int $days, int $hours, int $minutes, $seconds)
177
    {
178
        return $this->addPartsImpl($years, $months, $days, $hours, $minutes, $seconds);
179
    }
180
181
    /**
182
     * Adds a given number of hours (1 by default) to this date/time and returns the result. Only affects finite dates.
183
     *
184
     * @param int $hours
185
     * @return static the date <tt>$hours</tt> hours after (or before, if negative) this date/time
186
     */
187
    public function addHour(int $hours = 1)
188
    {
189
        return $this->addPartsImpl(0, 0, 0, $hours, 0, 0);
190
    }
191
192
    /**
193
     * Adds a given number of minutes (1 by default) to this date/time and returns the result. Only affects finite
194
     * dates.
195
     *
196
     * @param int $minutes
197
     * @return static the date <tt>$minutes</tt> minutes after (or before, if negative) this date/time
198
     */
199
    public function addMinute(int $minutes = 1)
200
    {
201
        return $this->addPartsImpl(0, 0, 0, 0, $minutes, 0);
202
    }
203
204
    /**
205
     * Adds a given number of seconds (1 by default) to this date/time and returns the result. Only affects finite
206
     * dates.
207
     *
208
     * @param int|float $seconds
209
     * @return static the date <tt>$seconds</tt> seconds after (or before, if negative) this date/time
210
     */
211
    public function addSecond($seconds = 1)
212
    {
213
        return $this->addPartsImpl(0, 0, 0, 0, 0, $seconds);
214
    }
215
}
216