Duration   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 96.51%

Importance

Changes 0
Metric Value
eloc 82
dl 0
loc 227
ccs 83
cts 86
cp 0.9651
rs 9.68
c 0
b 0
f 0
wmc 34

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
C create() 0 60 14
A withoutCarryOver() 0 3 1
A adjustedTo() 0 12 3
B __toString() 0 42 10
A createFromDateString() 0 13 3
1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period;
15
16
use DateInterval;
17
use DateTimeImmutable;
18
use TypeError;
19
use function filter_var;
20
use function gettype;
21
use function is_string;
22
use function method_exists;
23
use function preg_match;
24
use function property_exists;
25
use function rtrim;
26
use function sprintf;
27
use function str_pad;
28
use const FILTER_VALIDATE_INT;
29
30
/**
31
 * League Period Duration.
32
 *
33
 * @package League.period
34
 * @author  Ignace Nyamagana Butera <[email protected]>
35
 * @since   4.2.0
36
 */
37
final class Duration extends DateInterval
38
{
39
    private const REGEXP_MICROSECONDS_INTERVAL_SPEC = '@^(?<interval>.*)(\.|,)(?<fraction>\d{1,6})[email protected]';
40
41
    private const REGEXP_MICROSECONDS_DATE_SPEC = '@^(?<interval>.*)(\.)(?<fraction>\d{1,6})[email protected]';
42
43
    private const REGEXP_CHRONO_FORMAT = '@^
44
        (?<sign>\+|-)?
45
        (((?<hour>\d+):)?(?<minute>\d+):)?
46
        ((?<second>\d+)(\.(?<fraction>\d{1,6}))?)
47
    [email protected]';
48
49
    /**
50
     * New instance.
51
     *
52
     * Returns a new instance from an Interval specification
53
     */
54 282
    public function __construct(string $interval_spec)
55
    {
56 282
        if (1 === preg_match(self::REGEXP_MICROSECONDS_INTERVAL_SPEC, $interval_spec, $matches)) {
57 3
            parent::__construct($matches['interval'].'S');
58 3
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
59 3
            return;
60
        }
61
62 282
        if (1 === preg_match(self::REGEXP_MICROSECONDS_DATE_SPEC, $interval_spec, $matches)) {
63 3
            parent::__construct($matches['interval']);
64 3
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
65 3
            return;
66
        }
67
68 279
        parent::__construct($interval_spec);
69 273
    }
70
71
    /**
72
     * Returns a continuous portion of time between two datepoints expressed as a DateInterval object.
73
     *
74
     * The duration can be
75
     * <ul>
76
     * <li>an Period object</li>
77
     * <li>a DateInterval object</li>
78
     * <li>an integer interpreted as the duration expressed in seconds.</li>
79
     * <li>a string parsable by DateInterval::createFromDateString</li>
80
     * </ul>
81
     *
82
     * @param mixed $duration a continuous portion of time
83
     *
84
     * @throws TypeError if the duration type is not a supported
85
     */
86 285
    public static function create($duration): self
87
    {
88 285
        if ($duration instanceof Period) {
89 15
            $duration = $duration->getDateInterval();
90
        }
91
92 285
        if ($duration instanceof DateInterval) {
93 48
            $new = new self('PT0S');
94 48
            foreach ($duration as $name => $value) {
95 48
                if (property_exists($new, $name)) {
96 48
                    $new->$name = $value;
97
                }
98
            }
99
100 48
            return $new;
101
        }
102
103 237
        if (false !== ($second = filter_var($duration, FILTER_VALIDATE_INT))) {
104 57
            return new self('PT'.$second.'S');
105
        }
106
107 180
        if (!is_string($duration) && !method_exists($duration, '__toString')) {
108 9
            throw new TypeError(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($duration)));
109
        }
110
111 171
        $duration = (string) $duration;
112 171
        if (1 !== preg_match(self::REGEXP_CHRONO_FORMAT, $duration, $matches)) {
113 156
            $new = self::createFromDateString($duration);
114 156
            if ($new !== false) {
0 ignored issues
show
introduced by Ignace Nyamagana Butera
The condition $new !== false is always true.
Loading history...
115 156
                return $new;
116
            }
117
118
            throw new Exception(sprintf('Unknown or bad format (%s)', $duration));
119
        }
120
121 15
        $matches['hour'] = $matches['hour'] ?? '0';
122 15
        if ('' === $matches['hour']) {
123 9
            $matches['hour'] = '0';
124
        }
125
126 15
        $matches['minute'] = $matches['minute'] ?? '0';
127 15
        if ('' === $matches['minute']) {
128 3
            $matches['minute'] = '0';
129
        }
130
131 15
        $matches['fraction'] = str_pad($matches['fraction'] ?? '0000000', 6, '0');
132 15
        $expression = $matches['hour'].' hours '.
133 15
            $matches['minute'].' minutes '.
134 15
            $matches['second'].' seconds '.$matches['fraction'].' microseconds';
135
136 15
        $instance = self::createFromDateString($expression);
137 15
        if (false === $instance) {
0 ignored issues
show
introduced by Ignace Nyamagana Butera
The condition false === $instance is always false.
Loading history...
138
            throw new Exception(sprintf('Unknown or bad format (%s)', $expression));
139
        }
140
141 15
        if ('-' === $matches['sign']) {
142 6
            $instance->invert = 1;
143
        }
144
145 15
        return $instance;
146
    }
