From c91ff377f46e73348f12e58261be4368c6cd7b5b Mon Sep 17 00:00:00 2001 From: Chuck Hagenbuch Date: Fri, 29 May 2009 21:01:38 -0400 Subject: [PATCH] add toDays(), fromDays(), and diff() functions, and convert to PHP 5 style --- framework/Date/lib/Horde/Date.php | 343 ++++++++++++++++++++++++++------------ 1 file changed, 236 insertions(+), 107 deletions(-) diff --git a/framework/Date/lib/Horde/Date.php b/framework/Date/lib/Horde/Date.php index 564a42633..6612c6aa1 100644 --- a/framework/Date/lib/Horde/Date.php +++ b/framework/Date/lib/Horde/Date.php @@ -46,6 +46,11 @@ * usually better to store datetimes as either UTC datetime or plain unix * timestamp. I usually go with the former - using database datetime type. */ + +/** + * @category Horde + * @package Horde_Date + */ class Horde_Date { const DATE_SUNDAY = 0; @@ -162,7 +167,7 @@ class Horde_Date 'sec' => self::MASK_SECOND, ); - var $_formatCache = array(); + protected $_formatCache = array(); /** * Build a new date object. If $date contains date parts, use them to @@ -179,7 +184,7 @@ class Horde_Date * 03 Mar 1973) * - unix timestamps */ - function __construct($date = null, $timezone = null) + public function __construct($date = null, $timezone = null) { if (!self::$_supportedSpecs) { self::$_supportedSpecs = self::$_defaultSpecs; @@ -252,7 +257,7 @@ class Horde_Date * * @return string This object converted to a string. */ - function __toString() + public function __toString() { try { return $this->format($this->_defaultFormat); @@ -266,7 +271,7 @@ class Horde_Date * * @return DateTime */ - function toDateTime() + public function toDateTime() { $date = new DateTime(null, new DateTimeZone($this->_timezone)); $date->setDate($this->_year, $this->_month, $this->_mday); @@ -275,6 +280,123 @@ class Horde_Date } /** + * Converts a date in the proleptic Gregorian calendar to the no of days + * since 24th November, 4714 B.C. + * + * Returns the no of days since Monday, 24th November, 4714 B.C. in the + * proleptic Gregorian calendar (which is 24th November, -4713 using + * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the + * proleptic Julian calendar). This is also the first day of the 'Julian + * Period' proposed by Joseph Scaliger in 1583, and the number of days + * since this date is known as the 'Julian Day'. (It is not directly + * to do with the Julian calendar, although this is where the name + * is derived from.) + * + * The algorithm is valid for all years (positive and negative), and + * also for years preceding 4714 B.C. + * + * Algorithm is from PEAR::Date_Calc + * + * @author Monte Ohrt + * @author Pierre-Alain Joye + * @author Daniel Convissor + * @author C.A. Woodcock + * + * @return integer The number of days since 24th November, 4714 B.C. + */ + public function toDays() + { + $day = $this->_mday; + $month = $this->_month; + $year = $this->_year; + + if ($month > 2) { + // March = 0, April = 1, ..., December = 9, + // January = 10, February = 11 + $month -= 3; + } else { + $month += 9; + --$year; + } + + $hb_negativeyear = $year < 0; + $century = intval($year / 100); + $year = $year % 100; + + if ($hb_negativeyear) { + // Subtract 1 because year 0 is a leap year; + // And N.B. that we must treat the leap years as occurring + // one year earlier than they do, because for the purposes + // of calculation, the year starts on 1st March: + // + return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) + + intval((1461 * $year + 1) / 4) + + intval((153 * $month + 2) / 5) + + $day + 1721118; + } else { + return intval(146097 * $century / 4) + + intval(1461 * $year / 4) + + intval((153 * $month + 2) / 5) + + $day + 1721119; + } + } + + /** + * Converts number of days since 24th November, 4714 B.C. (in the proleptic + * Gregorian calendar, which is year -4713 using 'Astronomical' year + * numbering) to Gregorian calendar date. + * + * Returned date belongs to the proleptic Gregorian calendar, using + * 'Astronomical' year numbering. + * + * The algorithm is valid for all years (positive and negative), and + * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'), + * and so the only limitation is platform-dependent (for 32-bit systems + * the maximum year would be something like about 1,465,190 A.D.). + * + * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'. + * + * Algorithm is from PEAR::Date_Calc + * + * @author Monte Ohrt + * @author Pierre-Alain Joye + * @author Daniel Convissor + * @author C.A. Woodcock + * + * @param int $days the number of days since 24th November, 4714 B.C. + * @param string $format the string indicating how to format the output + * + * @return Horde_Date A Horde_Date object representing the date. + */ + public static function fromDays($days) + { + $days = intval($days); + + $days -= 1721119; + $century = floor((4 * $days - 1) / 146097); + $days = floor(4 * $days - 1 - 146097 * $century); + $day = floor($days / 4); + + $year = floor((4 * $day + 3) / 1461); + $day = floor(4 * $day + 3 - 1461 * $year); + $day = floor(($day + 4) / 4); + + $month = floor((5 * $day - 3) / 153); + $day = floor(5 * $day - 3 - 153 * $month); + $day = floor(($day + 5) / 5); + + $year = $century * 100 + $year; + if ($month < 10) { + $month +=3; + } else { + $month -=9; + ++$year; + } + + return new Horde_Date($year, $month, $day); + } + + /** * Getter for the date and time properties. * * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or @@ -282,7 +404,7 @@ class Horde_Date * * @return integer The property value, or null if not set. */ - function __get($name) + public function __get($name) { if ($name == 'day') { $name = 'mday'; @@ -298,7 +420,7 @@ class Horde_Date * 'sec'. * @param integer $value The property value. */ - function __set($name, $value) + public function __set($name, $value) { if ($name == 'day') { $name = 'mday'; @@ -322,7 +444,7 @@ class Horde_Date * * @return boolen True if the property exists and is set. */ - function __isset($name) + public function __isset($name) { if ($name == 'day') { $name = 'mday'; @@ -398,13 +520,11 @@ class Horde_Date /** * Returns whether a year is a leap year. * - * @static - * * @param integer $year The year. * * @return boolan True if the year is a leap year. */ - function isLeapYear($year) + public static function isLeapYear($year) { if (strlen($year) != 4 || preg_match('/\D/', $year)) { return false; @@ -422,7 +542,7 @@ class Horde_Date * * @return Horde_Date The date of the first day of the given week. */ - function firstDayOfWeek($week, $year) + public static function firstDayOfWeek($week, $year) { return new Horde_Date(sprintf('%04dW%02d', $year, $week)); } @@ -430,14 +550,12 @@ class Horde_Date /** * Returns the number of days in the specified month. * - * @static - * * @param integer $month The month * @param integer $year The year. * * @return integer The number of days in the month. */ - function daysInMonth($month, $year) + public static function daysInMonth($month, $year) { static $cache = array(); if (!isset($cache[$year][$month])) { @@ -452,7 +570,7 @@ class Horde_Date * * @return integer The day of the week. */ - function dayOfWeek() + public function dayOfWeek() { if ($this->_month > 2) { $month = $this->_month - 2; @@ -476,7 +594,7 @@ class Horde_Date * * @return integer The day of the year. */ - function dayOfYear() + public function dayOfYear() { return $this->format('z') + 1; } @@ -486,7 +604,7 @@ class Horde_Date * * @return integer The week number. */ - function weekOfMonth() + public function weekOfMonth() { return ceil($this->_mday / 7); } @@ -496,7 +614,7 @@ class Horde_Date * * @return integer The week number. */ - function weekOfYear() + public function weekOfYear() { return $this->format('W'); } @@ -504,13 +622,11 @@ class Horde_Date /** * Return the number of weeks in the given year (52 or 53). * - * @static - * * @param integer $year The year to count the number of weeks in. * * @return integer $numWeeks The number of weeks in $year. */ - function weeksInYear($year) + public static function weeksInYear($year) { // Find the last Thursday of the year. $date = new Horde_Date($year . '-12-31'); @@ -526,7 +642,7 @@ class Horde_Date * @param integer $weekday The day of the week (0 = Sunday, etc). * @param integer $nth The $nth $weekday to set to (defaults to 1). */ - function setNthWeekday($weekday, $nth = 1) + public function setNthWeekday($weekday, $nth = 1) { if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) { return; @@ -548,82 +664,12 @@ class Horde_Date * * @return boolean Validity, counting leap years, etc. */ - function isValid() + public function isValid() { return ($this->_year >= 0 && $this->_year <= 9999); } /** - * Correct any over- or underflows in any of the date's members. - * - * @param integer $mask We may not want to correct some overflows. - */ - protected function _correct($mask = self::MASK_ALLPARTS) - { - if ($mask & self::MASK_SECOND) { - if ($this->_sec < 0 || $this->_sec > 59) { - $mask |= self::MASK_MINUTE; - - $this->_min += (int)($this->_sec / 60); - $this->_sec %= 60; - if ($this->_sec < 0) { - $this->_min--; - $this->_sec += 60; - } - } - } - - if ($mask & self::MASK_MINUTE) { - if ($this->_min < 0 || $this->_min > 59) { - $mask |= self::MASK_HOUR; - - $this->_hour += (int)($this->_min / 60); - $this->_min %= 60; - if ($this->_min < 0) { - $this->_hour--; - $this->_min += 60; - } - } - } - - if ($mask & self::MASK_HOUR) { - if ($this->_hour < 0 || $this->_hour > 23) { - $mask |= self::MASK_DAY; - - $this->_mday += (int)($this->_hour / 24); - $this->_hour %= 24; - if ($this->_hour < 0) { - $this->_mday--; - $this->_hour += 24; - } - } - } - - if ($mask & self::MASK_MONTH) { - $this->_year += (int)($this->_month / 12); - $this->_month %= 12; - if ($this->_month < 1) { - $this->_year--; - $this->_month += 12; - } - } - - if ($mask & self::MASK_DAY) { - while ($this->_mday > 28 && - $this->_mday > Horde_Date::daysInMonth($this->_month, $this->_year)) { - $this->_mday -= Horde_Date::daysInMonth($this->_month, $this->_year); - ++$this->_month; - $this->_correct(self::MASK_MONTH); - } - while ($this->_mday < 1) { - --$this->_month; - $this->_correct(self::MASK_MONTH); - $this->_mday += Horde_Date::daysInMonth($this->_month, $this->_year); - } - } - } - - /** * Compare this date to another date object to see which one is * greater (later). Assumes that the dates are in the same * timezone. @@ -634,7 +680,7 @@ class Horde_Date * >= 1 if $this is greater (later) * <= -1 if $other is greater (later) */ - function compareDate($other) + public function compareDate($other) { if (!is_a($other, 'Horde_Date')) { $other = new Horde_Date($other); @@ -657,7 +703,7 @@ class Horde_Date * * @return boolean True if this date is after the other. */ - function after($other) + public function after($other) { return $this->compareDate($other) > 0; } @@ -669,7 +715,7 @@ class Horde_Date * * @return boolean True if this date is before the other. */ - function before($other) + public function before($other) { return $this->compareDate($other) < 0; } @@ -681,7 +727,7 @@ class Horde_Date * * @return boolean True if this date is the same like the other. */ - function equals($other) + public function equals($other) { return $this->compareDate($other) == 0; } @@ -697,7 +743,7 @@ class Horde_Date * >= 1 if $this is greater (later) * <= -1 if $other is greater (later) */ - function compareTime($other) + public function compareTime($other) { if (!is_a($other, 'Horde_Date')) { $other = new Horde_Date($other); @@ -724,7 +770,7 @@ class Horde_Date * >= 1 if $this is greater (later) * <= -1 if $other is greater (later) */ - function compareDateTime($other) + public function compareDateTime($other) { if (!is_a($other, 'Horde_Date')) { $other = new Horde_Date($other); @@ -738,13 +784,25 @@ class Horde_Date } /** + * Returns number of days between this date and another. + * + * @param Horde_Date $other The other day to diff with. + * + * @return integer The absolute number of days between the two dates. + */ + public function diff($other) + { + return abs($this->toDays() - $other->toDays()); + } + + /** * Get the time offset for local time zone. * * @param boolean $colon Place a colon between hours and minutes? * * @return string Timezone offset as a string in the format +HH:MM. */ - function tzOffset($colon = true) + public function tzOffset($colon = true) { return $colon ? $this->format('P') : $this->format('O'); } @@ -754,7 +812,7 @@ class Horde_Date * * @return integer A unix timestamp. */ - function timestamp() + public function timestamp() { if ($this->_year >= 1970 && $this->_year < 2038) { return mktime($this->_hour, $this->_min, $this->_sec, @@ -768,7 +826,7 @@ class Horde_Date * * @return integer A unix timestamp. */ - function datestamp() + public function datestamp() { if ($this->_year >= 1970 && $this->_year < 2038) { return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year); @@ -782,7 +840,7 @@ class Horde_Date * * @return string Date and time. */ - function dateString() + public function dateString() { return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday); } @@ -792,7 +850,7 @@ class Horde_Date * * @return string Date and time. */ - function toJson() + public function toJson() { return $this->format(self::DATE_JSON); } @@ -805,7 +863,7 @@ class Horde_Date * * @return string Formatted time. */ - function format($format) + public function format($format) { if (!isset($this->_formatCache[$format])) { $this->_formatCache[$format] = $this->toDateTime()->format($format); @@ -818,7 +876,7 @@ class Horde_Date * * @return string strftime() formatted date and time. */ - function strftime($format) + public function strftime($format) { if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) { return strftime($format, $this->timestamp()); @@ -832,7 +890,7 @@ class Horde_Date * * @return string strftime() formatted date and time. */ - function _strftime($format) + protected function _strftime($format) { if (preg_match('/%[bBpxX]/', $format)) { require_once 'Horde/NLS.php'; @@ -885,6 +943,76 @@ class Horde_Date } /** + * Correct any over- or underflows in any of the date's members. + * + * @param integer $mask We may not want to correct some overflows. + */ + protected function _correct($mask = self::MASK_ALLPARTS) + { + if ($mask & self::MASK_SECOND) { + if ($this->_sec < 0 || $this->_sec > 59) { + $mask |= self::MASK_MINUTE; + + $this->_min += (int)($this->_sec / 60); + $this->_sec %= 60; + if ($this->_sec < 0) { + $this->_min--; + $this->_sec += 60; + } + } + } + + if ($mask & self::MASK_MINUTE) { + if ($this->_min < 0 || $this->_min > 59) { + $mask |= self::MASK_HOUR; + + $this->_hour += (int)($this->_min / 60); + $this->_min %= 60; + if ($this->_min < 0) { + $this->_hour--; + $this->_min += 60; + } + } + } + + if ($mask & self::MASK_HOUR) { + if ($this->_hour < 0 || $this->_hour > 23) { + $mask |= self::MASK_DAY; + + $this->_mday += (int)($this->_hour / 24); + $this->_hour %= 24; + if ($this->_hour < 0) { + $this->_mday--; + $this->_hour += 24; + } + } + } + + if ($mask & self::MASK_MONTH) { + $this->_year += (int)($this->_month / 12); + $this->_month %= 12; + if ($this->_month < 1) { + $this->_year--; + $this->_month += 12; + } + } + + if ($mask & self::MASK_DAY) { + while ($this->_mday > 28 && + $this->_mday > Horde_Date::daysInMonth($this->_month, $this->_year)) { + $this->_mday -= Horde_Date::daysInMonth($this->_month, $this->_year); + ++$this->_month; + $this->_correct(self::MASK_MONTH); + } + while ($this->_mday < 1) { + --$this->_month; + $this->_correct(self::MASK_MONTH); + $this->_mday += Horde_Date::daysInMonth($this->_month, $this->_year); + } + } + } + + /** * Handle args in order: year month day hour min sec tz */ protected function _initializeFromArgs($args) @@ -927,6 +1055,7 @@ class Horde_Date (string)(int)$date['minute'] == $date['minute']) { $this->_min = (int)$date['minute']; } + $this->_correct(); } -- 2.11.0