Duration::formatPeriods()   C
last analyzed

Complexity

Conditions 7
Paths 24

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 26
rs 6.7272
cc 7
eloc 19
nc 24
nop 2
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
	const MILLISECOND = '0.001';
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
	 * 1y 2m 3d 4h 5m 6s.
28
	 */
29
	const FORMAT_SHORT = 0;
30
	/**
31
	 * 1 year 2 months 3 days 4 hours 5 minutes 6 seconds.
32
	 */
33
	const FORMAT_LONG = 1;
34
35
	private static $defaultFormats = [
36
		self::FORMAT_SHORT => [
37
			self::YEAR => 'y',
38
			self::MONTH => 'mo',
39
			self::WEEK => 'w',
40
			self::DAY => 'd',
41
			self::HOUR => 'h',
42
			self::MINUTE => 'm',
43
			self::SECOND => 's',
44
			self::MILLISECOND => 'ms',
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
			self::MILLISECOND => ' millisecond(s)',
55
		],
56
	];
57
	/**
58
	 * @var int|string
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
		$this->formats = self::$defaultFormats;
75
		$this->setFormat($format);
76
	}
77
78
	/**
79
	 * Create a new Duration.
80
	 *
81
	 * @param int $format
82
	 *
83
	 * @return \nochso\Omni\Format\Duration
84
	 */
85
	public static function create($format = self::FORMAT_SHORT) {
86
		return new self($format);
87
	}
88
89
	/**
90
	 * addFormat to the existing defaults and set it as the current format.
91
	 *
92
	 * e.g.
93
	 *
94
	 * ```php
95
	 * $format = Duration::FORMAT_LONG => [
96
	 *     Duration::YEAR => ' year(s)',
97
	 *     Duration::MONTH => ' month(s)',
98
	 *     Duration::WEEK => ' week(s)',
99
	 *     Duration::DAY => ' day(s)',
100
	 *     Duration::HOUR => ' hour(s)',
101
	 *     Duration::MINUTE => ' minute(s)',
102
	 *     Duration::SECOND => ' second(s)',
103
	 * ];
104
	 * $df->addFormat('my custom period format', $format);
105
	 * ```
106
	 *
107
	 * @param string   $name
108
	 * @param string[] $periodFormats
109
	 *
110
	 * @return $this
111
	 */
112
	public function addFormat($name, array $periodFormats) {
113
		$this->formats[$name] = $periodFormats;
114
		$this->setFormat($name);
115
		return $this;
116
	}
117
118
	/**
119
	 * setFormat to use by its custom name or one of the default Duration constants.
120
	 *
121
	 * @param string $name One of the `Duration::FORMAT_*` constants or a name of a format added via `addFormat()`
122
	 *
123
	 * @return $this
124
	 */
125
	public function setFormat($name) {
126
		if (!isset($this->formats[$name])) {
127
			throw new \InvalidArgumentException(sprintf("Duration format named '%s' does not exist.", $name));
128
		}
129
		$this->format = $name;
130
		return $this;
131
	}
132
133
	/**
134
	 * limitPeriods limits the amount of significant periods (years, months, etc.) to keep.
135
	 *
136
	 * Significant periods are periods with non-zero values.
137
	 *
138
	 * @param int $limit 0 for keeping all significant periods or any positive integer.
139
	 *
140
	 * @return $this
141
	 */
142
	public function limitPeriods($limit) {
143
		$this->limit = Numeric::ensureInteger($limit);
144
		return $this;
145
	}
146
147
	/**
148
	 * Format an amount of seconds or a `DateInterval` object.
149
	 *
150
	 * @param int|\DateInterval $duration
151
	 *
152
	 * @return string A formatted duration for human consumption.
153
	 */
154
	public function format($duration) {
155
		return $this->formatPeriods($duration, $this->formats[$this->format]);
156
	}
157
158
	/**
159
	 * @param int|\DateInterval $duration
160
	 * @param array             $steps
161
	 *
162
	 * @return string
163
	 */
164
	private function formatPeriods($duration, $steps) {
165
		$seconds = $this->ensureSeconds($duration);
166
		$parts = [];
167
		foreach ($steps as $minValue => $suffix) {
168
			if ($seconds >= $minValue) {
169
				$stepValue = $seconds / $minValue;
170
				if ($minValue < 1) {
171
					$stepValue = round($stepValue);
172
				} else {
173
					$stepValue = floor($stepValue);
174
				}
175
				if ($stepValue > 0) {
176
					$suffix = Quantity::format($suffix, $stepValue);
177
					$parts[] = $stepValue . $suffix;
178
					$seconds -= $stepValue * $minValue;
179
				}
180
			}
181
		}
182
		if (count($parts) === 0) {
183
			$parts[] = $seconds . Quantity::format($steps[self::SECOND], $seconds);
184
		}
185
		if ($this->limit > 0) {
186
			$parts = array_slice($parts, 0, $this->limit);
187
		}
188
		return implode(' ', $parts);
189
	}
190
191
	/**
192
	 * @param int|\DateInterval $duration
193
	 *
194
	 * @return int
195
	 */
196
	private function ensureSeconds($duration) {
197
		if ($duration instanceof \DateInterval) {
198
			$d1 = new \DateTime('@0');
199
			return $d1->add($duration)->getTimestamp();
200
		}
201
		return Numeric::ensure($duration);
202
	}
203
}
204