Completed
Push — master ( 031221...6cba54 )
by Marcel
02:18
created

Duration::limitPeriods()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace nochso\Omni\Format;
3
4
use nochso\Omni\Numeric;
5
6
/**
7
 * Duration formats seconds or DateInterval objects as human readable strings.
8
 *
9
 * e.g.
10
 *
11
 * ```php
12
 * $df = Duration::create();
13
 * $df->format(119);                        // '1m 59s'
14
 * $df->format(new \DateInterval('P1Y5D')); // '1y 5d'
15
 * ```
16
 */
17
class Duration
18
{
19
    const SECOND = 1;
20
    const MINUTE = 60;
21
    const HOUR = self::MINUTE * 60;
22
    const DAY = self::HOUR * 24;
23
    const WEEK = self::DAY * 7;
24
    const MONTH = self::DAY * 30;
25
    const YEAR = self::DAY * 365;
26
27
    /**
28
     * 1y 2m 3d 4h 5m 6s.
29
     */
30
    const FORMAT_SHORT = 0;
31
    /**
32
     * 1 year 2 months 3 days 4 hours 5 minutes 6 seconds.
33
     */
34
    const FORMAT_LONG = 1;
35
36
    private static $defaultFormats = [
37
        self::FORMAT_SHORT => [
38
            self::YEAR => 'y',
39
            self::MONTH => 'mo',
40
            self::WEEK => 'w',
41
            self::DAY => 'd',
42
            self::HOUR => 'h',
43
            self::MINUTE => 'm',
44
            self::SECOND => 's',
45
        ],
46
        self::FORMAT_LONG => [
47
            self::YEAR => ' year(s)',
48
            self::MONTH => ' month(s)',
49
            self::WEEK => ' week(s)',
50
            self::DAY => ' day(s)',
51
            self::HOUR => ' hour(s)',
52
            self::MINUTE => ' minute(s)',
53
            self::SECOND => ' second(s)',
54
        ],
55
    ];
56
57
    /**
58
     * @var int
59
     */
60
    private $format = self::FORMAT_SHORT;
61
    /**
62
     * @var array
63
     */
64
    private $formats;
65
    /**
66
     * @var int
67
     */
68
    private $limit = 0;
69
70
    /**
71
     * @param int $format
72
     */
73
    public function __construct($format = self::FORMAT_SHORT)
74
    {
75
        $this->formats = self::$defaultFormats;
76
        $this->setFormat($format);
77
    }
78
79
    /**
80
     * Create a new Duration.
81
     *
82
     * @param int $format
83
     *
84
     * @return \nochso\Omni\Duration
85
     */
86
    public static function create($format = self::FORMAT_SHORT)
87
    {
88
        return new self($format);
89
    }
90
91
    /**
92
     * addFormat to the existing defaults and set it as the current format.
93
     *
94
     * e.g.
95
     *
96
     * ```php
97
     * $format = Duration::FORMAT_LONG => [
98
     *     Duration::YEAR => ' year(s)',
99
     *     Duration::MONTH => ' month(s)',
100
     *     Duration::WEEK => ' week(s)',
101
     *     Duration::DAY => ' day(s)',
102
     *     Duration::HOUR => ' hour(s)',
103
     *     Duration::MINUTE => ' minute(s)',
104
     *     Duration::SECOND => ' second(s)',
105
     * ];
106
     * $df->addFormat('my custom period format', $format);
107
     * ```
108
     *
109
     * @param string   $name
110
     * @param string[] $periodFormats
111
     *
112
     * @return $this
113
     */
114
    public function addFormat($name, array $periodFormats)
115
    {
116
        $this->formats[$name] = $periodFormats;
117
        $this->setFormat($name);
118
        return $this;
119
    }
120
121
    /**
122
     * setFormat to use by its custom name or one of the default Duration constants.
123
     *
124
     * @param string $name One of the `Duration::FORMAT_*` constants or a name of a format added via `addFormat()`
125
     *
126
     * @return $this
127
     */
128
    public function setFormat($name)
129
    {
130
        if (!isset($this->formats[$name])) {
131
            throw new \InvalidArgumentException(sprintf("Duration format named '%s' does not exist.", $name));
132
        }
133
        $this->format = $name;
0 ignored issues
show
Documentation Bug introduced by
The property $format was declared of type integer, but $name is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
134
        return $this;
135
    }
136
137
    /**
138
     * limitPeriods limits the amount of significant periods (years, months, etc.) to keep.
139
     *
140
     * Significant periods are periods with non-zero values.
141
     *
142
     * @param int $limit 0 for keeping all significant periods or any positive integer.
143
     *
144
     * @return $this
145
     */
146
    public function limitPeriods($limit)
147
    {
148
        $this->limit = Numeric::ensureInteger($limit);
149
        return $this;
150
    }
151
152
    /**
153
     * Format an amount of seconds or a `DateInterval` object.
154
     *
155
     * @param int|\DateInterval $duration
156
     *
157
     * @return string A formatted duration for human consumption.
158
     */
159
    public function format($duration)
160
    {
161
        return $this->formatPeriods($duration, $this->formats[$this->format]);
162
    }
163
164
    /**
165
     * @param int|\DateInterval $duration
166
     * @param array             $steps
167
     *
168
     * @return string
169
     */
170
    private function formatPeriods($duration, $steps)
171
    {
172
        $seconds = $this->ensureSeconds($duration);
173
        $parts = [];
174
        foreach ($steps as $minValue => $suffix) {
175
            if ($seconds >= $minValue) {
176
                $stepValue = floor($seconds / $minValue);
177
                if ($stepValue > 0) {
178
                    $suffix = Quantity::format($suffix, $stepValue);
179
                    $parts[] = $stepValue . $suffix;
180
                    $seconds -= $stepValue * $minValue;
181
                }
182
            }
183
        }
184
        if (count($parts) === 0) {
185
            $parts[] = $seconds . Quantity::format($steps[self::SECOND], $seconds);
186
        }
187
        if ($this->limit > 0) {
188
            $parts = array_slice($parts, 0, $this->limit);
189
        }
190
        return implode(' ', $parts);
191
    }
192
193
    /**
194
     * @param int|\DateInterval $duration
195
     *
196
     * @return int
197
     */
198
    private function ensureSeconds($duration)
199
    {
200
        if ($duration instanceof \DateInterval) {
201
            $d1 = new \DateTime('@0');
202
            return $d1->add($duration)->getTimestamp();
203
        }
204
        return Numeric::ensureInteger($duration);
205
    }
206
}
207