Merge branch 'develop' of github.com:EllisLab/CodeIgniter into develop-dh-date-range
diff --git a/system/helpers/date_helper.php b/system/helpers/date_helper.php
index f1ba364..e982021 100644
--- a/system/helpers/date_helper.php
+++ b/system/helpers/date_helper.php
@@ -687,5 +687,136 @@
 	}
 }
 
+// ------------------------------------------------------------------------
+
+/**
+ * Date range
+ *
+ * Returns a list of dates within a specified period.
+ *
+ * @param	int	unix_start	UNIX timestamp of period start date
+ * @param	int	unix_end|days	UNIX timestamp of period end date
+ *					or interval in days.
+ * @param	mixed	is_unix		Specifies wether the second @param
+ *					is a UNIX timestamp or day interval
+ *					 - TRUE or 'unix' for a timestamp
+ *					 - FALSE or 'days' for an interval
+ * @param	string  date_format	Output date format, same as in date()
+ * @return	array
+ */
+if ( ! function_exists('date_range'))
+{
+	function date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d')
+	{
+		if ($unix_start == '' OR $mixed == '' OR $format == '')
+		{
+			return FALSE;
+		}
+
+		$is_unix = ! ( ! $is_unix OR $is_unix === 'days');
+
+		// Validate input and try strtotime() on invalid timestamps/intervals, just in case
+		if ( ( ! preg_match('/^[0-9]+$/', $unix_start) && ($unix_start = @strtotime($unix_time)) === FALSE)
+			OR ( ! preg_match('/^[0-9]+$/', $mixed) && ($is_unix === FALSE OR ($mixed = @strtotime($mixed)) === FALSE))
+			OR ($is_unix === TRUE && $mixed < $unix_start))
+		{
+			return FALSE;
+		}
+
+		if ($is_unix && ($unix_start == $mixed OR date($format, $unix_start) === date($format, $mixed)))
+		{
+			return array($start_date);
+		}
+
+		$range = array();
+
+		/* NOTE: Even though the DateTime object has many useful features, it appears that
+		 *	 it doesn't always handle properly timezones, when timestamps are passed
+		 *	 directly to its constructor. Neither of the following gave proper results:
+		 *
+		 *		new DateTime('<timestamp>')
+		 *		new DateTime('<timestamp>', '<timezone>')
+		 *
+		 *	 --- available in PHP 5.3:
+		 *
+		 *		DateTime::createFromFormat('<format>', '<timestamp>')
+		 *		DateTime::createFromFormat('<format>', '<timestamp>', '<timezone')
+		 *
+		 *	 ... so we'll have to set the timestamp after the object is instantiated.
+		 *	 Furthermore, in PHP 5.3 we can use DateTime::setTimestamp() to do that and
+		 *	 given that we have UNIX timestamps - we should use it.
+		*/
+		$from = new DateTime();
+
+		if (is_php('5.3'))
+		{
+			$from->setTimestamp($unix_start);
+			if ($is_unix)
+			{
+				$arg = new DateTime();
+				$arg->setTimestamp($mixed);
+			}
+			else
+			{
+				$arg = (int) $mixed;
+			}
+
+			$period = new DatePeriod($from, new DateInterval('P1D'), $arg);
+			foreach ($period as $date)
+			{
+				$range[] = $date->format($format);
+			}
+
+			/* If a period end date was passed to the DatePeriod constructor, it might not
+			 * be in our results. Not sure if this is a bug or it's just possible because
+			 * the end date might actually be less than 24 hours away from the previously
+			 * generated DateTime object, but either way - we have to append it manually.
+			 */
+			if ( ! is_int($arg) && $range[count($range) - 1] !== $arg->format($format))
+			{
+				$range[] = $arg->format($format);
+			}
+
+			return $range;
+		}
+
+		$from->setDate(date('Y', $unix_start), date('n', $unix_start), date('j', $unix_start));
+		$from->setTime(date('G', $unix_start), date('i', $unix_start), date('s', $unix_start));
+		if ($is_unix)
+		{
+			$arg = new DateTime();
+			$arg->setDate(date('Y', $mixed), date('n', $mixed), date('j', $mixed));
+			$arg->setTime(date('G', $mixed), date('i', $mixed), date('s', $mixed));
+		}
+		else
+		{
+			$arg = (int) $mixed;
+		}
+		$range[] = $from->format($format);
+
+		if (is_int($arg)) // Day intervals
+		{
+			do
+			{
+				$from->modify('+1 day');
+				$range[] = $from->format($format);
+			}
+			while (--$arg > 0);
+		}
+		else // end date UNIX timestamp
+		{
+			for ($from->modify('+1 day'), $end_check = $arg->format('Ymd'); $from->format('Ymd') < $end_check; $from->modify('+1 day'))
+			{
+				$range[] = $from->format($format);
+			}
+
+			// Our loop only appended dates prior to our end date
+			$range[] = $arg->format($format);
+		}
+
+		return $range;
+	}
+}
+
 /* End of file date_helper.php */
 /* Location: ./system/helpers/date_helper.php */
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 37c38f1..72b3b54 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -50,6 +50,7 @@
    -  form_dropdown() will now also take an array for unity with other form helpers.
    -  set_realpath() can now also handle file paths as opposed to just directories.
    -  do_hash() now uses PHP's native hash() function, supporting more algorithms.
+   -  Added date_range() to the :doc:`Date Helper <helpers/html_helper>`.
 
 -  Database
 
diff --git a/user_guide_src/source/helpers/date_helper.rst b/user_guide_src/source/helpers/date_helper.rst
index b21d147..bf76707 100644
--- a/user_guide_src/source/helpers/date_helper.rst
+++ b/user_guide_src/source/helpers/date_helper.rst
@@ -299,6 +299,30 @@
 
 If the second parameter is empty, the current year will be used.
 
+date_range()
+============
+
+Returns a list of dates within a specified period.
+
+.. php:method:: date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d')
+
+	:param integer	$unix_start: UNIX timestamp of the range start date
+	:param integer	$mixed: UNIX timestamp of the range end date or interval in days
+	:param boolean	$is_unix: set to FALSE if $mixed is not a timestamp
+	:param string	$format: output date format, same as in date()
+	:returns: array
+
+Example
+
+::
+
+	$range = date_range('2012-01-01', '2012-01-15');
+	echo "First 15 days of 2012:";
+	foreach ($range as $date)
+	{
+		echo $date."\n";
+	}
+
 timezones()
 ===========