Duration   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 96.25%

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 217
ccs 77
cts 80
cp 0.9625
rs 9.84
c 0
b 0
f 0
wmc 32

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
C create() 0 50 12
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['fraction'] = str_pad($matches['fraction'] ?? '0000000', 6, '0');
122 15
        $expression = $matches['hour'].' hours '.
123 15
            $matches['minute'].' minutes '.
124 15
            $matches['second'].' seconds '.$matches['fraction'].' microseconds';
125
126 15
        $instance = self::createFromDateString($expression);
127 15
        if (false === $instance) {
0 ignored issues
show
introduced by Ignace Nyamagana Butera
The condition false === $instance is always false.
Loading history...
128
            throw new Exception(sprintf('Unknown or bad format (%s)', $expression));
129
        }
130
131 15
        if ('-' === $matches['sign']) {
132 6
            $instance->invert = 1;
133
        }
134
135 15
        return $instance;
136
    }
137
138
    /**
139
     * @inheritdoc
140
     *
141
     * @param mixed $duration a date with relative parts
142
     *
143
     * @return self|false
144
     */
145 174
    public static function createFromDateString($duration)
146
    {
147 174
        $duration = parent::createFromDateString($duration);
148 174
        if (false === $duration) {
149
            return $duration;
150
        }
151
152 174
        $new = new self('PT0S');
153 174
        foreach ($duration as $name => $value) {
154 174
            $new->$name = $value;
155
        }
156
157 174
        return $new;
158
    }
159
160
    /**
161
     * DEPRECATION WARNING! This method will be removed in the next major point release.
162
     *
163
     * @deprecated deprecated since version 4.5
164
     * @see ::format
165
     *
166
     * Returns the ISO8601 interval string representation.
167
     *
168
     * Microseconds fractions are included
169
     */
170 69
    public function __toString(): string
171
    {
172 69
        $date = 'P';
173 69
        foreach (['Y' => 'y', 'M' => 'm', 'D' => 'd'] as $key => $value) {
174 69
            if (0 !== $this->$value) {
175 39
                $date .= '%'.$value.$key;
176
            }
177
        }
178
179 69
        $time = 'T';
180 69
        foreach (['H' => 'h', 'M' => 'i'] as $key => $value) {
181 69
            if (0 !== $this->$value) {
182 41
                $time .= '%'.$value.$key;
183
            }
184
        }
185
186 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...
187 21
            $second = $this->s + $this->f;
188 21
            if (0 > $this->s) {
189 3
                $second = $this->s - $this->f;
190
            }
191
            
192 21
            $second = rtrim(sprintf('%f', $second), '0');
193
194 21
            return $this->format($date.$time).$second.'S';
195
        }
196
197 48
        if (0 !== $this->s) {
198 9
            $time .= '%sS';
199
200 9
            return $this->format($date.$time);
201
        }
202
        
203 39
        if ('T' !== $time) {
204 12
            return $this->format($date.$time);
205
        }
206
207 27
        if ('P' !== $date) {
208 24
            return $this->format($date);
209
        }
210
211 3
        return 'PT0S';
212
    }
213
214
    /**
215
     * DEPRECATION WARNING! This method will be removed in the next major point release.
216
     *
217
     * @deprecated deprecated since version 4.6
218
     * @see ::adjustedTo
219
     *
220
     * Returns a new instance with recalculate time and date segments to remove carry over points.
221
     *
222
     * This method MUST retain the state of the current instance, and return
223
     * an instance that contains the time and date segments recalculate to remove
224
     * carry over points.
225
     *
226
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
227
     */
228 27
    public function withoutCarryOver($reference_date): self
229
    {
230 27
        return $this->adjustedTo($reference_date);
231
    }
232
233
    /**
234
     * Returns a new instance with recalculate properties according to a given datepoint.
235
     *
236
     * This method MUST retain the state of the current instance, and return
237
     * an instance that contains the time and date segments recalculate to remove
238
     * carry over points.
239
     *
240
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
241
     */
242 27
    public function adjustedTo($reference_date): self
243
    {
244 27
        if (!$reference_date instanceof DateTimeImmutable) {
245 24
            $reference_date = Datepoint::create($reference_date);
246
        }
247
248 27
        $duration = self::create($reference_date->diff($reference_date->add($this)));
249 27
        if ($duration == $this) {
250 3
            return $this;
251
        }
252
253 24
        return $duration;
254
    }
255
}
256