Completed
Pull Request — master (#75)
by ignace nyamagana
02:02
created

Duration::fromChrono()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 5
nop 1
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
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 function array_pop;
18
use function explode;
19
use function filter_var;
20
use function preg_grep;
21
use function preg_match;
22
use function property_exists;
23
use function rtrim;
24
use function sprintf;
25
use function str_pad;
26
use const FILTER_VALIDATE_INT;
27
28
/**
29
 * League Period Duration.
30
 *
31
 * @package League.period
32
 * @author  Ignace Nyamagana Butera <[email protected]>
33
 * @since   4.2.0
34
 */
35
final class Duration extends DateInterval
36
{
37
    private const REGEXP_MICROSECONDS_INTERVAL_SPEC = '@^(?<interval>.*)(\.|,)(?<fraction>\d{1,6})S$@';
38
39
    private const REGEXP_MICROSECONDS_DATE_SPEC = '@^(?<interval>.*)(\.)(?<fraction>\d{1,6})$@';
40
41
    private const REGEXP_CHRONO_SECOND = '@^\d+(\.\d+)?$@';
42
43
    private const REGEXP_CHRONO_UNIT = '@^\d+$@';
44
45
    /**
46
     * Returns a continuous portion of time between two datepoints expressed as a DateInterval object.
47
     *
48
     * The duration can be
49
     * <ul>
50
     * <li>an Period object</li>
51
     * <li>a DateInterval object</li>
52
     * <li>an integer interpreted as the duration expressed in seconds.</li>
53
     * <li>a string parsable by DateInterval::createFromDateString</li>
54
     * </ul>
55
     *
56
     * @param mixed $duration a continuous portion of time
57
     */
58 225
    public static function create($duration): self
59
    {
60 225
        if ($duration instanceof Period) {
61 12
            $duration = $duration->getDateInterval();
62
        }
63
64 225
        if ($duration instanceof DateInterval) {
65 21
            $new = new self('PT0S');
66 21
            foreach ($duration as $name => $value) {
67 21
                if (property_exists($new, $name)) {
68 21
                    $new->$name = $value;
69
                }
70
            }
71
72 21
            return $new;
73
        }
74
75 204
        if (false !== ($second = filter_var($duration, FILTER_VALIDATE_INT))) {
76 57
            return new self('PT'.$second.'S');
77
        }
78
79 147
        return self::createFromDateString($duration);
80
    }
81
82
    /**
83
     * @inheritdoc
84
     *
85
     * @param mixed $duration a date with relative parts
86
     */
87 150
    public static function createFromDateString($duration): self
88
    {
89 150
        $duration = parent::createFromDateString($duration);
90 141
        $new = new self('PT0S');
91 141
        foreach ($duration as $name => $value) {
92 141
            $new->$name = $value;
93
        }
94
95 141
        return $new;
96
    }
97
98
    /**
99
     * Sets up a Duration from the string representation of a chronometer.
100
     *
101
     * The chronometer string is a representation of time
102
     * without any date part following the below format
103
     * HH:MM:SS.f
104
     *
105
     * The chronometer unit are always positive or equal to 0
106
     * except for the second unit which accept a fraction part.
107
     *
108
     * @throws Exception If the chrono string can not be parsed
109
     */
110 24
    public static function fromChrono(string $chrono): self
111
    {
112 24
        $parts = explode(':', $chrono, 3);
113 24
        $second = array_pop($parts);
114 24
        if (null === $second || 1 !== preg_match(self::REGEXP_CHRONO_SECOND, $second)) {
115 6
            throw new Exception(sprintf('%s: Unknown or bad chrono string format (%s)', __METHOD__, $chrono));
116
        }
117
118 18
        if ([] === $parts) {
119 6
            return new self('PT'.$second.'S');
120
        }
121
122 12
        if ($parts !== preg_grep(self::REGEXP_CHRONO_UNIT, $parts)) {
123 3
            throw new Exception(sprintf('%s: Unknown or bad chrono string format (%s)', __METHOD__, $chrono));
124
        }
125
126 9
        if (isset($parts[1])) {
127 6
            return new self('PT'.$parts[0].'H'.$parts[1].'M'.$second.'S');
128
        }
129
130 3
        return new self('PT'.$parts[0].'M'.$second.'S');
131
    }
132
133
    /**
134
     * New instance.
135
     *
136
     * Returns a new instance from an Interval specification
137
     */
138 240
    public function __construct(string $interval_spec)
139
    {
140 240
        if (1 === preg_match(self::REGEXP_MICROSECONDS_INTERVAL_SPEC, $interval_spec, $matches)) {
141 9
            parent::__construct($matches['interval'].'S');
142 9
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
143 9
            return;
144
        }
145
146 234
        if (1 === preg_match(self::REGEXP_MICROSECONDS_DATE_SPEC, $interval_spec, $matches)) {
147 3
            parent::__construct($matches['interval']);
148 3
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
149 3
            return;
150
        }
151
152 231
        parent::__construct($interval_spec);
153 225
    }
154
155
    /**
156
     * Returns the ISO8601 interval string representation.
157
     *
158
     * Microseconds fractions are included
159
     */
160 42
    public function __toString(): string
161
    {
162 42
        $date = 'P';
163 42
        foreach (['Y' => $this->y, 'M' => $this->m, 'D' => $this->d] as $key => $value) {
164 42
            if (0 !== $value) {
165 31
                $date .= $value.$key;
166
            }
167
        }
168
169 42
        $time = 'T';
170 42
        foreach (['H' => $this->h, 'M' => $this->i] as $key => $value) {
171 42
            if (0 !== $value) {
172 33
                $time .= $value.$key;
173
            }
174
        }
175
176 42
        if (0.0 !== $this->f) {
0 ignored issues
show
introduced by
The condition 0.0 !== $this->f is always true.
Loading history...
177 12
            $time .= rtrim(sprintf('%f', $this->s + $this->f), '0').'S';
178
179 12
            return $date.$time;
180
        }
181
182 30
        if (0 !== $this->s) {
183 15
            $time .= $this->s.'S';
184
185 15
            return $date.$time;
186
        }
187
188 15
        if ('T' !== $time) {
189 3
            return $date.$time;
190
        }
191
192 12
        if ('P' !== $date) {
193 9
            return $date;
194
        }
195
196 3
        return 'PT0S';
197
    }
198
}
199