147
148
    /**
149
     * @inheritDoc
150
     *
151
     * @param mixed $duration a date with relative parts
152
     *
153
     * @return static|false
154
     */
155 174
    public static function createFromDateString($duration): self
156
    {
157 174
        $duration = parent::createFromDateString($duration);
158 174
        if (false === $duration) {
159
            return $duration;
0 ignored issues
show
Bug Best Practice introduced by Ignace Nyamagana Butera
The expression return $duration returns the type DateInterval which includes types incompatible with the type-hinted return League\Period\Duration.
Loading history...
160
        }
161
162 174
        $new = new self('PT0S');
163 174
        foreach ($duration as $name => $value) {
164 174
            $new->$name = $value;
165
        }
166
167 174
        return $new;
168
    }
169
170
    /**
171
     * DEPRECATION WARNING! This method will be removed in the next major point release.
172
     *
173
     * @deprecated deprecated since version 4.5
174
     * @see ::format
175
     *
176
     * Returns the ISO8601 interval string representation.
177
     *
178
     * Microseconds fractions are included
179
     */
180 69
    public function __toString(): string
181
    {
182 69
        $date = 'P';
183 69
        foreach (['Y' => 'y', 'M' => 'm', 'D' => 'd'] as $key => $value) {
184 69
            if (0 !== $this->$value) {
185 39
                $date .= '%'.$value.$key;
186
            }
187
        }
188
189 69
        $time = 'T';
190 69
        foreach (['H' => 'h', 'M' => 'i'] as $key => $value) {
191 69
            if (0 !== $this->$value) {
192 41
                $time .= '%'.$value.$key;
193
            }
194
        }
195
196 69
        if (0.0 !== $this->f) {
0 ignored issues
show
introduced by ignace namagana butera
The condition 0.0 !== $this->f is always true.
Loading history...
197 21
            $second = $this->s + $this->f;
198 21
            if (0 > $this->s) {
199 3
                $second = $this->s - $this->f;
200
            }
201
            
202 21
            $second = rtrim(sprintf('%f', $second), '0');
203
204 21
            return $this->format($date.$time).$second.'S';
205
        }
206
207 48
        if (0 !== $this->s) {
208 9
            $time .= '%sS';
209
210 9
            return $this->format($date.$time);
211
        }
212
        
213 39
        if ('T' !== $time) {
214 12
            return $this->format($date.$time);
215
        }
216
217 27
        if ('P' !== $date) {
218 24
            return $this->format($date);
219
        }
220
221 3
        return 'PT0S';
222
    }
223
224
    /**
225
     * DEPRECATION WARNING! This method will be removed in the next major point release.
226
     *
227
     * @deprecated deprecated since version 4.6
228
     * @see ::adjustedTo
229
     *
230
     * Returns a new instance with recalculate time and date segments to remove carry over points.
231
     *
232
     * This method MUST retain the state of the current instance, and return
233
     * an instance that contains the time and date segments recalculate to remove
234
     * carry over points.
235
     *
236
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
237
     */
238 27
    public function withoutCarryOver($reference_date): self
239
    {
240 27
        return $this->adjustedTo($reference_date);
241
    }
242
243
    /**
244
     * Returns a new instance with recalculate properties according to a given datepoint.
245
     *
246
     * This method MUST retain the state of the current instance, and return
247
     * an instance that contains the time and date segments recalculate to remove
248
     * carry over points.
249
     *
250
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
251
     */
252 27
    public function adjustedTo($reference_date): self
253
    {
254 27
        if (!$reference_date instanceof DateTimeImmutable) {
255 24
            $reference_date = Datepoint::create($reference_date);
256
        }
257
258 27
        $duration = self::create($reference_date->diff($reference_date->add($this)));
259 27
        if ($duration == $this) {
260 3
            return $this;
261
        }
262
263 24
        return $duration;
264
    }
265
}
266