DateTimeSince::calc()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.2
cc 4
eloc 9
nc 6
nop 3
crap 4
1
<?php
2
/**
3
 * Part of the Joomla Framework DateTime Package
4
 *
5
 * @copyright  Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
6
 * @license    GNU Lesser General Public License version 2.1 or later; see LICENSE
7
 */
8
9
namespace Joomla\DateTime\Since;
10
11
use Joomla\DateTime\DateInterval;
12
use Joomla\DateTime\DateTime;
13
use Joomla\DateTime\Translator\AbstractTranslator;
14
15
/**
16
 * Default implementation of SinceInterface.
17
 *
18
 * @since  2.0.0
19
 */
20
final class DateTimeSince implements SinceInterface
21
{
22
	/**
23
	 * Translator object
24
	 *
25
	 * @var    AbstractTranslator
26
	 * @since  2.0.0
27
	 */
28
	private $translator;
29
30
	/**
31
	 * Returns the difference in a human readable format.
32
	 *
33
	 * @param   DateTime  $base         The base date.
34
	 * @param   DateTime  $datetime     The date to compare to. Default is null and this means that
35
	 *                                  the base date will be compared to the current time.
36
	 * @param   integer   $detailLevel  The level of detail to retrieve.
37
	 *
38
	 * @return  string
39
	 *
40
	 * @since   2.0.0
41
	 */
42 121
	public function since(DateTime $base, DateTime $datetime = null, $detailLevel = 1)
43
	{
44 121
		list($item, $diff) = $this->calc($base, $datetime, $detailLevel);
45
46 121
		return $this->translator->get($item, array('time' => $this->parseUnits($diff)));
47
	}
48
49
	/**
50
	 * Returns the almost difference in a human readable format.
51
	 *
52
	 * @param   DateTime  $base      The base date.
53
	 * @param   DateTime  $datetime  The date to compare to. Default is null and this means that
54
	 *                               the base date will be compared to the current time.
55
	 *
56
	 * @return  string
57
	 *
58
	 * @since   2.0.0
59
	 */
60 22
	public function almost(DateTime $base, DateTime $datetime = null)
61
	{
62 22
		list($item, $diff) = $this->calc($base, $datetime);
63
64 22
		return $this->translator->get($item, array('time' => $this->parseUnits($diff, true)));
65
	}
66
67
	/**
68
	 * Calculates the difference between dates.
69
	 *
70
	 * @param   DateTime  $base         The base date.
71
	 * @param   DateTime  $datetime     The date to compare to. Default is null and this means that
72
	 *                                  the base date will be compared to the current time.
73
	 *
74
	 * @param   integer   $detailLevel  The level of detail to retrieve.
75
	 *
76
	 * @return  array
77
	 *
78
	 * @since   2.0.0
79
	 */
80 143
	private function calc(DateTime $base, DateTime $datetime = null, $detailLevel = 1)
81
	{
82 143
		$this->translator = $base->getTranslator();
83
84 143
		$datetime = is_null($datetime) ? DateTime::now() : $datetime;
85 143
		$detailLevel = intval($detailLevel);
86
87 143
		$diff = $this->diffInUnits($base->diff($datetime, true), $detailLevel);
88
89 143
		$item = 'just_now';
90
91 143
		if (!$this->isNow($diff))
92 143
		{
93 133
			$item = $base->isAfter($datetime) ? 'in' : 'ago';
94 133
		}
95
96 143
		return array($item, $diff);
97
	}
98
99
	/**
100
	 * Calculates the difference between dates for all units of a time.
101
	 *
102
	 * @param   DateInterval  $interval     The difference between dates.
103
	 * @param   integer       $detailLevel  The level of detail to retrieve.
104
	 *
105
	 * @return  array
106
	 *
107
	 * @since   2.0.0
108
	 */
109 143
	private function diffInUnits(DateInterval $interval, $detailLevel)
110
	{
111 143
		$units = array('y' => 'year', 'm' => 'month', 'd' => 'day',
112 143
			'h' => 'hour', 'i' => 'minute', 's' => 'second'
113 143
		);
114
115 143
		$diff = array();
116
117 143
		foreach ($units as $format => $unit)
118
		{
119 143
			$amount = $interval->format('%' . $format);
120
121
			/** Adding support for weeks */
122 143
			if ($unit == 'day' && $amount >= 7)
123 143
			{
124 27
				$weeks = floor($amount / 7);
125 27
				$amount -= $weeks * 7;
126 27
				$diff[] = array(
127 27
					'amount' => $weeks,
128
					'unit' => 'week'
129 27
				);
130
131 27
				$detailLevel--;
132 27
			}
133
134
			// Save only non-zero units of time
135 143
			if ($amount > 0 && $detailLevel > 0)
136 143
			{
137 124
				$diff[] = array(
138 124
					'amount' => $amount,
139
					'unit' => $unit
140 124
				);
141
142 124
				$detailLevel--;
143 124
			}
144
145 143
			if ($detailLevel === 0)
146 143
			{
147 135
				break;
148
			}
149 143
		}
150
151 143
		return $diff;
152
	}
153
154
	/**
155
	 * Parses an array of units into a string.
156
	 *
157
	 * @param   array    $diff         An array of differences for every unit of a time.
158
	 * @param   boolean  $allowAlmost  Do you want to get an almost difference?
159
	 *
160
	 * @return  string
161
	 *
162
	 * @since   2.0.0
163
	 */
164 143
	private function parseUnits($diff, $allowAlmost = false)
165
	{
166 143
		if (empty($diff))
167 143
		{
168 2
			return '';
169
		}
170
171 141
		$isAlmost = false;
172 141
		$string = array();
173
174 141
		foreach ($diff as $time)
175
		{
176
			if ($allowAlmost)
177 141
			{
178 22
				$isAlmost = $this->isAlmost($time);
179 22
			}
180
181 141
			$string[] = $this->translator->choice($time['unit'], $time['amount']);
182 141
		}
183
184 141
		$parsed = $string[0];
185
186
		// Add 'and' separator
187 141
		if (count($string) > 1)
188 141
		{
189 30
			$theLastOne = $string[count($string) - 1];
190 30
			unset($string[count($string) - 1]);
191
192 30
			$and = $this->translator->get('and');
193 30
			$parsed = sprintf('%s %s %s', implode(', ', $string), $and, $theLastOne);
194 30
		}
195
196
		if ($isAlmost)
197 141
		{
198 21
			$parsed = $this->translator->get('almost', array('time' => $parsed));
199 21
		}
200
201 141
		return $parsed;
202
	}
203
204
	/**
205
	 * Checks if a time is more than 80% of the another unit of a time.
206
	 * (For example for 50 minutes it's FALSE, but for 55 minutes it's TRUE.)
207
	 *
208
	 * @param   array  $time  An array of a time, eg: array('unit' => 'hour', 'amount' => 4)
209
	 *
210
	 * @return  boolean
211
	 *
212
	 * @since   2.0.0
213
	 */
214 22
	private function isAlmost(&$time)
215
	{
216 22
		$units = array('second' => 60, 'minute' => 60, 'hour' => 24,
217 22
			'day' => 7, 'week' => 4, 'month' => 12, 'year' => null
218 22
		);
219
220
		// Finds the next unit of a time
221 22
		while (key($units) !== $time['unit'])
222
		{
223 22
			if (!next($units))
224 22
			{
225 1
				break;
226
			}
227 22
		}
228
229 22
		$current = current($units);
230 22
		next($units);
231
232
		// Checks if the current amount is 'almost' the next one
233 22
		if ($current && $current < $time['amount'] * 1.2)
234 22
		{
235
			$time = array(
236 21
				'amount' => 1,
237 21
				'unit' => key($units)
238 21
			);
239
240 21
			return true;
241
		}
242
243 1
		return false;
244
	}
245
246
	/**
247
	 * Checks if the difference can be treated as now.
248
	 *
249
	 * @param   array  $diff  An array of all units of a time
250
	 *
251
	 * @return  boolean
252
	 *
253
	 * @since   2.0.0
254
	 */
255 143
	private function isNow($diff)
256
	{
257
		// For all differences below one minute
258 143
		return empty($diff) || $diff[0]['unit'] == 'second';
259
	}
260
}
261