DateTimeRange::emptyRange()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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;
10
11
/**
12
 * Class representing a range of a time. You can create a range from start to end date,
13
 * but also from start date or end date for the given number of dates.
14
 *
15
 * @since  2.0.0
16
 */
17
final class DateTimeRange implements \IteratorAggregate
18
{
19
	/**
20
	 * DateTime object representing the start date of the iterator
21
	 *
22
	 * @var    DateTime
23
	 * @since  2.0.0
24
	 */
25
	protected $start;
26
27
	/**
28
	 * DateTime object representing the end date of the iterator
29
	 *
30
	 * @var    DateTime
31
	 * @since  2.0.0
32
	 */
33
	protected $end;
34
35
	/**
36
	 * Interval between dates
37
	 *
38
	 * @var    DateInterval
39
	 * @since  2.0.0
40
	 */
41
	protected $interval;
42
43
	/**
44
	 * Constructor.
45
	 *
46
	 * @param   DateTime      $start     The start date.
47
	 * @param   DateTime      $end       The end date.
48
	 * @param   DateInterval  $interval  The interval between adjacent dates.
49
	 *
50
	 * @since   2.0.0
51
	 * @throws  \InvalidArgumentException
52
	 */
53 32
	public function __construct(DateTime $start, DateTime $end, DateInterval $interval)
54
	{
55 32
		if ($start->isBefore($end) && $start->add($interval)->isAfter($end))
56 32
		{
57 1
			throw new \InvalidArgumentException("Interval is too big");
58
		}
59
60 31
		$this->start = $start;
61 31
		$this->end = $end;
62 31
		$this->interval = $interval;
63 31
	}
64
65
	/**
66
	 * Creates a DateTimeRange object from the start date for the given amount od dates.
67
	 *
68
	 * @param   DateTime      $start     The start date.
69
	 * @param   integer       $amount    The amount of dates included in a range.
70
	 * @param   DateInterval  $interval  The interval between adjacent dates.
71
	 *
72
	 * @return  DateTimeRange
73
	 *
74
	 * @since   2.0.0
75
	 */
76 3
	public static function from(DateTime $start, $amount, DateInterval $interval)
77
	{
78 3
		$end = self::buildDatetime($start, $amount, $interval, true);
79
80 2
		return new DateTimeRange($start, $end, $interval);
81
	}
82
83
	/**
84
	 * Creates a DateTimeRange object to the end date for the given amount od dates.
85
	 *
86
	 * @param   DateTime      $end       The end date.
87
	 * @param   integer       $amount    The amount of dates included in a range.
88
	 * @param   DateInterval  $interval  The interval between adjacent dates.
89
	 *
90
	 * @return  DateTimeRange
91
	 *
92
	 * @since   2.0.0
93
	 */
94 19
	public static function to(DateTime $end, $amount, DateInterval $interval)
95 17
	{
96 19
		$start = self::buildDatetime($end, $amount, $interval, false);
97
98 19
		return new DateTimeRange($start, $end, $interval);
99 17
	}
100
101
	/**
102
	 * Returns an empty range.
103
	 *
104
	 * @return  DateTimeRange
105
	 *
106
	 * @since   2.0.0
107
	 */
108 4
	public static function emptyRange()
109
	{
110 4
		return new DateTimeRange(DateTime::tomorrow(), DateTime::yesterday(), new DateInterval('P1D'));
111
	}
112
113
	/**
114
	 * Returns the start date.
115
	 *
116
	 * @return  DateTime
117
	 *
118
	 * @since   2.0.0
119
	 */
120 20
	public function start()
121
	{
122 20
		return $this->start;
123
	}
124
125
	/**
126
	 * Returns the end date.
127
	 *
128
	 * @return  DateTime
129
	 *
130
	 * @since   2.0.0
131
	 */
132 11
	public function end()
133
	{
134 11
		return $this->end;
135
	}
136
137
	/**
138
	 * Checks if a range is empty.
139
	 *
140
	 * @return  boolean
141
	 *
142
	 * @since   2.0.0
143
	 */
144 15
	public function isEmpty()
145
	{
146 15
		return $this->start->isAfter($this->end);
147
	}
148
149
	/**
150
	 * Checks if the given date is included in the range.
151
	 *
152
	 * @param   DateTime  $datetime  The date to compare to.
153
	 *
154
	 * @return  boolean
155
	 *
156
	 * @since   2.0.0
157
	 */
158 80
	public function includes(DateTime $datetime)
159
	{
160 80
		return !$datetime->isBefore($this->start) && !$datetime->isAfter($this->end);
161
	}
162
163
	/**
164
	 * Checks if ranges are equal.
165
	 *
166
	 * @param   DateTimeRange  $range  The range to compare to.
167
	 *
168
	 * @return  boolean
169
	 *
170
	 * @since   2.0.0
171
	 */
172 16
	public function equals(DateTimeRange $range)
173
	{
174 16
		return $this->start->equals($range->start) && $this->end->equals($range->end) && $this->interval->equals($range->interval);
175
	}
176
177
	/**
178
	 * Checks if ranges overlap with each other.
179
	 *
180
	 * @param   DateTimeRange  $range  The range to compare to.
181
	 *
182
	 * @return  boolean
183
	 *
184
	 * @since   2.0.0
185
	 */
186 50
	public function overlaps(DateTimeRange $range)
187
	{
188 50
		return $range->includes($this->start) || $range->includes($this->end) || $this->includesRange($range);
189
	}
190
191
	/**
192
	 * Checks if the given range is included in the current one.
193
	 *
194
	 * @param   DateTimeRange  $range  The range to compare to.
195
	 *
196
	 * @return  boolean
197
	 *
198
	 * @since   2.0.0
199
	 */
200 42
	public function includesRange(DateTimeRange $range)
201
	{
202 42
		return $this->includes($range->start) && $this->includes($range->end);
203
	}
204
205
	/**
206
	 * Returns a gap range between two ranges.
207
	 *
208
	 * @param   DateTimeRange  $range  The range to compare to.
209
	 *
210
	 * @return  DateTimeRange
211
	 *
212
	 * @since   2.0.0
213
	 */
214 18
	public function gap(DateTimeRange $range)
215
	{
216 18
		if ($this->overlaps($range))
217 18
		{
218 2
			return self::emptyRange();
219
		}
220
221 16
		if ($this->start->isBefore($range->start))
222 16
		{
223 10
			$lower = $this;
224 10
			$higher = $range;
225 10
		}
226
		else
227
		{
228 6
			$lower = $range;
229 6
			$higher = $this;
230
		}
231
232 16
		return new DateTimeRange($lower->end->add($this->interval), $higher->start->sub($this->interval), $this->interval);
233
	}
234
235
	/**
236
	 * Checks if ranges abuts with each other.
237
	 *
238
	 * @param   DateTimeRange  $range  The range to compare to.
239
	 *
240
	 * @return  boolean
241
	 *
242
	 * @since   2.0.0
243
	 */
244 24
	public function abuts(DateTimeRange $range)
245
	{
246 24
		return !$this->overlaps($range) && $this->gap($range)->isEmpty();
247
	}
248
249
	/**
250
	 * Returns an array of dates which are included in the current range.
251
	 *
252
	 * @return  DateTime[]
253
	 *
254
	 * @since   2.0.0
255
	 */
256 7
	public function toArray()
257
	{
258 7
		$range = array();
259
260 7
		foreach ($this as $datetime)
261
		{
262 7
			$range[] = $datetime;
263 7
		}
264
265 7
		return $range;
266
	}
267
268
	/**
269
	 * Returns an external iterator.
270
	 *
271
	 * @return  DateTimeIterator
272
	 *
273
	 * @since   2.0.0
274
	 */
275 7
	public function getIterator()
276
	{
277 7
		return new DateTimeIterator($this->start, $this->end, $this->interval);
278
	}
279
280
	/**
281
	 * Returns string representation of the range.
282
	 *
283
	 * @return  string
284
	 *
285
	 * @since   2.0.0
286
	 */
287 1
	public function __toString()
288
	{
289 1
		return sprintf('%s - %s', $this->start->format('Y-m-d H:i:s'), $this->end->format('Y-m-d H:i:s'));
290
	}
291
292
	/**
293
	 * Returns a range which is created by combination of given ranges. There can't
294
	 * be a gap between given ranges.
295
	 *
296
	 * @param   DateTimeRange[]  $ranges  An array of ranges to combine.
297
	 *
298
	 * @return  DateTimeRange
299
	 *
300
	 * @since   2.0.0
301
	 * @throws  \InvalidArgumentException
302
	 */
303 10
	public static function combination(array $ranges)
304
	{
305 10
		if (!self::isContiguous($ranges))
306 10
		{
307 8
			throw new \InvalidArgumentException('Unable to combine date ranges');
308
		}
309
310 2
		return new DateTimeRange($ranges[0]->start, $ranges[count($ranges) - 1]->end, $ranges[0]->interval);
311
	}
312
313
	/**
314
	 * Checks if ranges are contiguous.
315
	 *
316
	 * @param   DateTimeRange[]  $ranges  An array of ranges to combine.
317
	 *
318
	 * @return  boolean
319
	 *
320
	 * @since   2.0.0
321
	 */
322 15
	public static function isContiguous(array $ranges)
323
	{
324 15
		$ranges = self::sortArrayOfRanges($ranges);
325
326 14
		for ($i = 0; $i < count($ranges) - 1; $i++)
327
		{
328 14
			if (!$ranges[$i]->abuts($ranges[$i + 1]))
329 14
			{
330 10
				return false;
331
			}
332 4
		}
333
334 4
		return true;
335
	}
336
337
	/**
338
	 * Sorts an array of ranges.
339
	 *
340
	 * @param   DateTimeRange[]  $ranges  An array of ranges to sort.
341
	 *
342
	 * @return  DateTimeRange[]
343
	 *
344
	 * @since   2.0.0
345
	 */
346 15
	protected static function sortArrayOfRanges(array $ranges)
347
	{
348 15
		usort($ranges, array("self", "compare"));
349
350 14
		return array_values($ranges);
351
	}
352
353
	/**
354
	 * Compares two objects.
355
	 *
356
	 * @param   DateTimeRange  $a  Base object.
357
	 * @param   DateTimeRange  $b  Object to compare to.
358
	 *
359
	 * @return  integer
360
	 *
361
	 * @since   2.0.0
362
	 * @throws  \InvalidArgumentException
363
	 */
364 15
	private static function compare(DateTimeRange $a, DateTimeRange $b)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
365
	{
366 15
		if (!$a->interval->equals($b->interval))
367 15
		{
368 1
			throw new \InvalidArgumentException('Intervals of ranges are not equal.');
369
		}
370
371 14
		if ($a->equals($b))
372 14
		{
373 2
			return 0;
374
		}
375
376 12
		if ($a->start()->isAfter($b->start()))
377 12
		{
378 8
			return 1;
379
		}
380
381 4
		if ($a->start()->isBefore($b->start()) || $a->end()->isBefore($b->end()))
382 4
		{
383 3
			return -1;
384
		}
385
386 1
		return 1;
387
	}
388
389
	/**
390
	 * Builds the date.
391
	 *
392
	 * @param   DateTime      $base        The base date.
393
	 * @param   integer       $amount      The amount of dates included in a range.
394
	 * @param   DateInterval  $interval    The interval between adjacent dates.
395
	 * @param   boolean       $byAddition  Should build the final date using addition or subtraction?
396
	 *
397
	 * @return  DateTime
398
	 *
399
	 * @since   2.0.0
400
	 * @throws  \InvalidArgumentException
401
	 */
402 5
	private static function buildDatetime(DateTime $base, $amount, DateInterval $interval, $byAddition = true)
403
	{
404 5
		if (intval($amount) < 2)
405 5
		{
406 1
			throw new \InvalidArgumentException('Amount have to be greater than 2');
407
		}
408
409
		// Start from 2, because start date and end date also count
410 4
		for ($i = 2; $i <= $amount; $i++)
411
		{
412 4
			$base = $byAddition ? $base->add($interval) : $base->sub($interval);
413 4
		}
414
415 4
		return $base;
416
	}
417
}
418