Merge pull request #918 from narfbg/develop-dh-date-range

Added date_range() to the Date helper
diff --git a/system/helpers/date_helper.php b/system/helpers/date_helper.php
index a792f09..955d745 100644
--- a/system/helpers/date_helper.php
+++ b/system/helpers/date_helper.php
@@ -657,5 +657,136 @@
 	}
 }
 
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('date_range'))
+{
+	/**
+	 * 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 parameter
+	 *					is a UNIX timestamp or a 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
+	 */
+	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/tests/codeigniter/helpers/date_helper_test.php b/tests/codeigniter/helpers/date_helper_test.php
index 1b79b94..9feade7 100644
--- a/tests/codeigniter/helpers/date_helper_test.php
+++ b/tests/codeigniter/helpers/date_helper_test.php
@@ -290,6 +290,29 @@
 		$this->assertEquals(0, timezones('non_existant'));
 	}
 
+	// ------------------------------------------------------------------------
+
+	public function test_date_range()
+	{
+		$dates = array(
+			'29-01-2012', '30-01-2012', '31-01-2012',
+			'01-02-2012', '02-02-2012', '03-02-2012',
+			'04-02-2012', '05-02-2012', '06-02-2012',
+			'07-02-2012', '08-02-2012', '09-02-2012',
+			'10-02-2012', '11-02-2012', '12-02-2012',
+			'13-02-2012', '14-02-2012', '15-02-2012',
+			'16-02-2012', '17-02-2012', '18-02-2012',
+			'19-02-2012', '20-02-2012', '21-02-2012',
+			'22-02-2012', '23-02-2012', '24-02-2012',
+			'25-02-2012', '26-02-2012', '27-02-2012',
+			'28-02-2012', '29-02-2012', '01-03-2012'
+		);
+
+		$this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), mktime(12, 0, 0, 3, 1, 2012), TRUE, 'd-m-Y'));
+		array_pop($dates);
+		$this->assertEquals($dates, date_range(mktime(12, 0, 0, 1, 29, 2012), 31, FALSE, 'd-m-Y'));
+	}
+
 }
 
 /* End of file date_helper_test.php */
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 40ba218..486a676 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -60,7 +60,8 @@
 	 - ``now()`` now works with all timezone strings supported by PHP.
 	 - Added an optional third parameter to ``timespan()`` that constrains the number of time units displayed.
 	 - Added an optional parameter to ``timezone_menu()`` that allows more attributes to be added to the generated select tag.
-	 - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants <http://bg2.php.net/manual/en/class.datetime.php#datetime.constants.types>`_.
+	 - Deprecated ``standard_date()``, which now just uses the native ``date()`` with `DateTime constants <http://www.php.net/manual/en/class.datetime.php#datetime.constants.types>`_.
+	 - Added function ``date_range()`` that generates a list of dates between a specified period.
    -  ``create_captcha()`` accepts additional colors parameter, allowing for color customization.
    -  :doc:`URL Helper <helpers/url_helper>` changes include:
 	 - ``url_title()`` will now trim extra dashes from beginning and end.
@@ -75,7 +76,7 @@
    -  Added a work-around in ``force_download()`` for a bug Android <= 2.1, where the filename extension needs to be in uppercase.
    -  ``form_dropdown()`` will now also take an array for unity with other form helpers.
    -  ``do_hash()`` now uses PHP's native ``hash()`` function (supporting more algorithms) and is deprecated.
-   -  Removed previously deprecated helper function ``js_insert_smiley()`` from smiley helper.
+   -  Removed previously deprecated helper function ``js_insert_smiley()`` from :doc:`Smiley Helper <helpers/smiley_helper>`.
    -  :doc:`File Helper <helpers/file_helper>` changes include:
 	 - ``set_realpath()`` can now also handle file paths as opposed to just directories.
 	 - Added an optional paramater to ``delete_files()`` to enable it to skip deleting files such as .htaccess and index.html.
diff --git a/user_guide_src/source/helpers/date_helper.rst b/user_guide_src/source/helpers/date_helper.rst
index e332a91..9de925b 100644
--- a/user_guide_src/source/helpers/date_helper.rst
+++ b/user_guide_src/source/helpers/date_helper.rst
@@ -306,6 +306,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()
 ===========