Completed
Push — master ( 0c6d34...25f2b1 )
by ignace nyamagana
03:38
created

Duration::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 3
rs 9.9666
c 0
b 0
f 0
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})S$@';
40
41
    private const REGEXP_MICROSECONDS_DATE_SPEC = '@^(?<interval>.*)(\.)(?<fraction>\d{1,6})$@';
42
43
    private const REGEXP_CHRONO_FORMAT = '@^
44
        (?<sign>\+|-)?
45
        (((?<hour>\d+):)?(?<minute>\d+):)?
46
        ((?<second>\d+)(\.(?<fraction>\d{1,6}))?)
47
    $@x';
48
49
    /**
50
     * New instance.
51
     *
52
     * Returns a new instance from an Interval specification
53
     */
54 270
    public function __construct(string $interval_spec)
55
    {
56 270
        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 270
        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 267
        parent::__construct($interval_spec);
69 261
    }
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 273
    public static function create($duration): self
87
    {
88 273
        if ($duration instanceof Period) {
89 15
            $duration = $duration->getDateInterval();
90
        }
91
92 273
        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 225
        if (false !== ($second = filter_var($duration, FILTER_VALIDATE_INT))) {
104 57
            return new self('PT'.$second.'S');
105
        }
106
107 168
        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 159
        $duration = (string) $duration;
112 159
        if (1 !== preg_match(self::REGEXP_CHRONO_FORMAT, $duration, $matches)) {
113 144
            return self::createFromDateString($duration);
114
        }
115
116 15
        $matches['fraction'] = str_pad($matches['fraction'] ?? '0000000', 6, '0');
117 15
        $instance = self::createFromDateString(
118 15
            $matches['hour'].' hours '.
119 15
            $matches['minute'].' minutes '.
120 15
            $matches['second'].' seconds '.$matches['fraction'].' microseconds'
121
        );
122 15
        if ('-' === $matches['sign']) {
123 6
            $instance->invert = 1;
124
        }
125
126 15
        return $instance;
127
    }
128
129
    /**
130
     * @inheritdoc
131
     *
132
     * @param mixed $duration a date with relative parts
133
     */
134 162
    public static function createFromDateString($duration): self
135
    {
136 162
        $duration = parent::createFromDateString($duration);
137 162
        $new = new self('PT0S');
138 162
        foreach ($duration as $name => $value) {
139 162
            $new->$name = $value;
140
        }
141
142 162
        return $new;
143
    }
144
145
    /**
146
     * DEPRECATION WARNING! This method will be removed in the next major point release.
147
     *
148
     * @deprecated deprecated since version 4.5
149
     * @see ::format
150
     *
151
     * Returns the ISO8601 interval string representation.
152
     *
153
     * Microseconds fractions are included
154
     */
155 69
    public function __toString(): string
156
    {
157 69
        $date = 'P';
158 69
        foreach (['Y' => 'y', 'M' => 'm', 'D' => 'd'] as $key => $value) {
159 69
            if (0 !== $this->$value) {
160 39
                $date .= '%'.$value.$key;
161
            }
162
        }
163
164 69
        $time = 'T';
165 69
        foreach (['H' => 'h', 'M' => 'i'] as $key => $value) {
166 69
            if (0 !== $this->$value) {
167 41
                $time .= '%'.$value.$key;
168
            }
169
        }
170
171 69
        if (0.0 !== $this->f) {
0 ignored issues
show
introduced by
The condition 0.0 !== $this->f is always true.
Loading history...
172 21
            $second = $this->s + $this->f;
173 21
            if (0 > $this->s) {
174 3
                $second = $this->s - $this->f;
175
            }
176
            
177 21
            $second = rtrim(sprintf('%f', $second), '0');
178
179 21
            return $this->format($date.$time).$second.'S';
180
        }
181
182 48
        if (0 !== $this->s) {
183 9
            $time .= '%sS';
184
185 9
            return $this->format($date.$time);
186
        }
187
        
188 39
        if ('T' !== $time) {
189 12
            return $this->format($date.$time);
190
        }
191
192 27
        if ('P' !== $date) {
193 24
            return $this->format($date);
194
        }
195
196 3
        return 'PT0S';
197
    }
198
199
    /**
200
     * DEPRECATION WARNING! This method will be removed in the next major point release.
201
     *
202
     * @deprecated deprecated since version 4.6
203
     * @see ::adjustedTo
204
     *
205
     * Returns a new instance with recalculate time and date segments to remove carry over points.
206
     *
207
     * This method MUST retain the state of the current instance, and return
208
     * an instance that contains the time and date segments recalculate to remove
209
     * carry over points.
210
     *
211
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
212
     */
213 27
    public function withoutCarryOver($reference_date): self
214
    {
215 27
        return $this->adjustedTo($reference_date);
216
    }
217
218
    /**
219
     * Returns a new instance with recalculate properties according to a given datepoint.
220
     *
221
     * This method MUST retain the state of the current instance, and return
222
     * an instance that contains the time and date segments recalculate to remove
223
     * carry over points.
224
     *
225
     * @param mixed $reference_date a reference datepoint {@see \League\Period\Datepoint::create}
226
     */
227 27
    public function adjustedTo($reference_date): self
228
    {
229 27
        if (!$reference_date instanceof DateTimeImmutable) {
230 24
            $reference_date = Datepoint::create($reference_date);
231
        }
232
233 27
        $duration = self::create($reference_date->diff($reference_date->add($this)));
234 27
        if ($duration == $this) {
235 3
            return $this;
236
        }
237
238 24
        return $duration;
239
    }
240
}
241