Completed
Push — master ( 9d10f7...f3bdc0 )
by ignace nyamagana
04:59
created

Duration   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 68
dl 0
loc 184
ccs 72
cts 72
cp 1
rs 10
c 0
b 0
f 0
wmc 27

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
A createFromDateString() 0 9 2
B toString() 0 37 9
B create() 0 42 9
A withoutCarryOver() 0 12 3
A __toString() 0 3 1
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 preg_replace;
25
use function property_exists;
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
        (
45
            ((?<signH>\+|-)?(?<hour>\d+):)?
46
            ((?<signM>\+|-)?(?<minute>\d+)):
47
        )?
48
        ((?<signS>\+|-)?(?<second>\d+)(\.(?<fraction>\d{1,6}))?)
49
    $@x';
50
51
    /**
52
     * Returns a continuous portion of time between two datepoints expressed as a DateInterval object.
53
     *
54
     * The duration can be
55
     * <ul>
56
     * <li>an Period object</li>
57
     * <li>a DateInterval object</li>
58
     * <li>an integer interpreted as the duration expressed in seconds.</li>
59
     * <li>a string parsable by DateInterval::createFromDateString</li>
60
     * </ul>
61
     *
62
     * @param mixed $duration a continuous portion of time
63
     */
64 255
    public static function create($duration): self
65
    {
66 255
        if ($duration instanceof Period) {
67 12
            $duration = $duration->getDateInterval();
68
        }
69
70 255
        if ($duration instanceof DateInterval) {
71 30
            $new = new self('PT0S');
72 30
            foreach ($duration as $name => $value) {
73 30
                if (property_exists($new, $name)) {
74 30
                    $new->$name = $value;
75
                }
76
            }
77
78 30
            return $new;
79
        }
80
81 225
        if (false !== ($second = filter_var($duration, FILTER_VALIDATE_INT))) {
82 60
            return new self('PT'.$second.'S');
83
        }
84
85 165
        if (!is_string($duration) && !method_exists($duration, '__toString')) {
86 9
            throw new TypeError(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($duration)));
87
        }
88
89 156
        $duration = (string) $duration;
90 156
        if (1 === preg_match(self::REGEXP_CHRONO_FORMAT, $duration, $matches)) {
91 15
            $matches['signH'] = $matches['signH'] ?? '+';
92 15
            $matches['hour'] = $matches['hour'] ?? '0';
93 15
            $matches['signM'] = $matches['signM'] ?? '+';
94 15
            $matches['minute'] = $matches['minute'] ?? '0';
95 15
            $matches['signS'] = $matches['signS'] ?? '+';
96 15
            $matches['fraction'] = str_pad($matches['fraction'] ?? '0000000', 6, '0');
97
98 15
            return self::createFromDateString(
99 15
                $matches['signH'].$matches['hour'].' hours '.
100 15
                $matches['signM'].$matches['minute'].' minutes '.
101 15
                $matches['signS'].$matches['second'].' seconds '.$matches['fraction'].' microseconds'
102
            );
103
        }
104
105 141
        return self::createFromDateString($duration);
106
    }
107
108
    /**
109
     * @inheritdoc
110
     *
111
     * @param mixed $duration a date with relative parts
112
     */
113 159
    public static function createFromDateString($duration): self
114
    {
115 159
        $duration = parent::createFromDateString($duration);
116 159
        $new = new self('PT0S');
117 159
        foreach ($duration as $name => $value) {
118 159
            $new->$name = $value;
119
        }
120
121 159
        return $new;
122
    }
123
124
    /**
125
     * New instance.
126
     *
127
     * Returns a new instance from an Interval specification
128
     */
129 255
    public function __construct(string $interval_spec)
130
    {
131 255
        if (1 === preg_match(self::REGEXP_MICROSECONDS_INTERVAL_SPEC, $interval_spec, $matches)) {
132 3
            parent::__construct($matches['interval'].'S');
133 3
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
134 3
            return;
135
        }
136
137 255
        if (1 === preg_match(self::REGEXP_MICROSECONDS_DATE_SPEC, $interval_spec, $matches)) {
138 3
            parent::__construct($matches['interval']);
139 3
            $this->f = (float) str_pad($matches['fraction'], 6, '0') / 1e6;
140 3
            return;
141
        }
142
143 252
        parent::__construct($interval_spec);
144 246
    }
145
146
    /**
147
     * Returns the ISO8601 interval string representation.
148
     *
149
     * Microseconds fractions are included
150
     */
151 57
    public function __toString(): string
152
    {
153 57
        return $this->toString($this);
154
    }
155
156
    /**
157
     * Generates the ISO8601 interval string representation.
158
     */
159 57
    private function toString(DateInterval $interval): string
160
    {
161 57
        $date = 'P';
162 57
        foreach (['Y' => 'y', 'M' => 'm', 'D' => 'd'] as $key => $value) {
163 57
            if (0 !== $interval->$value) {
164 44
                $date .= '%'.$value.$key;
165
            }
166
        }
167
168 57
        $time = 'T';
169 57
        foreach (['H' => 'h', 'M' => 'i'] as $key => $value) {
170 57
            if (0 !== $interval->$value) {
171 46
                $time .= '%'.$value.$key;
172
            }
173
        }
174
175 57
        if (0.0 !== $interval->f) {
0 ignored issues
show
introduced by
The condition 0.0 !== $interval->f is always true.
Loading history...
176 15
            $time .= '%s.%FS';
177
178 15
            return (string) preg_replace('/0+S$/', 'S', $interval->format($date.$time));
179
        }
180
181 42
        if (0 !== $interval->s) {
182 15
            $time .= '%sS';
183
184 15
            return $interval->format($date.$time);
185
        }
186
        
187 27
        if ('T' !== $time) {
188 9
            return $interval->format($date.$time);
189
        }
190
191 21
        if ('P' !== $date) {
192 18
            return $interval->format($date);
193
        }
194
195 3
        return 'PT0S';
196
    }
197
198
    /**
199
     * Returns a new instance with recalculate time and date segments to remove carry over points.
200
     *
201
     * This method MUST retain the state of the current instance, and return
202
     * an instance that contains the time and date segments recalculate to remove
203
     * carry over points.
204
     *
205
     * @param mixed $datepoint a Reference datepoint
206
     *                         by default will use the epoch time
207
     *                         accepts the same input as {@see Duration::create}
208
     */
209 12
    public function withoutCarryOver($datepoint = 0): self
210
    {
211 12
        if (!$datepoint instanceof DateTimeImmutable) {
212 12
            $datepoint = Datepoint::create($datepoint);
213
        }
214
215 12
        $duration = $datepoint->diff($datepoint->add($this));
216 12
        if ($this->toString($duration) === $this->toString($this)) {
217 3
            return $this;
218
        }
219
220 9
        return self::create($duration);
221
    }
222
}
223