Completed
Push — master ( 73c8dd...7be29b )
by Ondřej
03:37
created

TimeBase::partsToSecStrict()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 20
Code Lines 11

Duplication

Lines 8
Ratio 40 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 8
loc 20
rs 7.2765
cc 10
eloc 11
nc 8
nop 3

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
namespace Ivory\Value;
3
4
use Ivory\Utils\ComparableWithPhpOperators;
5
use Ivory\Utils\IComparable;
6
7
/**
8
 * Common base for time representations.
9
 *
10
 * @internal Only for the purpose of Ivory itself.
11
 */
12
abstract class TimeBase implements IComparable
13
{
14
    use ComparableWithPhpOperators;
15
16
    /** Number of decimal digits of precision in the fractional seconds part. */
17
    const PRECISION = 6;
18
19
    /** @var int|float */
20
    protected $sec;
21
22
23
    /**
24
     * @internal
25
     * @param int $hour
26
     * @param int $minute
27
     * @param int|float $second
28
     * @return int|float
29
     * @throws \OutOfRangeException
30
     */
31
    protected static function partsToSec($hour, $minute, $second)
32
    {
33
        $s = $hour * 60 * 60 + $minute * 60 + $second;
34
35
        if ($s < 0) {
36
            throw new \OutOfRangeException('The resulting time underruns 00:00:00');
37
        } elseif ($s > 24 * 60 * 60) {
38
            throw new \OutOfRangeException('The resulting time exceeds 24:00:00');
39
        } else {
40
            return $s;
41
        }
42
    }
43
44
    /**
45
     * @internal
46
     * @param int $hour
47
     * @param int $minute
48
     * @param int|float $second
49
     * @return int|float
50
     * @throws \OutOfRangeException
51
     */
52
    protected static function partsToSecStrict($hour, $minute, $second)
53
    {
54 View Code Duplication
        if ($hour == 24) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
55
            if ($minute > 0 || $second > 0) {
56
                throw new \OutOfRangeException('with hour 24, the minutes and seconds must be zero');
57
            }
58
        } elseif ($hour < 0 || $hour > 24) {
59
            throw new \OutOfRangeException('hours');
60
        }
61
62
        if ($minute < 0 || $minute > 59) {
63
            throw new \OutOfRangeException('minutes');
64
        }
65
66
        if ($second < 0 || $second >= 61) {
67
            throw new \OutOfRangeException('seconds');
68
        }
69
70
        return $hour * 60 * 60 + $minute * 60 + $second;
71
    }
72
73
    /**
74
     * @internal
75
     * @param int|float $timestamp
76
     * @return int
77
     */
78
    protected static function cutUnixTimestampToSec($timestamp)
79
    {
80
        if ($timestamp == 24 * 60 * 60) {
81
            return $timestamp;
82
        }
83
84
        $dayRes = (int)($timestamp - ($timestamp % (24 * 60 * 60)));
85
        $sec = $timestamp - $dayRes;
86
        if ($sec < 0) {
87
            $sec += 24 * 60 * 60;
88
        }
89
90
        return $sec;
91
    }
92
93
    /**
94
     * @internal Only for the purpose of Ivory itself.
95
     * @param int|float $sec
96
     */
97
    protected function __construct($sec)
98
    {
99
        $this->sec = $sec;
100
    }
101
102
    /**
103
     * @return int the hours part of the time (0-24)
104
     */
105
    public function getHours()
106
    {
107
        return (int)($this->sec / (60 * 60));
108
    }
109
110
    /**
111
     * @return int the minutes part of the time (0-59)
112
     */
113
    public function getMinutes()
114
    {
115
        return ($this->sec / 60) % 60;
116
    }
117
118
    /**
119
     * @return int|float the seconds part of the time (0-59), potentially with the fractional part, if any
120
     */
121
    public function getSeconds()
122
    {
123
        return $this->sec - $this->getMinutes() * 60 - $this->getHours() * 60 * 60;
124
    }
125
126
    /**
127
     * @param Date|string|null $date the date for the resulting timestamp;
128
     *                               besides a {@link Date} object, an ISO date string is accepted - see
129
     *                                 {@link Date::fromISOString()};
130
     *                               the given date (if any) must be finite;
131
     *                               if not given the time on 1970-01-01 is returned, which is effectively the amount of
132
     *                                 time, in seconds, between the time this object represents and <tt>00:00:00</tt>
133
     * @return float|int the UNIX timestamp of this time on the given day
134
     * @throws \InvalidArgumentException if the date is infinite or if the <tt>$date</tt> string is not a valid ISO date
135
     *                                     string
136
     */
137
    public function toUnixTimestamp($date = null)
138
    {
139
        if ($date === null) {
140
            return $this->sec;
141
        } else {
142
            if (!$date instanceof Date) {
143
                $date = Date::fromISOString($date);
144
            }
145
            $dayTs = $date->toUnixTimestamp();
146
            if ($dayTs !== null) {
147
                return $dayTs + $this->sec;
148
            } else {
149
                throw new \InvalidArgumentException('infinite date');
150
            }
151
        }
152
    }
153
154
    /**
155
     * @param string $timeFmt the format string as accepted by {@link date()}
156
     * @return string the time formatted according to <tt>$timeFmt</tt>
157
     */
158
    public function format($timeFmt)
159
    {
160
        $ts = $this->toUnixTimestamp();
161
162
        // microseconds are not supported by gmdate(), and constructing a new \DateTime object would be overkill
163
        if (strpos($timeFmt, 'u') !== false) {
164
            $frac = round($ts - (int)$ts, self::PRECISION);
165
            $fracPart = ($frac ? substr($frac, 2) : '0'); // cut off the leading "0." for non-zero fractional seconds
166
            $fracStr = str_pad($fracPart, 6, '0', STR_PAD_RIGHT);
167
168
            $re = '~
169
                   (?<!\\\\)            # not prefixed with a backslash
170
                   ((?:\\\\\\\\)*)      # any number of pairs of backslashes, each meaning a single literal backslash
171
                   u                    # the microseconds format character to be replaced
172
                   ~x';
173
            $timeFmt = preg_replace($re, '${1}' . $fracStr, $timeFmt);
174
        }
175
176
        return gmdate($timeFmt, $ts);
177
    }
178
179
    /**
180
     * @return string the ISO representation of this time, in format <tt>HH:MM:SS[.p]</tt>;
181
     *                the fractional seconds part is only used if non-zero
182
     */
183
    public function toString()
184
    {
185
        $frac = round($this->sec - (int)$this->sec, self::PRECISION);
186
        return sprintf(
187
            '%02d:%02d:%02d%s',
188
            $this->getHours(), $this->getMinutes(), $this->getSeconds(),
189
            ($frac ? substr($frac, 1) : '') // cut off the leading "0" for non-zero fractional seconds
190
        );
191
    }
192
}
193