_supportedSpecs .= 'bBpxX'; } if (is_array($date) || is_object($date)) { foreach ($date as $key => $val) { if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) { $this->$key = (int)$val; } } // If $date['day'] is present and numeric we may have been passed // a Horde_Form_datetime array. if (is_array($date) && isset($date['day']) && is_numeric($date['day'])) { $this->mday = (int)$date['day']; } // 'minute' key also from Horde_Form_datetime if (is_array($date) && isset($date['minute'])) { $this->min = $date['minute']; } } elseif (!is_null($date)) { // Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'. if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) { $this->year = (int)$parts[1]; $this->month = (int)$parts[2]; $this->mday = (int)$parts[3]; $this->hour = (int)$parts[4]; $this->min = (int)$parts[5]; $this->sec = (int)$parts[6]; } else { // Try as a timestamp. $parts = @getdate($date); if ($parts) { $this->year = $parts['year']; $this->month = $parts['mon']; $this->mday = $parts['mday']; $this->hour = $parts['hours']; $this->min = $parts['minutes']; $this->sec = $parts['seconds']; } } } } /** * @static */ function isLeapYear($year) { if (strlen($year) != 4 || preg_match('/\D/', $year)) { return false; } return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); } /** * Returns the day of the year (1-366) that corresponds to the * first day of the given week. * * TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php * * @param integer $week The week of the year to find the first day of. * @param integer $year The year to calculate for. * * @return integer The day of the year of the first day of the given week. */ function firstDayOfWeek($week, $year) { $jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1)); $start = $jan1->dayOfWeek(); if ($start > HORDE_DATE_THURSDAY) { $start -= 7; } return (($week * 7) - (7 + $start)) + 1; } /** * @static */ function daysInMonth($month, $year) { if ($month == 2) { if (Horde_Date::isLeapYear($year)) { return 29; } else { return 28; } } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) { return 30; } else { return 31; } } /** * Return the day of the week (0 = Sunday, 6 = Saturday) of this * object's date. * * @return integer The day of the week. */ function dayOfWeek() { if ($this->month > 2) { $month = $this->month - 2; $year = $this->year; } else { $month = $this->month + 10; $year = $this->year - 1; } $day = (floor((13 * $month - 1) / 5) + $this->mday + ($year % 100) + floor(($year % 100) / 4) + floor(($year / 100) / 4) - 2 * floor($year / 100) + 77); return (int)($day - 7 * floor($day / 7)); } /** * Returns the day number of the year (1 to 365/366). * * @return integer The day of the year. */ function dayOfYear() { $monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334); $dayOfYear = $this->mday + $monthTotals[$this->month - 1]; if (Horde_Date::isLeapYear($this->year) && $this->month > 2) { ++$dayOfYear; } return $dayOfYear; } /** * Returns the week of the month. * * @since Horde 3.2 * * @return integer The week number. */ function weekOfMonth() { return ceil($this->mday / 7); } /** * Returns the week of the year, first Monday is first day of first week. * * @return integer The week number. */ function weekOfYear() { return $this->format('W'); } /** * 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) { // Find the last Thursday of the year. $day = 31; $date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0)); while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) { --$date->mday; } return $date->weekOfYear(); } /** * Set the date of this object to the $nth weekday of $weekday. * * @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) { if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) { return false; } $this->mday = 1; $first = $this->dayOfWeek(); if ($weekday < $first) { $this->mday = 8 + $weekday - $first; } else { $this->mday = $weekday - $first + 1; } $this->mday += 7 * $nth - 7; $this->correct(); return true; } function dump($prefix = '') { echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "
\n"; } /** * Is the date currently represented by this object a valid date? * * @return boolean Validity, counting leap years, etc. */ function isValid() { if ($this->year < 0 || $this->year > 9999) { return false; } return checkdate($this->month, $this->mday, $this->year); } /** * Correct any over- or underflows in any of the date's members. * * @param integer $mask We may not want to correct some overflows. */ function correct($mask = HORDE_DATE_MASK_ALLPARTS) { if ($mask & HORDE_DATE_MASK_SECOND) { while ($this->sec < 0) { --$this->min; $this->sec += 60; } while ($this->sec > 59) { ++$this->min; $this->sec -= 60; } } if ($mask & HORDE_DATE_MASK_MINUTE) { while ($this->min < 0) { --$this->hour; $this->min += 60; } while ($this->min > 59) { ++$this->hour; $this->min -= 60; } } if ($mask & HORDE_DATE_MASK_HOUR) { while ($this->hour < 0) { --$this->mday; $this->hour += 24; } while ($this->hour > 23) { ++$this->mday; $this->hour -= 24; } } if ($mask & HORDE_DATE_MASK_MONTH) { while ($this->month > 12) { ++$this->year; $this->month -= 12; } while ($this->month < 1) { --$this->year; $this->month += 12; } } if ($mask & HORDE_DATE_MASK_DAY) { while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) { $this->mday -= Horde_Date::daysInMonth($this->month, $this->year); ++$this->month; $this->correct(HORDE_DATE_MASK_MONTH); } while ($this->mday < 1) { --$this->month; $this->correct(HORDE_DATE_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. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareDate($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($this->year != $date->year) { return $this->year - $date->year; } if ($this->month != $date->month) { return $this->month - $date->month; } return $this->mday - $date->mday; } /** * Compare this to another date object by time, to see which one * is greater (later). Assumes that the dates are in the same * timezone. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareTime($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($this->hour != $date->hour) { return $this->hour - $date->hour; } if ($this->min != $date->min) { return $this->min - $date->min; } return $this->sec - $date->sec; } /** * Compare this to another date object, including times, to see * which one is greater (later). Assumes that the dates are in the * same timezone. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareDateTime($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($diff = $this->compareDate($date)) { return $diff; } return $this->compareTime($date); } /** * 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) { $secs = $this->format('Z'); if ($secs < 0) { $sign = '-'; $secs = -$secs; } else { $sign = '+'; } $colon = $colon ? ':' : ''; $mins = intval(($secs + 30) / 60); return sprintf('%s%02d%s%02d', $sign, $mins / 60, $colon, $mins % 60); } /** * Return the unix timestamp representation of this date. * * @return integer A unix timestamp. */ function timestamp() { if (class_exists('DateTime')) { return $this->format('U'); } else { return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year); } } /** * Return the unix timestamp representation of this date, 12:00am. * * @return integer A unix timestamp. */ function datestamp() { if (class_exists('DateTime')) { $dt = new DateTime(); $dt->setDate($this->year, $this->month, $this->mday); $dt->setTime(0, 0, 0); return $dt->format('U'); } else { return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year); } } /** * Format time using the specifiers available in date() or in the DateTime * class' format() method. * * @since Horde 3.3 * * @param string $format * * @return string Formatted time. */ function format($format) { if (class_exists('DateTime')) { $dt = new DateTime(); $dt->setDate($this->year, $this->month, $this->mday); $dt->setTime($this->hour, $this->min, $this->sec); return $dt->format($format); } else { return date($format, $this->timestamp()); } } /** * Format time in ISO-8601 format. Works correctly since Horde 3.2. * * @return string Date and time in ISO-8601 format. */ function iso8601DateTime() { return $this->rfc3339DateTime() . $this->tzOffset(); } /** * Format time in RFC 2822 format. * * @return string Date and time in RFC 2822 format. */ function rfc2822DateTime() { return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false); } /** * Format time in RFC 3339 format. * * @since Horde 3.1 * * @return string Date and time in RFC 3339 format. The seconds part has * been added with Horde 3.2. */ function rfc3339DateTime() { return $this->format('Y-m-d\TH:i:s'); } /** * Format time to standard 'ctime' format. * * @return string Date and time. */ function cTime() { return $this->format('D M j H:i:s Y'); } /** * Format date and time using strftime() format. * * @since Horde 3.1 * * @return string strftime() formatted date and time. */ function strftime($format) { if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) { return strftime($format, $this->timestamp()); } else { return $this->_strftime($format); } } /** * Format date and time using a limited set of the strftime() format. * * @return string strftime() formatted date and time. */ function _strftime($format) { if (preg_match('/%[bBpxX]/', $format)) { require_once 'Horde/NLS.php'; } return preg_replace( array('/%b/e', '/%B/e', '/%C/e', '/%d/e', '/%D/e', '/%e/e', '/%H/e', '/%I/e', '/%m/e', '/%M/e', '/%n/', '/%p/e', '/%R/e', '/%S/e', '/%t/', '/%T/e', '/%x/e', '/%X/e', '/%y/e', '/%Y/', '/%%/'), array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))', '$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))', '(int)($this->year / 100)', 'sprintf(\'%02d\', $this->mday)', '$this->_strftime(\'%m/%d/%y\')', 'sprintf(\'%2d\', $this->mday)', 'sprintf(\'%02d\', $this->hour)', 'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))', 'sprintf(\'%02d\', $this->month)', 'sprintf(\'%02d\', $this->min)', "\n", '$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))', '$this->_strftime(\'%H:%M\')', 'sprintf(\'%02d\', $this->sec)', "\t", '$this->_strftime(\'%H:%M:%S\')', '$this->_strftime(NLS::getLangInfo(D_FMT))', '$this->_strftime(NLS::getLangInfo(T_FMT))', 'substr(sprintf(\'%04d\', $this->year), -2)', (int)$this->year, '%'), $format); } /** * mktime() implementation that supports dates outside of 1970-2038, * from http://phplens.com/phpeverywhere/adodb_date_library. * * @TODO remove in Horde 4 * * This does NOT work with pre-1970 daylight saving times. * * @static */ function _mktime($hr, $min, $sec, $mon = false, $day = false, $year = false, $is_dst = false, $is_gmt = false) { if ($mon === false) { return $is_gmt ? @gmmktime($hr, $min, $sec) : @mktime($hr, $min, $sec); } if ($year > 1901 && $year < 2038 && ($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) { return $is_gmt ? @gmmktime($hr, $min, $sec, $mon, $day, $year) : @mktime($hr, $min, $sec, $mon, $day, $year); } $gmt_different = $is_gmt ? 0 : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0)); $mon = intval($mon); $day = intval($day); $year = intval($year); if ($mon > 12) { $y = floor($mon / 12); $year += $y; $mon -= $y * 12; } elseif ($mon < 1) { $y = ceil((1 - $mon) / 12); $year -= $y; $mon += $y * 12; } $_day_power = 86400; $_hour_power = 3600; $_min_power = 60; $_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); $_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); $_total_date = 0; if ($year >= 1970) { for ($a = 1970; $a <= $year; $a++) { $leaf = Horde_Date::isLeapYear($a); if ($leaf == true) { $loop_table = $_month_table_leaf; $_add_date = 366; } else { $loop_table = $_month_table_normal; $_add_date = 365; } if ($a < $year) { $_total_date += $_add_date; } else { for ($b = 1; $b < $mon; $b++) { $_total_date += $loop_table[$b]; } } } return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different; } for ($a = 1969 ; $a >= $year; $a--) { $leaf = Horde_Date::isLeapYear($a); if ($leaf == true) { $loop_table = $_month_table_leaf; $_add_date = 366; } else { $loop_table = $_month_table_normal; $_add_date = 365; } if ($a > $year) { $_total_date += $_add_date; } else { for ($b = 12; $b > $mon; $b--) { $_total_date += $loop_table[$b]; } } } $_total_date += $loop_table[$mon] - $day; $_day_time = $hr * $_hour_power + $min * $_min_power + $sec; $_day_time = $_day_power - $_day_time; $ret = -($_total_date * $_day_power + $_day_time - $gmt_different); if ($ret < -12220185600) { // If earlier than 5 Oct 1582 - gregorian correction. return $ret + 10 * 86400; } elseif ($ret < -12219321600) { // If in limbo, reset to 15 Oct 1582. return -12219321600; } else { return $ret; } } } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ // {{{ Header /** * Calculates, manipulates and retrieves dates * * It does not rely on 32-bit system time stamps, so it works dates * before 1970 and after 2038. * * PHP versions 4 and 5 * * LICENSE: * * Copyright (c) 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor, * C.A. Woodcock * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted under the terms of the BSD License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @category Date and Time * @package Date * @author Monte Ohrt * @author Pierre-Alain Joye * @author Daniel Convissor * @author C.A. Woodcock * @copyright 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor, C.A. Woodcock * @license http://www.opensource.org/licenses/bsd-license.php * BSD License * @version CVS: $Id: Calc.php,v 1.57 2008/03/23 18:34:16 c01234 Exp $ * @link http://pear.php.net/package/Date * @since File available since Release 1.2 */ // }}} // {{{ General constants: if (!defined('DATE_CALC_BEGIN_WEEKDAY')) { /** * Defines what day starts the week * * Monday (1) is the international standard. * Redefine this to 0 if you want weeks to begin on Sunday. */ define('DATE_CALC_BEGIN_WEEKDAY', 1); } if (!defined('DATE_CALC_FORMAT')) { /** * The default value for each method's $format parameter * * The default is '%Y%m%d'. To override this default, define * this constant before including Calc.php. * * @since Constant available since Release 1.4.4 */ define('DATE_CALC_FORMAT', '%Y%m%d'); } // {{{ Date precision constants (used in 'round()' and 'trunc()'): define('DATE_PRECISION_YEAR', -2); define('DATE_PRECISION_MONTH', -1); define('DATE_PRECISION_DAY', 0); define('DATE_PRECISION_HOUR', 1); define('DATE_PRECISION_10MINUTES', 2); define('DATE_PRECISION_MINUTE', 3); define('DATE_PRECISION_10SECONDS', 4); define('DATE_PRECISION_SECOND', 5); // }}} // {{{ Class: Date_Calc /** * Calculates, manipulates and retrieves dates * * It does not rely on 32-bit system time stamps, so it works dates * before 1970 and after 2038. * * @category Date and Time * @package Date * @author Monte Ohrt * @author Daniel Convissor * @author C.A. Woodcock * @copyright 1999-2007 Monte Ohrt, Pierre-Alain Joye, Daniel Convissor, C.A. Woodcock * @license http://www.opensource.org/licenses/bsd-license.php * BSD License * @version Release: 1.5.0a1 * @link http://pear.php.net/package/Date * @since Class available since Release 1.2 */ class Date_Calc { // {{{ dateFormat() /** * Formats the date in the given format, much like strfmt() * * This function is used to alleviate the problem with 32-bit numbers for * dates pre 1970 or post 2038, as strfmt() has on most systems. * Most of the formatting options are compatible. * * Formatting options: *
     * %a   abbreviated weekday name (Sun, Mon, Tue)
     * %A   full weekday name (Sunday, Monday, Tuesday)
     * %b   abbreviated month name (Jan, Feb, Mar)
     * %B   full month name (January, February, March)
     * %d   day of month (range 00 to 31)
     * %e   day of month, single digit (range 0 to 31)
     * %E   number of days since unspecified epoch (integer)
     *        (%E is useful for passing a date in a URL as
     *        an integer value. Then simply use
     *        daysToDate() to convert back to a date.)
     * %j   day of year (range 001 to 366)
     * %m   month as decimal number (range 1 to 12)
     * %n   newline character (\n)
     * %t   tab character (\t)
     * %w   weekday as decimal (0 = Sunday)
     * %U   week number of current year, first sunday as first week
     * %y   year as decimal (range 00 to 99)
     * %Y   year as decimal including century (range 0000 to 9999)
     * %%   literal '%'
     * 
* * @param int $day the day of the month * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * @param string $format the format string * * @return string the date in the desired format * @access public * @static */ function dateFormat($day, $month, $year, $format) { if (!Date_Calc::isValidDate($day, $month, $year)) { $year = Date_Calc::dateNow('%Y'); $month = Date_Calc::dateNow('%m'); $day = Date_Calc::dateNow('%d'); } $output = ''; for ($strpos = 0; $strpos < strlen($format); $strpos++) { $char = substr($format, $strpos, 1); if ($char == '%') { $nextchar = substr($format, $strpos + 1, 1); switch($nextchar) { case 'a': $output .= Date_Calc::getWeekdayAbbrname($day, $month, $year); break; case 'A': $output .= Date_Calc::getWeekdayFullname($day, $month, $year); break; case 'b': $output .= Date_Calc::getMonthAbbrname($month); break; case 'B': $output .= Date_Calc::getMonthFullname($month); break; case 'd': $output .= sprintf('%02d', $day); break; case 'e': $output .= $day; break; case 'E': $output .= Date_Calc::dateToDays($day, $month, $year); break; case 'j': $output .= Date_Calc::dayOfYear($day, $month, $year); break; case 'm': $output .= sprintf('%02d', $month); break; case 'n': $output .= "\n"; break; case 't': $output .= "\t"; break; case 'w': $output .= Date_Calc::dayOfWeek($day, $month, $year); break; case 'U': $output .= Date_Calc::weekOfYear($day, $month, $year); break; case 'y': $output .= sprintf('%0' . ($year < 0 ? '3' : '2') . 'd', $year % 100); break; case "Y": $output .= sprintf('%0' . ($year < 0 ? '5' : '4') . 'd', $year); break; case '%': $output .= '%'; break; default: $output .= $char.$nextchar; } $strpos++; } else { $output .= $char; } } return $output; } // }}} // {{{ dateNow() /** * Returns the current local date * * NOTE: This function retrieves the local date using strftime(), * which may or may not be 32-bit safe on your system. * * @param string $format the string indicating how to format the output * * @return string the current date in the specified format * @access public * @static */ function dateNow($format = DATE_CALC_FORMAT) { return strftime($format, time()); } // }}} // {{{ getYear() /** * Returns the current local year in format CCYY * * @return string the current year in four digit format * @access public * @static */ function getYear() { return Date_Calc::dateNow('%Y'); } // }}} // {{{ getMonth() /** * Returns the current local month in format MM * * @return string the current month in two digit format * @access public * @static */ function getMonth() { return Date_Calc::dateNow('%m'); } // }}} // {{{ getDay() /** * Returns the current local day in format DD * * @return string the current day of the month in two digit format * @access public * @static */ function getDay() { return Date_Calc::dateNow('%d'); } // }}} // {{{ defaultCentury() /** * Turns a two digit year into a four digit year * * Return value depends on current year; the century chosen * will be the one which forms the year that is closest * to the current year. If the two possibilities are * equidistant to the current year (i.e. 50 years in the past * and 50 years in the future), then the past year is chosen. * * For example, if the current year is 2007: * 03 - returns 2003 * 09 - returns 2009 * 56 - returns 2056 (closer to 2007 than 1956) * 57 - returns 1957 (1957 and 2007 are equidistant, so previous century * chosen) * 58 - returns 1958 * * @param int $year the 2 digit year * * @return int the 4 digit year * @access public * @static */ function defaultCentury($year) { $hn_century = intval(($hn_currentyear = date("Y")) / 100); $hn_currentyear = $hn_currentyear % 100; if ($year < 0 || $year >= 100) $year = $year % 100; if ($year - $hn_currentyear < -50) return ($hn_century + 1) * 100 + $year; else if ($year - $hn_currentyear < 50) return $hn_century * 100 + $year; else return ($hn_century - 1) * 100 + $year; } // }}} // {{{ getSecondsInYear() /** * Returns the total number of seconds in the given year * * This takes into account leap seconds. * * @param int $pn_year the year in four digit format * * @return int * @access public * @static * @since Method available since Release 1.5.0 */ function getSecondsInYear($pn_year) { $pn_year = intval($pn_year); static $ha_leapseconds; if (!isset($ha_leapseconds)) { $ha_leapseconds = array(1972 => 2, 1973 => 1, 1974 => 1, 1975 => 1, 1976 => 1, 1977 => 1, 1978 => 1, 1979 => 1, 1981 => 1, 1982 => 1, 1983 => 1, 1985 => 1, 1987 => 1, 1989 => 1, 1990 => 1, 1992 => 1, 1993 => 1, 1994 => 1, 1995 => 1, 1997 => 1, 1998 => 1, 2005 => 1); } $ret = Date_Calc::daysInYear($pn_year) * 86400; if (isset($ha_leapseconds[$pn_year])) { return $ret + $ha_leapseconds[$pn_year]; } else { return $ret; } } // }}} // {{{ getSecondsInMonth() /** * Returns the total number of seconds in the given month * * This takes into account leap seconds. * * @param int $pn_month the month * @param int $pn_year the year in four digit format * * @return int * @access public * @static * @since Method available since Release 1.5.0 */ function getSecondsInMonth($pn_month, $pn_year) { $pn_month = intval($pn_month); $pn_year = intval($pn_year); static $ha_leapseconds; if (!isset($ha_leapseconds)) { $ha_leapseconds = array(1972 => array(6 => 1, 12 => 1), 1973 => array(12 => 1), 1974 => array(12 => 1), 1975 => array(12 => 1), 1976 => array(12 => 1), 1977 => array(12 => 1), 1978 => array(12 => 1), 1979 => array(12 => 1), 1981 => array(6 => 1), 1982 => array(6 => 1), 1983 => array(6 => 1), 1985 => array(6 => 1), 1987 => array(12 => 1), 1989 => array(12 => 1), 1990 => array(12 => 1), 1992 => array(6 => 1), 1993 => array(6 => 1), 1994 => array(6 => 1), 1995 => array(12 => 1), 1997 => array(6 => 1), 1998 => array(12 => 1), 2005 => array(12 => 1)); } $ret = Date_Calc::daysInMonth($pn_month, $pn_year) * 86400; if (isset($ha_leapseconds[$pn_year][$pn_month])) { return $ret + $ha_leapseconds[$pn_year][$pn_month]; } else { return $ret; } } // }}} // {{{ getSecondsInDay() /** * Returns the total number of seconds in the day of the given date * * This takes into account leap seconds. * * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year in four digit format * * @return int * @access public * @static * @since Method available since Release 1.5.0 */ function getSecondsInDay($pn_day, $pn_month, $pn_year) { // Note to developers: // // The leap seconds listed here are a matter of historical fact, // that is, it is known on which exact day they occurred. // However, the implementation of the class as a whole depends // on the fact that they always occur at the end of the month // (although it is assumed that they could occur in any month, // even though practically they only occur in June or December). // // Do not define a leap second on a day of the month other than // the last day without altering the implementation of the // functions that depend on this one. // // It is possible, though, to define an un-leap second (i.e. a skipped // second (I do not know what they are called), or a number of // consecutive leap seconds). $pn_day = intval($pn_day); $pn_month = intval($pn_month); $pn_year = intval($pn_year); static $ha_leapseconds; if (!isset($ha_leapseconds)) { $ha_leapseconds = array(1972 => array(6 => array(30 => 1), 12 => array(31 => 1)), 1973 => array(12 => array(31 => 1)), 1974 => array(12 => array(31 => 1)), 1975 => array(12 => array(31 => 1)), 1976 => array(12 => array(31 => 1)), 1977 => array(12 => array(31 => 1)), 1978 => array(12 => array(31 => 1)), 1979 => array(12 => array(31 => 1)), 1981 => array(6 => array(30 => 1)), 1982 => array(6 => array(30 => 1)), 1983 => array(6 => array(30 => 1)), 1985 => array(6 => array(30 => 1)), 1987 => array(12 => array(31 => 1)), 1989 => array(12 => array(31 => 1)), 1990 => array(12 => array(31 => 1)), 1992 => array(6 => array(30 => 1)), 1993 => array(6 => array(30 => 1)), 1994 => array(6 => array(30 => 1)), 1995 => array(12 => array(31 => 1)), 1997 => array(6 => array(30 => 1)), 1998 => array(12 => array(31 => 1)), 2005 => array(12 => array(31 => 1))); } if (isset($ha_leapseconds[$pn_year][$pn_month][$pn_day])) { return 86400 + $ha_leapseconds[$pn_year][$pn_month][$pn_day]; } else { return 86400; } } // }}} // {{{ getSecondsInHour() /** * Returns the total number of seconds in the hour of the given date * * This takes into account leap seconds. * * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year in four digit format * @param int $pn_hour the hour * * @return int * @access public * @static */ function getSecondsInHour($pn_day, $pn_month, $pn_year, $pn_hour) { if ($pn_hour < 23) return 3600; else return Date_Calc::getSecondsInDay($pn_day, $pn_month, $pn_year) - 82800; } // }}} // {{{ getSecondsInMinute() /** * Returns the total number of seconds in the minute of the given hour * * This takes into account leap seconds. * * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year in four digit format * @param int $pn_hour the hour * @param int $pn_minute the minute * * @return int * @access public * @static * @since Method available since Release 1.5.0 */ function getSecondsInMinute($pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute) { if ($pn_hour < 23 || $pn_minute < 59) return 60; else return Date_Calc::getSecondsInDay($pn_day, $pn_month, $pn_year) - 86340; } // }}} // {{{ secondsPastMidnight() /** * Returns the no of seconds since midnight (0-86399) * * @param int $pn_hour the hour of the day * @param int $pn_minute the minute * @param mixed $pn_second the second as integer or float * * @return mixed integer or float from 0-86399 * @access public * @static * @since Method available since Release 1.5.0 */ function secondsPastMidnight($pn_hour, $pn_minute, $pn_second) { return 3600 * $pn_hour + 60 * $pn_minute + $pn_second; } // }}} // {{{ secondsPastMidnightToTime() /** * Returns the time as an array (i.e. hour, minute, second) * * @param mixed $pn_seconds the no of seconds since midnight (0-86399) * * @return mixed array of hour, minute (both as integers), second (as * integer or float, depending on parameter) * @access public * @static * @since Method available since Release 1.5.0 */ function secondsPastMidnightToTime($pn_seconds) { if ($pn_seconds >= 86400) { return array(23, 59, $pn_seconds - 86340); } $hn_hour = intval($pn_seconds / 3600); $hn_minute = intval(($pn_seconds - $hn_hour * 3600) / 60); $hn_second = is_float($pn_seconds) ? fmod($pn_seconds, 60) : $pn_seconds % 60; return array($hn_hour, $hn_minute, $hn_second); } // }}} // {{{ secondsPastTheHour() /** * Returns the no of seconds since the last hour o'clock (0-3599) * * @param int $pn_minute the minute * @param mixed $pn_second the second as integer or float * * @return mixed integer or float from 0-3599 * @access public * @static * @since Method available since Release 1.5.0 */ function secondsPastTheHour($pn_minute, $pn_second) { return 60 * $pn_minute + $pn_second; } // }}} // {{{ addHours() /** * Returns the date the specified no of hours from the given date * * To subtract hours use a negative value for the '$pn_hours' parameter * * @param int $pn_hours hours to add * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * @param int $pn_hour the hour * * @return array array of year, month, day, hour * @access public * @static * @since Method available since Release 1.5.0 */ function addHours($pn_hours, $pn_day, $pn_month, $pn_year, $pn_hour) { if ($pn_hours == 0) return array((int) $pn_year, (int) $pn_month, (int) $pn_day, (int) $pn_hour); $hn_days = intval($pn_hours / 24); $hn_hour = $pn_hour + $pn_hours % 24; if ($hn_hour >= 24) { ++$hn_days; $hn_hour -= 24; } else if ($hn_hour < 0) { --$hn_days; $hn_hour += 24; } if ($hn_days == 0) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; } else { list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::addDays($hn_days, $pn_day, $pn_month, $pn_year, "%Y %m %d")); } return array((int) $hn_year, (int) $hn_month, (int) $hn_day, $hn_hour); } // }}} // {{{ addMinutes() /** * Returns the date the specified no of minutes from the given date * * To subtract minutes use a negative value for the '$pn_minutes' parameter * * @param int $pn_minutes minutes to add * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * @param int $pn_hour the hour * @param int $pn_minute the minute * * @return array array of year, month, day, hour, minute * @access public * @static * @since Method available since Release 1.5.0 */ function addMinutes($pn_minutes, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute) { if ($pn_minutes == 0) return array((int) $pn_year, (int) $pn_month, (int) $pn_day, (int) $pn_hour, (int) $pn_minute); $hn_hours = intval($pn_minutes / 60); $hn_minute = $pn_minute + $pn_minutes % 60; if ($hn_minute >= 60) { ++$hn_hours; $hn_minute -= 60; } else if ($hn_minute < 0) { --$hn_hours; $hn_minute += 60; } if ($hn_hours == 0) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; $hn_hour = $pn_hour; } else { list($hn_year, $hn_month, $hn_day, $hn_hour) = Date_Calc::addHours($hn_hours, $pn_day, $pn_month, $pn_year, $pn_hour); } return array($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute); } // }}} // {{{ addSeconds() /** * Returns the date the specified no of seconds from the given date * * If leap seconds are specified to be counted, the passed time must be UTC. * To subtract seconds use a negative value for the '$pn_seconds' parameter. * * N.B. the return type of the second part of the date is float if * either '$pn_seconds' or '$pn_second' is a float; otherwise, it * is integer. * * @param mixed $pn_seconds seconds to add as integer or float * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * @param int $pn_hour the hour * @param int $pn_minute the minute * @param mixed $pn_second the second as integer or float * @param bool $pb_countleap whether to count leap seconds (defaults to * DATE_COUNT_LEAP_SECONDS) * * @return array array of year, month, day, hour, minute, second * @access public * @static * @since Method available since Release 1.5.0 */ function addSeconds($pn_seconds, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute, $pn_second, $pb_countleap = DATE_COUNT_LEAP_SECONDS) { if ($pn_seconds == 0) return array((int) $pn_year, (int) $pn_month, (int) $pn_day, (int) $pn_hour, (int) $pn_minute, $pn_second); if ($pb_countleap) { $hn_seconds = $pn_seconds; $hn_day = (int) $pn_day; $hn_month = (int) $pn_month; $hn_year = (int) $pn_year; $hn_hour = (int) $pn_hour; $hn_minute = (int) $pn_minute; $hn_second = $pn_second; $hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year); $hn_secondsofmonth = 86400 * ($hn_days - Date_Calc::firstDayOfMonth($pn_month, $pn_year)) + Date_Calc::secondsPastMidnight($pn_hour, $pn_minute, $pn_second); if ($hn_seconds > 0) { // Advance to end of month: // if ($hn_secondsofmonth != 0 && $hn_secondsofmonth + $hn_seconds >= ($hn_secondsinmonth = Date_Calc::getSecondsInMonth($hn_month, $hn_year))) { $hn_seconds -= $hn_secondsinmonth - $hn_secondsofmonth; $hn_secondsofmonth = 0; list($hn_year, $hn_month) = Date_Calc::nextMonth($hn_month, $hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); $hn_hour = $hn_minute = $hn_second = 0; } // Advance to end of year: // if ($hn_secondsofmonth == 0 && $hn_month != Date_Calc::getFirstMonthOfYear($hn_year)) { while ($hn_year == $pn_year && $hn_seconds >= ($hn_secondsinmonth = Date_Calc::getSecondsInMonth($hn_month, $hn_year))) { $hn_seconds -= $hn_secondsinmonth; list($hn_year, $hn_month) = Date_Calc::nextMonth($hn_month, $hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); } } if ($hn_secondsofmonth == 0) { // Add years: // if ($hn_month == Date_Calc::getFirstMonthOfYear($hn_year)) { while ($hn_seconds >= ($hn_secondsinyear = Date_Calc::getSecondsInYear($hn_year))) { $hn_seconds -= $hn_secondsinyear; $hn_month = Date_Calc::getFirstMonthOfYear(++$hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); } } // Add months: // while ($hn_seconds >= ($hn_secondsinmonth = Date_Calc::getSecondsInMonth($hn_month, $hn_year))) { $hn_seconds -= $hn_secondsinmonth; list($hn_year, $hn_month) = Date_Calc::nextMonth($hn_month, $hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); } } } else { // // (if $hn_seconds < 0) // Go back to start of month: // if ($hn_secondsofmonth != 0 && -$hn_seconds >= $hn_secondsofmonth) { $hn_seconds += $hn_secondsofmonth; $hn_secondsofmonth = 0; $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); $hn_hour = $hn_minute = $hn_second = 0; } // Go back to start of year: // if ($hn_secondsofmonth == 0) { while ($hn_month != Date_Calc::getFirstMonthOfYear($hn_year)) { list($hn_year, $hn_prevmonth) = Date_Calc::prevMonth($hn_month, $hn_year); if (-$hn_seconds >= ($hn_secondsinmonth = Date_Calc::getSecondsInMonth($hn_prevmonth, $hn_year))) { $hn_seconds += $hn_secondsinmonth; $hn_month = $hn_prevmonth; $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); } else { break; } } } if ($hn_secondsofmonth == 0) { // Subtract years: // if ($hn_month == Date_Calc::getFirstMonthOfYear($hn_year)) { while (-$hn_seconds >= ($hn_secondsinyear = Date_Calc::getSecondsInYear($hn_year - 1))) { $hn_seconds += $hn_secondsinyear; $hn_month = Date_Calc::getFirstMonthOfYear(--$hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); } } // Subtract months: // list($hn_pmyear, $hn_prevmonth) = Date_Calc::prevMonth($hn_month, $hn_year); while (-$hn_seconds >= ($hn_secondsinmonth = Date_Calc::getSecondsInMonth($hn_prevmonth, $hn_pmyear))) { $hn_seconds += $hn_secondsinmonth; $hn_year = $hn_pmyear; $hn_month = $hn_prevmonth; $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); list($hn_pmyear, $hn_prevmonth) = Date_Calc::prevMonth($hn_month, $hn_year); } } } if ($hn_seconds < 0 && $hn_secondsofmonth == 0) { list($hn_year, $hn_month) = Date_Calc::prevMonth($hn_month, $hn_year); $hn_day = Date_Calc::getFirstDayOfMonth($hn_month, $hn_year); $hn_seconds += Date_Calc::getSecondsInMonth($hn_month, $hn_year); } $hn_seconds += Date_Calc::secondsPastMidnight($hn_hour, $hn_minute, $hn_second); if ($hn_seconds < 0) { $hn_daysadd = intval($hn_seconds / 86400) - 1; } else if ($hn_seconds < 86400) { $hn_daysadd = 0; } else { $hn_daysadd = intval($hn_seconds / 86400) - 1; } if ($hn_daysadd != 0) { list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::addDays($hn_daysadd, $hn_day, $hn_month, $hn_year, "%Y %m %d")); $hn_seconds -= $hn_daysadd * 86400; } $hn_secondsinday = Date_Calc::getSecondsInDay($hn_day, $hn_month, $hn_year); if ($hn_seconds >= $hn_secondsinday) { list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::addDays(1, $hn_day, $hn_month, $hn_year, "%Y %m %d")); $hn_seconds -= $hn_secondsinday; } list($hn_hour, $hn_minute, $hn_second) = Date_Calc::secondsPastMidnightToTime($hn_seconds); return array((int) $hn_year, (int) $hn_month, (int) $hn_day, $hn_hour, $hn_minute, $hn_second); } else { // Assume every day has 86400 seconds exactly (ignore leap seconds): // $hn_minutes = intval($pn_seconds / 60); if (is_float($pn_seconds)) { $hn_second = $pn_second + fmod($pn_seconds, 60); } else { $hn_second = $pn_second + $pn_seconds % 60; } if ($hn_second >= 60) { ++$hn_minutes; $hn_second -= 60; } else if ($hn_second < 0) { --$hn_minutes; $hn_second += 60; } if ($hn_minutes == 0) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; $hn_hour = $pn_hour; $hn_minute = $pn_minute; } else { list($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute) = Date_Calc::addMinutes($hn_minutes, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute); } return array($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute, $hn_second); } } // }}} // {{{ dateToDays() /** * 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. * * @param int $day the day of the month * @param int $month the month * @param int $year the year (using 'Astronomical' year numbering) * * @return int the number of days since 24th November, 4714 B.C. * @access public * @static */ function dateToDays($day, $month, $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; } } // }}} // {{{ daysToDate() /** * Converts no 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'. * * @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 string the date in the desired format * @access public * @static */ function daysToDate($days, $format = DATE_CALC_FORMAT) { $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 Date_Calc::dateFormat($day, $month, $year, $format); } // }}} // {{{ getMonths() /** * Returns array of the month numbers, in order, for the given year * * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return array array of integer month numbers, in order * @access public * @static * @since Method available since Release 1.5.0 */ function getMonths($pn_year) { // N.B. Month numbers can be skipped but not duplicated: // return array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); } // }}} // {{{ getMonthNames() /** * Returns an array of month names * * Used to take advantage of the setlocale function to return * language specific month names. * * TODO: cache values to some global array to avoid performance * hits when called more than once. * * @param int $pb_abbreviated whether to return the abbreviated form of the * months * * @return array associative array of integer month numbers, in * order, to month names * @access public * @static */ function getMonthNames($pb_abbreviated = false) { $ret = array(); foreach (Date_Calc::getMonths(2001) as $i) { $ret[$i] = strftime($pb_abbreviated ? '%b' : '%B', mktime(0, 0, 0, $i, 1, 2001)); } return $ret; } // }}} // {{{ prevMonth() /** * Returns month and year of previous month * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return array array of year, month as integers * @access public * @static * @since Method available since Release 1.5.0 */ function prevMonth($pn_month, $pn_year) { $ha_months = Date_Calc::getMonths($pn_year); $hn_monthkey = array_search($pn_month, $ha_months); if (array_key_exists($hn_monthkey - 1, $ha_months)) { return array((int) $pn_year, $ha_months[$hn_monthkey - 1]); } else { $ha_months = Date_Calc::getMonths($pn_year - 1); return array($pn_year - 1, end($ha_months)); } } // }}} // {{{ nextMonth() /** * Returns month and year of next month * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return array array of year, month as integers * @access public * @static * @since Method available since Release 1.5.0 */ function nextMonth($pn_month, $pn_year) { $ha_months = Date_Calc::getMonths($pn_year); $hn_monthkey = array_search($pn_month, $ha_months); if (array_key_exists($hn_monthkey + 1, $ha_months)) { return array((int) $pn_year, $ha_months[$hn_monthkey + 1]); } else { $ha_months = Date_Calc::getMonths($pn_year + 1); return array($pn_year + 1, $ha_months[0]); } } // }}} // {{{ addMonthsToDays() /** * Returns 'Julian Day' of the date the specified no of months * from the given date * * To subtract months use a negative value for the '$pn_months' * parameter * * @param int $pn_months months to add * @param int $pn_days 'Julian Day', i.e. the no of days since 1st * January, 4713 B.C. * * @return int 'Julian Day', i.e. the no of days since 1st January, * 4713 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function addMonthsToDays($pn_months, $pn_days) { if ($pn_months == 0) return (int) $pn_days; list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::daysToDate($pn_days, "%Y %m %d")); $hn_retmonth = $hn_month + $pn_months % 12; $hn_retyear = $hn_year + intval($pn_months / 12); if ($hn_retmonth < 1) { $hn_retmonth += 12; --$hn_retyear; } else if ($hn_retmonth > 12) { $hn_retmonth -= 12; ++$hn_retyear; } if (Date_Calc::isValidDate($hn_day, $hn_retmonth, $hn_retyear)) return Date_Calc::dateToDays($hn_day, $hn_retmonth, $hn_retyear); // Calculate days since first of month: // $hn_dayoffset = $pn_days - Date_Calc::firstDayOfMonth($hn_month, $hn_year); $hn_retmonthfirstday = Date_Calc::firstDayOfMonth($hn_retmonth, $hn_retyear); $hn_retmonthlastday = Date_Calc::lastDayOfMonth($hn_retmonth, $hn_retyear); if ($hn_dayoffset > $hn_retmonthlastday - $hn_retmonthfirstday) { return $hn_retmonthlastday; } else { return $hn_retmonthfirstday + $hn_dayoffset; } } // }}} // {{{ addMonths() /** * Returns the date the specified no of months from the given date * * To subtract months use a negative value for the '$pn_months' * parameter * * @param int $pn_months months to add * @param int $pn_day the day of the month, default is current local * day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param string $ps_format string specifying how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.5.0 */ function addMonths($pn_months, $pn_day, $pn_month, $pn_year, $ps_format = DATE_CALC_FORMAT) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } if ($pn_months == 0) return Date_Calc::dateFormat($pn_day, $pn_month, $pn_year, $ps_format); $hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year); return Date_Calc::daysToDate(Date_Calc::addMonthsToDays($pn_months, $hn_days), $ps_format); } // }}} // {{{ addYearsToDays() /** * Returns 'Julian Day' of the date the specified no of years * from the given date * * To subtract years use a negative value for the '$pn_years' * parameter * * @param int $pn_years years to add * @param int $pn_days 'Julian Day', i.e. the no of days since 1st January, * 4713 B.C. * * @return int 'Julian Day', i.e. the no of days since 1st January, * 4713 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function addYearsToDays($pn_years, $pn_days) { if ($pn_years == 0) return (int) $pn_days; list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::daysToDate($pn_days, "%Y %m %d")); $hn_retyear = $hn_year + $pn_years; if (Date_Calc::isValidDate($hn_day, $hn_month, $hn_retyear)) return Date_Calc::dateToDays($hn_day, $hn_month, $hn_retyear); $ha_months = Date_Calc::getMonths($hn_retyear); if (in_array($hn_month, $ha_months)) { $hn_retmonth = $hn_month; // Calculate days since first of month: // $hn_dayoffset = $pn_days - Date_Calc::firstDayOfMonth($hn_month, $hn_year); $hn_retmonthfirstday = Date_Calc::firstDayOfMonth($hn_retmonth, $hn_retyear); $hn_retmonthlastday = Date_Calc::lastDayOfMonth($hn_retmonth, $hn_retyear); if ($hn_dayoffset > $hn_retmonthlastday - $hn_retmonthfirstday) { return $hn_retmonthlastday; } else { return $hn_retmonthfirstday + $hn_dayoffset; } } else { // Calculate days since first of year: // $hn_dayoffset = $pn_days - Date_Calc::firstDayOfYear($hn_year); $hn_retyearfirstday = Date_Calc::firstDayOfYear($hn_retyear); $hn_retyearlastday = Date_Calc::lastDayOfYear($hn_retyear); if ($hn_dayoffset > $hn_retyearlastday - $hn_retyearfirstday) { return $hn_retyearlastday; } else { return $hn_retyearfirstday + $hn_dayoffset; } } } // }}} // {{{ addYears() /** * Returns the date the specified no of years from the given date * * To subtract years use a negative value for the '$pn_years' * parameter * * @param int $pn_years years to add * @param int $pn_day the day of the month, default is current local * day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param string $ps_format string specifying how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.5.0 */ function addYears($pn_years, $pn_day, $pn_month, $pn_year, $ps_format = DATE_CALC_FORMAT) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } if ($pn_years == 0) return Date_Calc::dateFormat($pn_day, $pn_month, $pn_year, $ps_format); $hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year); return Date_Calc::daysToDate(Date_Calc::addYearsToDays($pn_years, $hn_days), $ps_format); } // }}} // {{{ addDays() /** * Returns the date the specified no of days from the given date * * To subtract days use a negative value for the '$pn_days' parameter * * @param int $pn_days days to add * @param int $pn_day the day of the month, default is current local * day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param string $ps_format string specifying how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.5.0 */ function addDays($pn_days, $pn_day, $pn_month, $pn_year, $ps_format = DATE_CALC_FORMAT) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } if ($pn_days == 0) return Date_Calc::dateFormat($pn_day, $pn_month, $pn_year, $ps_format); return Date_Calc::daysToDate(Date_Calc::dateToDays($pn_day, $pn_month, $pn_year) + $pn_days, $ps_format); } // }}} // {{{ getFirstDayOfMonth() /** * Returns first day of the specified month of specified year as integer * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return int number of first day of month * @access public * @static * @since Method available since Release 1.5.0 */ function getFirstDayOfMonth($pn_month, $pn_year) { return 1; } // }}} // {{{ getLastDayOfMonth() /** * Returns last day of the specified month of specified year as integer * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return int number of last day of month * @access public * @static * @since Method available since Release 1.5.0 */ function getLastDayOfMonth($pn_month, $pn_year) { return Date_Calc::daysInMonth($pn_month, $pn_year); } // }}} // {{{ firstDayOfMonth() /** * Returns the Julian Day of the first day of the month of the specified * year (i.e. the no of days since 24th November, 4714 B.C.) * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return integer the number of days since 24th November, 4714 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function firstDayOfMonth($pn_month, $pn_year) { return Date_Calc::dateToDays(Date_Calc::getFirstDayOfMonth($pn_month, $pn_year), $pn_month, $pn_year); } // }}} // {{{ lastDayOfMonth() /** * Returns the Julian Day of the last day of the month of the specified * year (i.e. the no of days since 24th November, 4714 B.C.) * * @param int $pn_month the month * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return integer the number of days since 24th November, 4714 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function lastDayOfMonth($pn_month, $pn_year) { list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($pn_month, $pn_year); return Date_Calc::firstDayOfMonth($hn_nextmonth, $hn_nmyear) - 1; } // }}} // {{{ getFirstMonthOfYear() /** * Returns first month of specified year as integer * * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return int number of first month of year * @access public * @static * @since Method available since Release 1.5.0 */ function getFirstMonthOfYear($pn_year) { $ha_months = Date_Calc::getMonths($pn_year); return $ha_months[0]; } // }}} // {{{ firstDayOfYear() /** * Returns the Julian Day of the first day of the year (i.e. the no of * days since 24th November, 4714 B.C.) * * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return integer the number of days since 24th November, 4714 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function firstDayOfYear($pn_year) { return Date_Calc::firstDayOfMonth(Date_Calc::getFirstMonthOfYear($pn_year), $pn_year); } // }}} // {{{ lastDayOfYear() /** * Returns the Julian Day of the last day of the year (i.e. the no of * days since 24th November, 4714 B.C.) * * @param int $pn_year the year (using 'Astronomical' year numbering) * * @return integer the number of days since 24th November, 4714 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function lastDayOfYear($pn_year) { return Date_Calc::firstDayOfYear($pn_year + 1) - 1; } // }}} // {{{ dateToDaysJulian() /** * Converts a date in the proleptic Julian calendar to the no of days * since 1st January, 4713 B.C. * * Returns the no of days since Monday, 1st January, 4713 B.C. in the * proleptic Julian calendar (which is 1st January, -4712 using * 'Astronomical' year numbering, and 24th November, 4713 B.C. in the * proleptic Gregorian 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 4713 B.C. * * @param int $day the day of the month * @param int $month the month * @param int $year the year (using 'Astronomical' year numbering) * * @return int the number of days since 1st January, 4713 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function dateToDaysJulian($day, $month, $year) { if ($month > 2) { // March = 0, April = 1, ..., December = 9, // January = 10, February = 11 $month -= 3; } else { $month += 9; --$year; } $hb_negativeyear = $year < 0; 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((1461 * $year + 1) / 4) + intval((153 * $month + 2) / 5) + $day + 1721116; } else { return intval(1461 * $year / 4) + floor((153 * $month + 2) / 5) + $day + 1721117; } } // }}} // {{{ daysToDateJulian() /** * Converts no of days since 1st January, 4713 B.C. (in the proleptic * Julian calendar, which is year -4712 using 'Astronomical' year * numbering) to Julian calendar date * * Returned date belongs to the proleptic Julian calendar, using * 'Astronomical' year numbering. * * @param int $days the number of days since 1st January, 4713 B.C. * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.5.0 */ function daysToDateJulian($days, $format = DATE_CALC_FORMAT) { $days = intval($days); $days -= 1721117; $days = floor(4 * $days - 1); $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); if ($month < 10) { $month +=3; } else { $month -=9; ++$year; } return Date_Calc::dateFormat($day, $month, $year, $format); } // }}} // {{{ isoWeekDate() /** * Returns array defining the 'ISO Week Date' as defined in ISO 8601 * * Expects a date in the proleptic Gregorian calendar using 'Astronomical' * year numbering, that is, with a year 0. Algorithm is valid for all * years (positive and negative). * * N.B. the ISO week day no for Sunday is defined as 7, whereas this * class and its related functions defines Sunday as 0. * * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * * @return array array of ISO Year, ISO Week No, ISO Day No as * integers * @access public * @static * @since Method available since Release 1.5.0 */ function isoWeekDate($pn_day = 0, $pn_month = 0, $pn_year = null) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_jd = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year); $hn_wd = Date_Calc::daysToDayOfWeek($hn_jd); if ($hn_wd == 0) $hn_wd = 7; $hn_jd1 = Date_Calc::firstDayOfYear($pn_year); $hn_day = $hn_jd - $hn_jd1 + 1; if ($hn_wd <= $hn_jd - Date_Calc::lastDayOfYear($pn_year) + 3) { // ISO week is the first week of the next ISO year: // $hn_year = $pn_year + 1; $hn_isoweek = 1; } else { switch ($hn_wd1 = Date_Calc::daysToDayOfWeek($hn_jd1)) { case 1: case 2: case 3: case 4: // Monday - Thursday: // $hn_year = $pn_year; $hn_isoweek = floor(($hn_day + $hn_wd1 - 2) / 7) + 1; break; case 0: $hn_wd1 = 7; case 5: case 6: // Friday - Sunday: // if ($hn_day <= 8 - $hn_wd1) { // ISO week is the last week of the previous ISO year: // list($hn_year, $hn_lastmonth, $hn_lastday) = explode(" ", Date_Calc::daysToDate($hn_jd1 - 1, "%Y %m %d")); list($hn_year, $hn_isoweek, $hn_pisoday) = Date_Calc::isoWeekDate($hn_lastday, $hn_lastmonth, $hn_year); } else { $hn_year = $pn_year; $hn_isoweek = floor(($hn_day + $hn_wd1 - 9) / 7) + 1; } break; } } return array((int) $hn_year, (int) $hn_isoweek, (int) $hn_wd); } // }}} // {{{ gregorianToISO() /** * Converts from Gregorian Year-Month-Day to ISO Year-WeekNumber-WeekDay * * Uses ISO 8601 definitions. * * @param int $day the day of the month * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return string the date in ISO Year-WeekNumber-WeekDay format * @access public * @static */ function gregorianToISO($day, $month, $year) { list($yearnumber, $weeknumber, $weekday) = Date_Calc::isoWeekDate($day, $month, $year); return sprintf("%04d", $yearnumber) . '-' . sprintf("%02d", $weeknumber) . '-' . $weekday; } // }}} // {{{ weekOfYear4th() /** * Returns week of the year counting week 1 as the week that contains 4th * January * * Week 1 is determined to be the week that includes the 4th January, and * therefore can be defined as the first week of the year that has at least * 4 days. The previous week is counted as week 52 or 53 of the previous * year. Note that this definition depends on which day is the first day of * the week, and that if this is not passed as the '$pn_firstdayofweek' * parameter, the default is assumed. * * Note also that the last day week of the year is likely to extend into * the following year, except in the case that the last day of the week * falls on 31st December. * * Also note that this is very similar to the ISO week returned by * 'isoWeekDate()', the difference being that the ISO week always has * 7 days, and if the 4th of January is a Friday, for example, * ISO week 1 would start on Monday, 31st December in the previous year, * whereas the week defined by this function would start on 1st January, * but would be only 6 days long. Of course you can also set the day * of the week, whereas the ISO week starts on a Monday by definition. * * Returned week is an integer from 1 to 53. * * @param int $pn_day the day of the month, default is current * local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param int $pn_firstdayofweek optional integer specifying the first day * of the week * * @return array array of year, week no as integers * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfYear4th($pn_day = 0, $pn_month = 0, $pn_year = null, $pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year)); $hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year); $hn_week = floor(($hn_day + (10 + $hn_wd1 - $pn_firstdayofweek) % 7 + 3) / 7); if ($hn_week > 0) { $hn_year = $pn_year; } else { // Week number is the last week of the previous year: // list($hn_year, $hn_lastmonth, $hn_lastday) = explode(" ", Date_Calc::daysToDate(Date_Calc::lastDayOfYear($pn_year - 1), "%Y %m %d")); list($hn_year, $hn_week) = Date_Calc::weekOfYear4th($hn_lastday, $hn_lastmonth, $hn_year, $pn_firstdayofweek); } return array((int) $hn_year, (int) $hn_week); } // }}} // {{{ weekOfYear7th() /** * Returns week of the year counting week 1 as the week that contains 7th * January * * Week 1 is determined to be the week that includes the 7th January, and * therefore can be defined as the first full week of the year. The * previous week is counted as week 52 or 53 of the previous year. Note * that this definition depends on which day is the first day of the week, * and that if this is not passed as the '$pn_firstdayofweek' parameter, the * default is assumed. * * Note also that the last day week of the year is likely to extend into * the following year, except in the case that the last day of the week * falls on 31st December. * * Returned week is an integer from 1 to 53. * * @param int $pn_day the day of the month, default is current * local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param int $pn_firstdayofweek optional integer specifying the first day * of the week * * @return array array of year, week no as integers * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfYear7th($pn_day = 0, $pn_month = 0, $pn_year = null, $pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year)); $hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year); $hn_week = floor(($hn_day + (6 + $hn_wd1 - $pn_firstdayofweek) % 7) / 7); if ($hn_week > 0) { $hn_year = $pn_year; } else { // Week number is the last week of the previous ISO year: // list($hn_year, $hn_lastmonth, $hn_lastday) = explode(" ", Date_Calc::daysToDate(Date_Calc::lastDayOfYear($pn_year - 1), "%Y %m %d")); list($hn_year, $hn_week) = Date_Calc::weekOfYear7th($hn_lastday, $hn_lastmonth, $hn_year, $pn_firstdayofweek); } return array((int) $hn_year, (int) $hn_week); } // }}} // {{{ dateSeason() /** * Determines julian date of the given season * * Adapted from previous work in Java by James Mark Hamilton. * * @param string $season the season to get the date for: VERNALEQUINOX, * SUMMERSOLSTICE, AUTUMNALEQUINOX, * or WINTERSOLSTICE * @param string $year the year in four digit format. Must be between * -1000 B.C. and 3000 A.D. * * @return float the julian date the season starts on * @access public * @static */ function dateSeason($season, $year = 0) { if ($year == '') { $year = Date_Calc::dateNow('%Y'); } if (($year >= -1000) && ($year <= 1000)) { $y = $year / 1000.0; switch ($season) { case 'VERNALEQUINOX': $juliandate = (((((((-0.00071 * $y) - 0.00111) * $y) + 0.06134) * $y) + 365242.1374) * $y) + 1721139.29189; break; case 'SUMMERSOLSTICE': $juliandate = (((((((0.00025 * $y) + 0.00907) * $y) - 0.05323) * $y) + 365241.72562) * $y) + 1721233.25401; break; case 'AUTUMNALEQUINOX': $juliandate = (((((((0.00074 * $y) - 0.00297) * $y) - 0.11677) * $y) + 365242.49558) * $y) + 1721325.70455; break; case 'WINTERSOLSTICE': default: $juliandate = (((((((-0.00006 * $y) - 0.00933) * $y) - 0.00769) * $y) + 365242.88257) * $y) + 1721414.39987; } } elseif (($year > 1000) && ($year <= 3000)) { $y = ($year - 2000) / 1000; switch ($season) { case 'VERNALEQUINOX': $juliandate = (((((((-0.00057 * $y) - 0.00411) * $y) + 0.05169) * $y) + 365242.37404) * $y) + 2451623.80984; break; case 'SUMMERSOLSTICE': $juliandate = (((((((-0.0003 * $y) + 0.00888) * $y) + 0.00325) * $y) + 365241.62603) * $y) + 2451716.56767; break; case 'AUTUMNALEQUINOX': $juliandate = (((((((0.00078 * $y) + 0.00337) * $y) - 0.11575) * $y) + 365242.01767) * $y) + 2451810.21715; break; case 'WINTERSOLSTICE': default: $juliandate = (((((((0.00032 * $y) - 0.00823) * $y) - 0.06223) * $y) + 365242.74049) * $y) + 2451900.05952; } } return $juliandate; } // }}} // {{{ dayOfYear() /** * Returns number of days since 31 December of year before given date * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * * @return int * @access public * @static * @since Method available since Release 1.5.0 */ function dayOfYear($pn_day = 0, $pn_month = 0, $pn_year = null) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_jd = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year); $hn_jd1 = Date_Calc::firstDayOfYear($pn_year); return $hn_jd - $hn_jd1 + 1; } // }}} // {{{ julianDate() /** * Returns number of days since 31 December of year before given date * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * * @return int * @access public * @static * @deprecated Method deprecated in Release 1.5.0 */ function julianDate($pn_day = 0, $pn_month = 0, $pn_year = null) { return Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year); } // }}} // {{{ getWeekdayFullname() /** * Returns the full weekday name for the given date * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * * @return string the full name of the day of the week * @access public * @static */ function getWeekdayFullname($pn_day = 0, $pn_month = 0, $pn_year = null) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $weekday_names = Date_Calc::getWeekDays(); $weekday = Date_Calc::dayOfWeek($pn_day, $pn_month, $pn_year); return $weekday_names[$weekday]; } // }}} // {{{ getWeekdayAbbrname() /** * Returns the abbreviated weekday name for the given date * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * @param int $length the length of abbreviation * * @return string the abbreviated name of the day of the week * @access public * @static * @see Date_Calc::getWeekdayFullname() */ function getWeekdayAbbrname($pn_day = 0, $pn_month = 0, $pn_year = null, $length = 3) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $weekday_names = Date_Calc::getWeekDays(true); $weekday = Date_Calc::dayOfWeek($pn_day, $pn_month, $pn_year); return $weekday_names[$weekday]; } // }}} // {{{ getMonthFullname() /** * Returns the full month name for the given month * * @param int $month the month * * @return string the full name of the month * @access public * @static */ function getMonthFullname($month) { $month = (int)$month; if (empty($month)) { $month = (int)Date_Calc::dateNow('%m'); } $month_names = Date_Calc::getMonthNames(); return $month_names[$month]; } // }}} // {{{ getMonthAbbrname() /** * Returns the abbreviated month name for the given month * * @param int $month the month * @param int $length the length of abbreviation * * @return string the abbreviated name of the month * @access public * @static * @see Date_Calc::getMonthFullname */ function getMonthAbbrname($month, $length = 3) { $month = (int)$month; if (empty($month)) { $month = Date_Calc::dateNow('%m'); } $month_names = Date_Calc::getMonthNames(true); return $month_names[$month]; } // }}} // {{{ getMonthFromFullname() /** * Returns the numeric month from the month name or an abreviation * * Both August and Aug would return 8. * * @param string $month the name of the month to examine. * Case insensitive. * * @return int the month's number * @access public * @static */ function getMonthFromFullName($month) { $month = strtolower($month); $months = Date_Calc::getMonthNames(); while (list($id, $name) = each($months)) { if (ereg($month, strtolower($name))) { return $id; } } return 0; } // }}} // {{{ getWeekDays() /** * Returns an array of week day names * * Used to take advantage of the setlocale function to return language * specific week days. * * @param int $pb_abbreviated whether to return the abbreviated form of the * days * * @return array an array of week-day names * @access public * @static */ function getWeekDays($pb_abbreviated = false) { for ($i = 0; $i < 7; $i++) { $weekdays[$i] = strftime($pb_abbreviated ? '%a' : '%A', mktime(0, 0, 0, 1, $i, 2001)); } return $weekdays; } // }}} // {{{ daysToDayOfWeek() /** * Returns day of week for specified 'Julian Day' * * 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'. * * @param int $pn_days the number of days since 24th November, 4714 B.C. * * @return int integer from 0 to 7 where 0 represents Sunday * @access public * @static * @since Method available since Release 1.5.0 */ function daysToDayOfWeek($pn_days) { // On Julian day 0 the day is Monday (PHP day 1): // $ret = ($pn_days + 1) % 7; return $ret < 0 ? $ret + 7 : $ret; } // }}} // {{{ dayOfWeek() /** * Returns day of week for given date (0 = Sunday) * * The algorithm is valid for all years (positive and negative). * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * * @return int the number of the day in the week * @access public * @static */ function dayOfWeek($day = null, $month = null, $year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } // if ($month <= 2) { // $month += 12; // --$year; // } // $wd = ($day + // intval((13 * $month + 3) / 5) + // $year + // floor($year / 4) - // floor($year / 100) + // floor($year / 400) + // 1) % 7; // return (int) ($wd < 0 ? $wd + 7 : $wd); return Date_Calc::daysToDayOfWeek(Date_Calc::dateToDays($day, $month, $year)); } // }}} // {{{ weekOfYearAbsolute() /** * Returns week of the year counting week 1 as 1st-7th January, * regardless of what day 1st January falls on * * Returned value is an integer from 1 to 53. Week 53 will start on * 31st December and have only one day, except in a leap year, in * which it will start a day earlier and contain two days. * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * * @return int integer from 1 to 53 * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfYearAbsolute($pn_day = 0, $pn_month = 0, $pn_year = null) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year); return intval(($hn_day + 6) / 7); } // }}} // {{{ weekOfYear1st() /** * Returns week of the year counting week 1 as the week that contains 1st * January * * Week 1 is determined to be the week that includes the 1st January, even * if this week extends into the previous year, in which case the week will * only contain between 1 and 6 days of the current year. Note that this * definition depends on which day is the first day of the week, and that if * this is not passed as the '$pn_firstdayofweek' parameter, the default is * assumed. * * Note also that the last day week of the year is also likely to contain * less than seven days, except in the case that the last day of the week * falls on 31st December. * * Returned value is an integer from 1 to 54. The year will only contain * 54 weeks in the case of a leap year in which 1st January is the last day * of the week, and 31st December is the first day of the week. In this * case, both weeks 1 and 54 will contain one day only. * * @param int $pn_day the day of the month, default is current * local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is * current local year * @param int $pn_firstdayofweek optional integer specifying the first day * of the week * * @return int integer from 1 to 54 * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfYear1st($pn_day = 0, $pn_month = 0, $pn_year = null, $pn_firstdayofweek = DATE_CALC_BEGIN_WEEKDAY) { if (is_null($pn_year)) { $pn_year = Date_Calc::dateNow('%Y'); } if (empty($pn_month)) { $pn_month = Date_Calc::dateNow('%m'); } if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } $hn_wd1 = Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfYear($pn_year)); $hn_day = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year); return floor(($hn_day + (7 + $hn_wd1 - $pn_firstdayofweek) % 7 + 6) / 7); } // }}} // {{{ weekOfYear() /** * Returns week of the year, where first Sunday is first day of first week * * N.B. this function is equivalent to calling: * * Date_Calc::weekOfYear7th($day, $month, $year, 0) * * Returned week is an integer from 1 to 53. * * @param int $pn_day the day of the month, default is current local day * @param int $pn_month the month, default is current local month * @param int $pn_year the year in four digit format, default is current * local year * * @return int integer from 1 to 53 * @access public * @static * @see Date_Calc::weekOfYear7th * @deprecated Method deprecated in Release 1.5.0 */ function weekOfYear($pn_day = 0, $pn_month = 0, $pn_year = null) { $ha_week = Date_Calc::weekOfYear7th($pn_day, $pn_month, $pn_year, 0); return $ha_week[1]; } // }}} // {{{ weekOfMonthAbsolute() /** * Returns week of the month counting week 1 as 1st-7th of the month, * regardless of what day the 1st falls on * * Returned value is an integer from 1 to 5. Week 5 will start on * the 29th of the month and have between 1 and 3 days, except * in February in a non-leap year, when there will be 4 weeks only. * * @param int $pn_day the day of the month, default is current local day * * @return int integer from 1 to 5 * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfMonthAbsolute($pn_day = 0) { if (empty($pn_day)) { $pn_day = Date_Calc::dateNow('%d'); } return intval(($pn_day + 6) / 7); } // }}} // {{{ weekOfMonth() /** * Alias for 'weekOfMonthAbsolute()' * * @param int $pn_day the day of the month, default is current local day * * @return int integer from 1 to 5 * @access public * @static * @since Method available since Release 1.5.0 */ function weekOfMonth($pn_day = 0) { return Date_Calc::weekOfMonthAbsolute($pn_day); } // }}} // {{{ quarterOfYear() /** * Returns quarter of the year for given date * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * * @return int the number of the quarter in the year * @access public * @static */ function quarterOfYear($day = 0, $month = 0, $year = null) { if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return intval(($month - 1) / 3 + 1); } // }}} // {{{ daysInMonth() /** * Returns the number of days in the given month * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * * @return int the number of days the month has * @access public * @static */ function daysInMonth($month = 0, $year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::lastDayOfMonth($month, $year) - Date_Calc::firstDayOfMonth($month, $year) + 1; } // }}} // {{{ daysInYear() /** * Returns the number of days in the given year * * @param int $year the year in four digit format, default is current local * year * * @return int the number of days the year has * @access public * @static */ function daysInYear($year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } return Date_Calc::firstDayOfYear($year + 1) - Date_Calc::firstDayOfYear($year); } // }}} // {{{ weeksInMonth() /** * Returns the number of rows on a calendar month * * Useful for determining the number of rows when displaying a typical * month calendar. * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * * @return int the number of weeks the month has * @access public * @static */ function weeksInMonth($month = 0, $year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } $FDOM = Date_Calc::firstOfMonthWeekday($month, $year); if (DATE_CALC_BEGIN_WEEKDAY==1 && $FDOM==0) { $first_week_days = 7 - $FDOM + DATE_CALC_BEGIN_WEEKDAY; $weeks = 1; } elseif (DATE_CALC_BEGIN_WEEKDAY==0 && $FDOM == 6) { $first_week_days = 7 - $FDOM + DATE_CALC_BEGIN_WEEKDAY; $weeks = 1; } else { $first_week_days = DATE_CALC_BEGIN_WEEKDAY - $FDOM; $weeks = 0; } $first_week_days %= 7; return ceil((Date_Calc::daysInMonth($month, $year) - $first_week_days) / 7) + $weeks; } // }}} // {{{ getCalendarWeek() /** * Return an array with days in week * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return array $week[$weekday] * @access public * @static */ function getCalendarWeek($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $week_array = array(); // date for the column of week $curr_day = Date_Calc::beginOfWeek($day, $month, $year, '%E'); for ($counter = 0; $counter <= 6; $counter++) { $week_array[$counter] = Date_Calc::daysToDate($curr_day, $format); $curr_day++; } return $week_array; } // }}} // {{{ getCalendarMonth() /** * Return a set of arrays to construct a calendar month for the given date * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return array $month[$row][$col] * @access public * @static */ function getCalendarMonth($month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } $month_array = array(); // date for the first row, first column of calendar month if (DATE_CALC_BEGIN_WEEKDAY == 1) { if (Date_Calc::firstOfMonthWeekday($month, $year) == 0) { $curr_day = Date_Calc::firstDayOfMonth($month, $year) - 6; } else { $curr_day = Date_Calc::firstDayOfMonth($month, $year) - Date_Calc::firstOfMonthWeekday($month, $year) + 1; } } else { $curr_day = (Date_Calc::firstDayOfMonth($month, $year) - Date_Calc::firstOfMonthWeekday($month, $year)); } // number of days in this month $daysInMonth = Date_Calc::daysInMonth($month, $year); $weeksInMonth = Date_Calc::weeksInMonth($month, $year); for ($row_counter = 0; $row_counter < $weeksInMonth; $row_counter++) { for ($column_counter = 0; $column_counter <= 6; $column_counter++) { $month_array[$row_counter][$column_counter] = Date_Calc::daysToDate($curr_day, $format); $curr_day++; } } return $month_array; } // }}} // {{{ getCalendarYear() /** * Return a set of arrays to construct a calendar year for the given date * * @param int $year the year in four digit format, default current * local year * @param string $format the string indicating how to format the output * * @return array $year[$month][$row][$col] * @access public * @static */ function getCalendarYear($year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } $year_array = array(); for ($curr_month = 0; $curr_month <= 11; $curr_month++) { $year_array[$curr_month] = Date_Calc::getCalendarMonth($curr_month + 1, $year, $format); } return $year_array; } // }}} // {{{ prevDay() /** * Returns date of day before given date * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function prevDay($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } return Date_Calc::addDays(-1, $day, $month, $year, $format); } // }}} // {{{ nextDay() /** * Returns date of day after given date * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function nextDay($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } return Date_Calc::addDays(1, $day, $month, $year, $format); } // }}} // {{{ prevWeekday() /** * Returns date of the previous weekday, skipping from Monday to Friday * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function prevWeekday($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $days = Date_Calc::dateToDays($day, $month, $year); if (Date_Calc::dayOfWeek($day, $month, $year) == 1) { $days -= 3; } elseif (Date_Calc::dayOfWeek($day, $month, $year) == 0) { $days -= 2; } else { $days -= 1; } return Date_Calc::daysToDate($days, $format); } // }}} // {{{ nextWeekday() /** * Returns date of the next weekday of given date, skipping from * Friday to Monday * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function nextWeekday($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $days = Date_Calc::dateToDays($day, $month, $year); if (Date_Calc::dayOfWeek($day, $month, $year) == 5) { $days += 3; } elseif (Date_Calc::dayOfWeek($day, $month, $year) == 6) { $days += 2; } else { $days += 1; } return Date_Calc::daysToDate($days, $format); } // }}} // {{{ daysToPrevDayOfWeek() /** * Returns 'Julian Day' of the previous specific day of the week * from the given date. * * @param int $dow the day of the week (0 = Sunday) * @param int $days 'Julian Day', i.e. the no of days since 1st * January, 4713 B.C. * @param bool $onorbefore if true and days are same, returns current day * * @return int 'Julian Day', i.e. the no of days since 1st January, * 4713 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function daysToPrevDayOfWeek($dow, $days, $onorbefore = false) { $curr_weekday = Date_Calc::daysToDayOfWeek($days); if ($curr_weekday == $dow) { if ($onorbefore) { return $days; } else { return $days - 7; } } else if ($curr_weekday < $dow) { return $days - 7 + $dow - $curr_weekday; } else { return $days - $curr_weekday + $dow; } } // }}} // {{{ prevDayOfWeek() /** * Returns date of the previous specific day of the week * from the given date * * @param int $dow the day of the week (0 = Sunday) * @param int $day the day of the month, default is current local * day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is * current local year * @param string $format the string indicating how to format the output * @param bool $onorbefore if true and days are same, returns current day * * @return string the date in the desired format * @access public * @static */ function prevDayOfWeek($dow, $day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT, $onorbefore = false) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $days = Date_Calc::dateToDays($day, $month, $year); $days = Date_Calc::daysToPrevDayOfWeek($dow, $days, $onorbefore); return Date_Calc::daysToDate($days, $format); } // }}} // {{{ daysToNextDayOfWeek() /** * Returns 'Julian Day' of the next specific day of the week * from the given date. * * @param int $dow the day of the week (0 = Sunday) * @param int $days 'Julian Day', i.e. the no of days since 1st * January, 4713 B.C. * @param bool $onorafter if true and days are same, returns current day * * @return int 'Julian Day', i.e. the no of days since 1st January, * 4713 B.C. * @access public * @static * @since Method available since Release 1.5.0 */ function daysToNextDayOfWeek($dow, $days, $onorafter = false) { $curr_weekday = Date_Calc::daysToDayOfWeek($days); if ($curr_weekday == $dow) { if ($onorafter) { return $days; } else { return $days + 7; } } else if ($curr_weekday > $dow) { return $days + 7 - $curr_weekday + $dow; } else { return $days + $dow - $curr_weekday; } } // }}} // {{{ nextDayOfWeek() /** * Returns date of the next specific day of the week * from the given date * * @param int $dow the day of the week (0 = Sunday) * @param int $day the day of the month, default is current local * day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is * current local year * @param string $format the string indicating how to format the output * @param bool $onorafter if true and days are same, returns current day * * @return string the date in the desired format * @access public * @static */ function nextDayOfWeek($dow, $day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT, $onorafter = false) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $days = Date_Calc::dateToDays($day, $month, $year); $days = Date_Calc::daysToNextDayOfWeek($dow, $days, $onorafter); return Date_Calc::daysToDate($days, $format); } // }}} // {{{ prevDayOfWeekOnOrBefore() /** * Returns date of the previous specific day of the week * on or before the given date * * @param int $dow the day of the week (0 = Sunday) * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function prevDayOfWeekOnOrBefore($dow, $day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { return Date_Calc::prevDayOfWeek($dow, $day, $month, $year, $format, true); } // }}} // {{{ nextDayOfWeekOnOrAfter() /** * Returns date of the next specific day of the week * on or after the given date * * @param int $dow the day of the week (0 = Sunday) * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function nextDayOfWeekOnOrAfter($dow, $day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { return Date_Calc::nextDayOfWeek($dow, $day, $month, $year, $format, true); } // }}} // {{{ beginOfWeek() /** * Find the month day of the beginning of week for given date, * using DATE_CALC_BEGIN_WEEKDAY * * Can return weekday of prev month. * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function beginOfWeek($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $hn_days = Date_Calc::dateToDays($day, $month, $year); $this_weekday = Date_Calc::daysToDayOfWeek($hn_days); $interval = (7 - DATE_CALC_BEGIN_WEEKDAY + $this_weekday) % 7; return Date_Calc::daysToDate($hn_days - $interval, $format); } // }}} // {{{ endOfWeek() /** * Find the month day of the end of week for given date, * using DATE_CALC_BEGIN_WEEKDAY * * Can return weekday of following month. * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function endOfWeek($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } $hn_days = Date_Calc::dateToDays($day, $month, $year); $this_weekday = Date_Calc::daysToDayOfWeek($hn_days); $interval = (6 + DATE_CALC_BEGIN_WEEKDAY - $this_weekday) % 7; return Date_Calc::daysToDate($hn_days + $interval, $format); } // }}} // {{{ beginOfPrevWeek() /** * Find the month day of the beginning of week before given date, * using DATE_CALC_BEGIN_WEEKDAY * * Can return weekday of prev month. * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function beginOfPrevWeek($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } list($hn_pwyear, $hn_pwmonth, $hn_pwday) = explode(" ", Date_Calc::daysToDate(Date_Calc::dateToDays($day, $month, $year) - 7, '%Y %m %d')); return Date_Calc::beginOfWeek($hn_pwday, $hn_pwmonth, $hn_pwyear, $format); } // }}} // {{{ beginOfNextWeek() /** * Find the month day of the beginning of week after given date, * using DATE_CALC_BEGIN_WEEKDAY * * Can return weekday of prev month. * * @param int $day the day of the month, default is current local day * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function beginOfNextWeek($day = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } if (empty($day)) { $day = Date_Calc::dateNow('%d'); } list($hn_pwyear, $hn_pwmonth, $hn_pwday) = explode(" ", Date_Calc::daysToDate(Date_Calc::dateToDays($day, $month, $year) + 7, '%Y %m %d')); return Date_Calc::beginOfWeek($hn_pwday, $hn_pwmonth, $hn_pwyear, $format); } // }}} // {{{ beginOfMonth() /** * Return date of first day of month of given date * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::beginOfMonthBySpan() * @deprecated Method deprecated in Release 1.4.4 */ function beginOfMonth($month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($month, $year), $month, $year, $format); } // }}} // {{{ endOfMonth() /** * Return date of last day of month of given date * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::beginOfMonthBySpan() * @since Method available since Release 1.5.0 * @deprecated Method deprecated in Release 1.5.0 */ function endOfMonth($month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::daysToDate(Date_Calc::lastDayOfMonth($month, $year), $format); } // }}} // {{{ beginOfPrevMonth() /** * Returns date of the first day of previous month of given date * * @param mixed $dummy irrelevant parameter * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::beginOfMonthBySpan() * @deprecated Method deprecated in Release 1.4.4 */ function beginOfPrevMonth($dummy = null, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } list($hn_pmyear, $hn_prevmonth) = Date_Calc::prevMonth($month, $year); return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($hn_prevmonth, $hn_pmyear), $hn_prevmonth, $hn_pmyear, $format); } // }}} // {{{ endOfPrevMonth() /** * Returns date of the last day of previous month for given date * * @param mixed $dummy irrelevant parameter * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::endOfMonthBySpan() * @deprecated Method deprecated in Release 1.4.4 */ function endOfPrevMonth($dummy = null, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::daysToDate(Date_Calc::firstDayOfMonth($month, $year) - 1, $format); } // }}} // {{{ beginOfNextMonth() /** * Returns date of begin of next month of given date * * @param mixed $dummy irrelevant parameter * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::beginOfMonthBySpan() * @deprecated Method deprecated in Release 1.4.4 */ function beginOfNextMonth($dummy = null, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($month, $year); return Date_Calc::dateFormat(Date_Calc::getFirstDayOfMonth($hn_nextmonth, $hn_nmyear), $hn_nextmonth, $hn_nmyear, $format); } // }}} // {{{ endOfNextMonth() /** * Returns date of the last day of next month of given date * * @param mixed $dummy irrelevant parameter * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @see Date_Calc::endOfMonthBySpan() * @deprecated Method deprecated in Release 1.4.4 */ function endOfNextMonth($dummy = null, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } list($hn_nmyear, $hn_nextmonth) = Date_Calc::nextMonth($month, $year); return Date_Calc::daysToDate(Date_Calc::lastDayOfMonth($hn_nextmonth, $hn_nmyear), $format); } // }}} // {{{ beginOfMonthBySpan() /** * Returns date of the first day of the month in the number of months * from the given date * * @param int $months the number of months from the date provided. * Positive numbers go into the future. * Negative numbers go into the past. * 0 is the month presented in $month. * @param string $month the month, default is current local month * @param string $year the year in four digit format, default is the * current local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.4.4 */ function beginOfMonthBySpan($months = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::addMonths($months, Date_Calc::getFirstDayOfMonth($month, $year), $month, $year, $format); } // }}} // {{{ endOfMonthBySpan() /** * Returns date of the last day of the month in the number of months * from the given date * * @param int $months the number of months from the date provided. * Positive numbers go into the future. * Negative numbers go into the past. * 0 is the month presented in $month. * @param string $month the month, default is current local month * @param string $year the year in four digit format, default is the * current local year * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static * @since Method available since Release 1.4.4 */ function endOfMonthBySpan($months = 0, $month = 0, $year = null, $format = DATE_CALC_FORMAT) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } $hn_days = Date_Calc::addMonthsToDays($months + 1, Date_Calc::firstDayOfMonth($month, $year)) - 1; return Date_Calc::daysToDate($hn_days, $format); } // }}} // {{{ firstOfMonthWeekday() /** * Find the day of the week for the first of the month of given date * * @param int $month the month, default is current local month * @param int $year the year in four digit format, default is current * local year * * @return int number of weekday for the first day, 0=Sunday * @access public * @static */ function firstOfMonthWeekday($month = 0, $year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if (empty($month)) { $month = Date_Calc::dateNow('%m'); } return Date_Calc::daysToDayOfWeek(Date_Calc::firstDayOfMonth($month, $year)); } // }}} // {{{ nWeekdayOfMonth() /** * Calculates the date of the Nth weekday of the month, * such as the second Saturday of January 2000 * * @param int $week the number of the week to get * (1 = first, etc. Also can be 'last'.) * @param int $dow the day of the week (0 = Sunday) * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * @param string $format the string indicating how to format the output * * @return string the date in the desired format * @access public * @static */ function nWeekdayOfMonth($week, $dow, $month, $year, $format = DATE_CALC_FORMAT) { if (is_numeric($week)) { $DOW1day = ($week - 1) * 7 + 1; $DOW1 = Date_Calc::dayOfWeek($DOW1day, $month, $year); $wdate = ($week - 1) * 7 + 1 + (7 + $dow - $DOW1) % 7; if ($wdate > Date_Calc::daysInMonth($month, $year)) { return -1; } else { return Date_Calc::dateFormat($wdate, $month, $year, $format); } } elseif ($week == 'last' && $dow < 7) { $lastday = Date_Calc::daysInMonth($month, $year); $lastdow = Date_Calc::dayOfWeek($lastday, $month, $year); $diff = $dow - $lastdow; if ($diff > 0) { return Date_Calc::dateFormat($lastday - (7 - $diff), $month, $year, $format); } else { return Date_Calc::dateFormat($lastday + $diff, $month, $year, $format); } } else { return -1; } } // }}} // {{{ isValidDate() /** * Returns true for valid date, false for invalid date * * Uses the proleptic Gregorian calendar, with the year 0 (1 B.C.) * assumed to be valid and also assumed to be a leap year. * * @param int $day the day of the month * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return bool * @access public * @static */ function isValidDate($day, $month, $year) { if ($day < 1 || $month < 1 || $month > 12) return false; if ($month == 2) { if (Date_Calc::isLeapYearGregorian($year)) { return $day <= 29; } else { return $day <= 28; } } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) { return $day <= 30; } else { return $day <= 31; } } // }}} // {{{ isLeapYearGregorian() /** * Returns true for a leap year, else false * * Uses the proleptic Gregorian calendar. The year 0 (1 B.C.) is * assumed in this algorithm to be a leap year. The function is * valid for all years, positive and negative. * * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return bool * @access public * @static * @since Method available since Release 1.5.0 */ function isLeapYearGregorian($year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } return (($year % 4 == 0) && ($year % 100 != 0)) || ($year % 400 == 0); } // }}} // {{{ isLeapYearJulian() /** * Returns true for a leap year, else false * * Uses the proleptic Julian calendar. The year 0 (1 B.C.) is * assumed in this algorithm to be a leap year. The function is * valid for all years, positive and negative. * * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return boolean * @access public * @static * @since Method available since Release 1.5.0 */ function isLeapYearJulian($year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } return $year % 4 == 0; } // }}} // {{{ isLeapYear() /** * Returns true for a leap year, else false * * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return boolean * @access public * @static */ function isLeapYear($year = null) { if (is_null($year)) { $year = Date_Calc::dateNow('%Y'); } if ($year < 1582) { // pre Gregorio XIII - 1582 return Date_Calc::isLeapYearJulian($year); } else { // post Gregorio XIII - 1582 return Date_Calc::isLeapYearGregorian($year); } } // }}} // {{{ isFutureDate() /** * Determines if given date is a future date from now * * @param int $day the day of the month * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return bool * @access public * @static */ function isFutureDate($day, $month, $year) { $this_year = Date_Calc::dateNow('%Y'); $this_month = Date_Calc::dateNow('%m'); $this_day = Date_Calc::dateNow('%d'); if ($year > $this_year) { return true; } elseif ($year == $this_year) { if ($month > $this_month) { return true; } elseif ($month == $this_month) { if ($day > $this_day) { return true; } } } return false; } // }}} // {{{ isPastDate() /** * Determines if given date is a past date from now * * @param int $day the day of the month * @param int $month the month * @param int $year the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return boolean * @access public * @static */ function isPastDate($day, $month, $year) { $this_year = Date_Calc::dateNow('%Y'); $this_month = Date_Calc::dateNow('%m'); $this_day = Date_Calc::dateNow('%d'); if ($year < $this_year) { return true; } elseif ($year == $this_year) { if ($month < $this_month) { return true; } elseif ($month == $this_month) { if ($day < $this_day) { return true; } } } return false; } // }}} // {{{ dateDiff() /** * Returns number of days between two given dates * * @param int $day1 the day of the month * @param int $month1 the month * @param int $year1 the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * @param int $day2 the day of the month * @param int $month2 the month * @param int $year2 the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return int the absolute number of days between the two dates. * If an error occurs, -1 is returned. * @access public * @static */ function dateDiff($day1, $month1, $year1, $day2, $month2, $year2) { if (!Date_Calc::isValidDate($day1, $month1, $year1)) { return -1; } if (!Date_Calc::isValidDate($day2, $month2, $year2)) { return -1; } return abs(Date_Calc::dateToDays($day1, $month1, $year1) - Date_Calc::dateToDays($day2, $month2, $year2)); } // }}} // {{{ compareDates() /** * Compares two dates * * @param int $day1 the day of the month * @param int $month1 the month * @param int $year1 the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * @param int $day2 the day of the month * @param int $month2 the month * @param int $year2 the year. Use the complete year instead of the * abbreviated version. E.g. use 2005, not 05. * * @return int 0 if the dates are equal. 1 if date 1 is later, -1 * if date 1 is earlier. * @access public * @static */ function compareDates($day1, $month1, $year1, $day2, $month2, $year2) { $ndays1 = Date_Calc::dateToDays($day1, $month1, $year1); $ndays2 = Date_Calc::dateToDays($day2, $month2, $year2); if ($ndays1 == $ndays2) { return 0; } return ($ndays1 > $ndays2) ? 1 : -1; } // }}} // {{{ round() /** * Rounds the date according to the specified precision * * The precision parameter must be one of the following constants: * * DATE_PRECISION_YEAR * DATE_PRECISION_MONTH * DATE_PRECISION_DAY * DATE_PRECISION_HOUR * DATE_PRECISION_10MINUTES * DATE_PRECISION_MINUTE * DATE_PRECISION_10SECONDS * DATE_PRECISION_SECOND * * The precision can also be specified as an integral offset from * one of these constants, where the offset reflects a precision * of 10 to the power of the offset greater than the constant. * For example: * * DATE_PRECISION_YEAR - 1 rounds the date to the nearest 10 * years * DATE_PRECISION_YEAR - 3 rounds the date to the nearest 1000 * years * DATE_PRECISION_SECOND + 1 rounds the date to 1 decimal * point of a second * DATE_PRECISION_SECOND + 1 rounds the date to 3 decimal * points of a second * DATE_PRECISION_SECOND + 1 rounds the date to the nearest 10 * seconds (thus it is equivalent to * DATE_PRECISION_10SECONDS) * * N.B. This function requires a time in UTC if both the precision is at * least DATE_PRECISION_SECOND and leap seconds are being counted, otherwise * any local time is acceptable. * * @param int $pn_precision a 'DATE_PRECISION_*' constant * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * @param int $pn_hour the hour * @param int $pn_minute the minute * @param mixed $pn_second the second as integer or float * @param bool $pb_countleap whether to count leap seconds (defaults to * DATE_COUNT_LEAP_SECONDS) * * @return array array of year, month, day, hour, minute, second * @access public * @static * @since Method available since Release 1.5.0 */ function round($pn_precision, $pn_day, $pn_month, $pn_year, $pn_hour = 0, $pn_minute = 0, $pn_second = 0, $pb_countleap = DATE_COUNT_LEAP_SECONDS) { if ($pn_precision <= DATE_PRECISION_YEAR) { $hn_month = 0; $hn_day = 0; $hn_hour = 0; $hn_minute = 0; $hn_second = 0; if ($pn_precision < DATE_PRECISION_YEAR) { $hn_year = round($pn_year, $pn_precision - DATE_PRECISION_YEAR); } else { // Check part-year: // $hn_midyear = (Date_Calc::firstDayOfYear($pn_year + 1) - Date_Calc::firstDayOfYear($pn_year)) / 2; if (($hn_days = Date_Calc::dayOfYear($pn_day, $pn_month, $pn_year)) <= $hn_midyear - 1) { $hn_year = $pn_year; } else if ($hn_days >= $hn_midyear) { // Round up: // $hn_year = $pn_year + 1; } else { // Take time into account: // $hn_partday = Date_Calc::secondsPastMidnight($pn_hour, $pn_minute, $pn_second) / 86400; if ($hn_partday >= $hn_midyear - $hn_days) { // Round up: // $hn_year = $pn_year + 1; } else { $hn_year = $pn_year; } } } } else if ($pn_precision == DATE_PRECISION_MONTH) { $hn_year = $pn_year; $hn_day = 0; $hn_hour = 0; $hn_minute = 0; $hn_second = 0; $hn_firstofmonth = Date_Calc::firstDayOfMonth($pn_month, $pn_year); $hn_midmonth = (Date_Calc::lastDayOfMonth($pn_month, $pn_year) + 1 - $hn_firstofmonth) / 2; if (($hn_days = Date_Calc::dateToDays($pn_day, $pn_month, $pn_year) - $hn_firstofmonth) <= $hn_midmonth - 1) { $hn_month = $pn_month; } else if ($hn_days >= $hn_midmonth) { // Round up: // list($hn_year, $hn_month) = Date_Calc::nextMonth($pn_month, $pn_year); } else { // Take time into account: // $hn_partday = Date_Calc::secondsPastMidnight($pn_hour, $pn_minute, $pn_second) / 86400; if ($hn_partday >= $hn_midmonth - $hn_days) { // Round up: // list($hn_year, $hn_month) = Date_Calc::nextMonth($pn_month, $pn_year); } else { $hn_month = $pn_month; } } } else if ($pn_precision == DATE_PRECISION_DAY) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_hour = 0; $hn_minute = 0; $hn_second = 0; if (Date_Calc::secondsPastMidnight($pn_hour, $pn_minute, $pn_second) >= 43200) { // Round up: // list($hn_year, $hn_month, $hn_day) = explode(" ", Date_Calc::nextDay($pn_day, $pn_month, $pn_year, "%Y %m %d")); } else { $hn_day = $pn_day; } } else if ($pn_precision == DATE_PRECISION_HOUR) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; $hn_minute = 0; $hn_second = 0; if (Date_Calc::secondsPastTheHour($pn_minute, $pn_second) >= 1800) { // Round up: // list($hn_year, $hn_month, $hn_day, $hn_hour) = Date_Calc::addHours(1, $pn_day, $pn_month, $pn_year, $pn_hour); } else { $hn_hour = $pn_hour; } } else if ($pn_precision <= DATE_PRECISION_MINUTE) { $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; $hn_hour = $pn_hour; $hn_second = 0; if ($pn_precision < DATE_PRECISION_MINUTE) { $hn_minute = round($pn_minute, $pn_precision - DATE_PRECISION_MINUTE); } else { // Check seconds: // if ($pn_second >= 30) { // Round up: // list($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute) = Date_Calc::addMinutes(1, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute); } else { $hn_minute = $pn_minute; } } } else { // Precision is at least (DATE_PRECISION_SECOND - 1): // $hn_year = $pn_year; $hn_month = $pn_month; $hn_day = $pn_day; $hn_hour = $pn_hour; $hn_minute = $pn_minute; $hn_second = round($pn_second, $pn_precision - DATE_PRECISION_SECOND); if (fmod($hn_second, 1) == 0.0) { $hn_second = (int) $hn_second; if ($hn_second != intval($pn_second)) { list($hn_year, $hn_month, $hn_day, $hn_hour, $hn_minute, $hn_second) = Date_Calc::addSeconds($hn_second - intval($pn_second), $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute, intval($pn_second), $pn_precision >= DATE_PRECISION_SECOND && $pb_countleap); // // (N.B. if rounded to nearest 10 seconds, // user does not expect seconds to be '60') } } } return array((int) $hn_year, (int) $hn_month, (int) $hn_day, (int) $hn_hour, (int) $hn_minute, $hn_second); } // }}} // {{{ roundSeconds() /** * Rounds seconds up or down to the nearest specified unit * * @param int $pn_precision number of digits after the decimal point * @param int $pn_day the day of the month * @param int $pn_month the month * @param int $pn_year the year * @param int $pn_hour the hour * @param int $pn_minute the minute * @param mixed $pn_second the second as integer or float * @param bool $pb_countleap whether to count leap seconds (defaults to * DATE_COUNT_LEAP_SECONDS) * * @return array array of year, month, day, hour, minute, second * @access public * @static * @since Method available since Release 1.5.0 */ function roundSeconds($pn_precision, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute, $pn_second, $pb_countleap = DATE_COUNT_LEAP_SECONDS) { return Date_Calc::round(DATE_PRECISION_SECOND + $pn_precision, $pn_day, $pn_month, $pn_year, $pn_hour, $pn_minute, $pn_second); } // }}} } // }}} /* * Local variables: * mode: php * tab-width: 4 * c-basic-offset: 4 * c-hanging-comment-ender-p: nil * End: */ /** * This file contains the Horde_Date_Recurrence class and according constants. * * $Horde: framework/Date/Date/Recurrence.php,v 1.7.2.16 2010-10-14 14:18:05 jan Exp $ * * Copyright 2007-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @since Horde 3.2 * @package Horde_Date */ /** Horde_Date */ // require_once 'Horde/Date.php'; /** Date_Calc */ //require_once 'Date/Calc.php'; /** No recurrence. */ define('HORDE_DATE_RECUR_NONE', 0); /** Recurs daily. */ define('HORDE_DATE_RECUR_DAILY', 1); /** Recurs weekly. */ define('HORDE_DATE_RECUR_WEEKLY', 2); /** Recurs monthly on the same date. */ define('HORDE_DATE_RECUR_MONTHLY_DATE', 3); /** Recurs monthly on the same week day. */ define('HORDE_DATE_RECUR_MONTHLY_WEEKDAY', 4); /** Recurs yearly on the same date. */ define('HORDE_DATE_RECUR_YEARLY_DATE', 5); /** Recurs yearly on the same day of the year. */ define('HORDE_DATE_RECUR_YEARLY_DAY', 6); /** Recurs yearly on the same week day. */ define('HORDE_DATE_RECUR_YEARLY_WEEKDAY', 7); /** * The Horde_Date_Recurrence class implements algorithms for calculating * recurrences of events, including several recurrence types, intervals, * exceptions, and conversion from and to vCalendar and iCalendar recurrence * rules. * * All methods expecting dates as parameters accept all values that the * Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date * object, an ISO time string or a hash. * * @author Jan Schneider * @since Horde 3.2 * @package Horde_Date */ class Horde_Date_Recurrence { /** * The start time of the event. * * @var Horde_Date */ var $start; /** * The end date of the recurrence interval. * * @var Horde_Date */ var $recurEnd = null; /** * The number of recurrences. * * @var integer */ var $recurCount = null; /** * The type of recurrence this event follows. HORDE_DATE_RECUR_* constant. * * @var integer */ var $recurType = HORDE_DATE_RECUR_NONE; /** * The length of time between recurrences. The time unit depends on the * recurrence type. * * @var integer */ var $recurInterval = 1; /** * Any additional recurrence data. * * @var integer */ var $recurData = null; /** * BYDAY recurrence number * * @var integer */ var $recurNthDay = 0; /** * BYMONTH recurrence data * * @var array */ var $recurMonths = array(); /** * All the exceptions from recurrence for this event. * * @var array */ var $exceptions = array(); /** * All the dates this recurrence has been marked as completed. * * @var array */ var $completions = array(); /** * Constructor. * * @param Horde_Date $start Start of the recurring event. */ function Horde_Date_Recurrence($start) { $this->start = new Horde_Date($start); } /** * Checks if this event recurs on a given day of the week. * * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* * constants specifying the day(s) to check. * * @return boolean True if this event recurs on the given day(s). */ function recurOnDay($dayMask) { return ($this->recurData & $dayMask); } /** * Specifies the days this event recurs on. * * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* * constants specifying the day(s) to recur on. */ function setRecurOnDay($dayMask) { $this->recurData = $dayMask; } /** * Returns the days this event recurs on. * * @return integer A mask consisting of HORDE_DATE_MASK_* constants * specifying the day(s) this event recurs on. */ function getRecurOnDays() { return $this->recurData; } /** * Specifies the months for yearly (weekday) recurrence * * @param array $months List of months (integers) this event recurs on. */ function setRecurByMonth($months) { $this->recurMonths = (array)$months; } /** * Returns a list of months this yearly event recurs on * * @return array List of months (integers) this event recurs on. */ function getRecurByMonth() { return $this->recurMonths; } /** * * @param integer $nthDay The nth weekday of month to repeat events on */ function setRecurNthWeekday($nthDay) { $this->recurNthDay = (int)$nthDay; } /** * * @return integer The nth weekday of month to repeat events. */ function getRecurNthWeekday() { return $this->recurNthDay; } /** * Returns whether this event has a specific recurrence type. * * @param integer $recurrence HORDE_DATE_RECUR_* constant of the * recurrence type to check for. * * @return boolean True if the event has the specified recurrence type. */ function hasRecurType($recurrence) { return ($recurrence == $this->recurType); } /** * Sets a recurrence type for this event. * * @param integer $recurrence A HORDE_DATE_RECUR_* constant. */ function setRecurType($recurrence) { $this->recurType = $recurrence; } /** * Returns recurrence type of this event. * * @return integer A HORDE_DATE_RECUR_* constant. */ function getRecurType() { return $this->recurType; } /** * Returns a description of this event's recurring type. * * @return string Human readable recurring type. */ function getRecurName() { switch ($this->getRecurType()) { case HORDE_DATE_RECUR_NONE: return _("No recurrence"); case HORDE_DATE_RECUR_DAILY: return _("Daily"); case HORDE_DATE_RECUR_WEEKLY: return _("Weekly"); case HORDE_DATE_RECUR_MONTHLY_DATE: case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: return _("Monthly"); case HORDE_DATE_RECUR_YEARLY_DATE: case HORDE_DATE_RECUR_YEARLY_DAY: case HORDE_DATE_RECUR_YEARLY_WEEKDAY: return _("Yearly"); } } /** * Sets the length of time between recurrences of this event. * * @param integer $interval The time between recurrences. */ function setRecurInterval($interval) { if ($interval > 0) { $this->recurInterval = $interval; } } /** * Retrieves the length of time between recurrences of this event. * * @return integer The number of seconds between recurrences. */ function getRecurInterval() { return $this->recurInterval; } /** * Sets the number of recurrences of this event. * * @param integer $count The number of recurrences. */ function setRecurCount($count) { if ($count > 0) { $this->recurCount = (int)$count; // Recurrence counts and end dates are mutually exclusive. $this->recurEnd = null; } else { $this->recurCount = null; } } /** * Retrieves the number of recurrences of this event. * * @return integer The number recurrences. */ function getRecurCount() { return $this->recurCount; } /** * Returns whether this event has a recurrence with a fixed count. * * @return boolean True if this recurrence has a fixed count. */ function hasRecurCount() { return isset($this->recurCount); } /** * Sets the start date of the recurrence interval. * * @param Horde_Date $start The recurrence start. */ function setRecurStart($start) { $this->start = new Horde_Date($start); } /** * Retrieves the start date of the recurrence interval. * * @return Horde_Date The recurrence start. */ function getRecurStart() { return $this->start; } /** * Sets the end date of the recurrence interval. * * @param Horde_Date $end The recurrence end. */ function setRecurEnd($end) { if (!empty($end)) { // Recurrence counts and end dates are mutually exclusive. $this->recurCount = null; } $this->recurEnd = new Horde_Date($end); } /** * Retrieves the end date of the recurrence interval. * * @return Horde_Date The recurrence end. */ function getRecurEnd() { return $this->recurEnd; } /** * Returns whether this event has a recurrence end. * * @return boolean True if this recurrence ends. */ function hasRecurEnd() { return isset($this->recurEnd) && isset($this->recurEnd->year) && $this->recurEnd->year != 9999; } /** * Finds the next recurrence of this event that's after $afterDate. * * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date|boolean The date of the next recurrence or false * if the event does not recur after * $afterDate. */ function nextRecurrence($afterDate) { $after = new Horde_Date($afterDate); $after->correct(); if ($this->start->compareDateTime($after) >= 0) { return new Horde_Date($this->start); } if ($this->recurInterval == 0) { return false; } switch ($this->getRecurType()) { case HORDE_DATE_RECUR_DAILY: $diff = Date_Calc::dateDiff($this->start->mday, $this->start->month, $this->start->year, $after->mday, $after->month, $after->year); $recur = ceil($diff / $this->recurInterval); if ($this->recurCount && $recur >= $this->recurCount) { return false; } $recur *= $this->recurInterval; $next = new Horde_Date($this->start); list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); if ((!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) && $next->compareDateTime($after) >= 0) { return new Horde_Date($next); } break; case HORDE_DATE_RECUR_WEEKLY: if (empty($this->recurData)) { return false; } list($start_week->mday, $start_week->month, $start_week->year) = explode('/', Date_Calc::beginOfWeek($this->start->mday, $this->start->month, $this->start->year, '%e/%m/%Y')); $start_week->hour = $this->start->hour; $start_week->min = $this->start->min; $start_week->sec = $this->start->sec; list($after_week->mday, $after_week->month, $after_week->year) = explode('/', Date_Calc::beginOfWeek($after->mday, $after->month, $after->year, '%e/%m/%Y')); $after_week_end = new Horde_Date($after_week); $after_week_end->mday += 7; $after_week_end->correct(); $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, $after_week->mday, $after_week->month, $after_week->year); $interval = $this->recurInterval * 7; $repeats = floor($diff / $interval); if ($diff % $interval < 7) { $recur = $diff; } else { /** * If the after_week is not in the first week interval the * search needs to skip ahead a complete interval. The way it is * calculated here means that an event that occurs every second * week on Monday and Wednesday with the event actually starting * on Tuesday or Wednesday will only have one incidence in the * first week. */ $recur = $interval * ($repeats + 1); } if ($this->hasRecurCount()) { $recurrences = 0; /** * Correct the number of recurrences by the number of events * that lay between the start of the start week and the * recurrence start. */ $next = new Horde_Date($start_week); while ($next->compareDateTime($this->start) < 0) { if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { $recurrences--; } ++$next->mday; $next->correct(); } if ($repeats > 0) { $weekdays = $this->recurData; $total_recurrences_per_week = 0; while ($weekdays > 0) { if ($weekdays % 2) { $total_recurrences_per_week++; } $weekdays = ($weekdays - ($weekdays % 2)) / 2; } $recurrences += $total_recurrences_per_week * $repeats; } } $next = $start_week; list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); $next = new Horde_Date($next); while ($next->compareDateTime($after) < 0 && $next->compareDateTime($after_week_end) < 0) { if ($this->hasRecurCount() && $next->compareDateTime($after) < 0 && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { $recurrences++; } ++$next->mday; $next->correct(); } if ($this->hasRecurCount() && $recurrences >= $this->recurCount) { return false; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) && $next->compareDateTime($after_week_end) < 0) { ++$next->mday; $next->correct(); } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } else { return $next; } } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $start = new Horde_Date($this->start); if ($after->compareDateTime($start) < 0) { $after = $start; } // If we're starting past this month's recurrence of the event, // look in the next month on the day the event recurs. if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; $after->correct(); } // Adjust $start to be the first match. $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; if ($this->recurCount && ($offset / $this->recurInterval) >= $this->recurCount) { return false; } $start->month += $offset; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } // Don't correct for day overflow; we just skip February 30th, // for example. $start->correct(HORDE_DATE_MASK_MONTH); // Bail if we've gone past the end of recurrence. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($start) < 0) { return false; } if ($start->isValid()) { return $start; } // If the interval is 12, and the date isn't valid, then we // need to see if February 29th is an option. If not, then the // event will _never_ recur, and we need to stop checking to // avoid an infinite loop. if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { return false; } // Add the recurrence interval. $start->month += $this->recurInterval; } while (true); break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: // Start with the start date of the event. $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? if ($this->recurNthDay != 0) { $nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // Adjust $estart to be the first candidate. $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->month += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->month += $this->recurInterval; $estart->correct(); $next = new Horde_Date($estart); if ($this->recurNthDay) { list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y')); } else { $next->setNthWeekday($weekday, $nth); } if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; case HORDE_DATE_RECUR_YEARLY_DATE: // Start with the start date of the event. $estart = new Horde_Date($this->start); if ($after->month > $estart->month || ($after->month == $estart->month && $after->mday > $estart->mday)) { ++$after->year; $after->month = $estart->month; $after->mday = $estart->mday; } // Seperate case here for February 29th if ($estart->month == 2 && $estart->mday == 29) { while (!Horde_Date::isLeapYear($after->year)) { ++$after->year; } } // Adjust $estart to be the first candidate. $offset = $after->year - $estart->year; if ($offset > 0) { $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $estart->year += $offset; } // We've gone past the end of recurrence; give up. if ($this->recurCount && $offset >= $this->recurCount) { return false; } if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case HORDE_DATE_RECUR_YEARLY_DAY: // Check count first. $dayofyear = $this->start->dayOfYear(); $count = ($after->year - $this->start->year) / $this->recurInterval + 1; if ($this->recurCount && ($count > $this->recurCount || ($count == $this->recurCount && $after->dayOfYear() > $dayofyear))) { return false; } // Start with a rough interval. $estart = new Horde_Date($this->start); $estart->year += floor($count - 1) * $this->recurInterval; // Now add the difference to the required day of year. $estart->mday += $dayofyear - $estart->dayOfYear(); $estart->correct(); // Add an interval if the estimation was wrong. if ($estart->compareDate($after) < 0) { $estart->year += $this->recurInterval; $estart->mday += $dayofyear - $estart->dayOfYear(); $estart->correct(); } // We've gone past the end of recurrence; give up. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: // Start with the start date of the event. $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? if ($this->recurNthDay != 0) { $nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // set month from recurrence rule (FIXME: support more than one month) if ($this->recurMonths) { $estart->month = $this->recurMonths[0]; } // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->year += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->year += $this->recurInterval; $estart->correct(); $next = new Horde_Date($estart); if ($this->recurNthDay) { list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y')); } else { $next->setNthWeekday($weekday, $nth); } if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; } // We didn't find anything, the recurType was bad, or something else // went wrong - return false. return false; } /** * Returns whether this event has any date that matches the recurrence * rules and is not an exception. * * @return boolean True if an active recurrence exists. */ function hasActiveRecurrence() { if (!$this->hasRecurEnd()) { return true; } $next = $this->nextRecurrence(new Horde_Date($this->start)); while (is_object($next)) { if (!$this->hasException($next->year, $next->month, $next->mday) && !$this->hasCompletion($next->year, $next->month, $next->mday)) { return true; } $next = $this->nextRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec)); } return false; } /** * Returns the next active recurrence. * * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date|boolean The date of the next active * recurrence or false if the event * has no active recurrence after * $afterDate. */ function nextActiveRecurrence($afterDate) { $next = $this->nextRecurrence($afterDate); while (is_object($next)) { if (!$this->hasException($next->year, $next->month, $next->mday) && !$this->hasCompletion($next->year, $next->month, $next->mday)) { return $next; } $next->mday++; $next = $this->nextRecurrence($next); } return false; } /** * Adds an exception to a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ function addException($year, $month, $mday) { $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } /** * Deletes an exception from a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ function deleteException($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions); if ($key !== false) { unset($this->exceptions[$key]); } } /** * Checks if an exception exists for a given reccurence of an event. * * @param integer $year The year of the reucrance. * @param integer $month The month of the reucrance. * @param integer $mday The day of the month of the reucrance. * * @return boolean True if an exception exists for the given date. */ function hasException($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getExceptions()); } /** * Retrieves all the exceptions for this event. * * @return array Array containing the dates of all the exceptions in * YYYYMMDD form. */ function getExceptions() { return $this->exceptions; } /** * Adds a completion to a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ function addCompletion($year, $month, $mday) { $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } /** * Deletes a completion from a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ function deleteCompletion($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions); if ($key !== false) { unset($this->completions[$key]); } } /** * Checks if a completion exists for a given reccurence of an event. * * @param integer $year The year of the reucrance. * @param integer $month The month of the recurrance. * @param integer $mday The day of the month of the recurrance. * * @return boolean True if a completion exists for the given date. */ function hasCompletion($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getCompletions()); } /** * Retrieves all the completions for this event. * * @return array Array containing the dates of all the completions in * YYYYMMDD form. */ function getCompletions() { return $this->completions; } /** * Parses a vCalendar 1.0 recurrence rule. * * @link http://www.imc.org/pdi/vcal-10.txt * @link http://www.shuchow.com/vCalAddendum.html * * @param string $rrule A vCalendar 1.0 conform RRULE value. */ function fromRRule10($rrule) { if (!$rrule) { return; } if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) { // No recurrence data - event does not recur. $this->setRecurType(HORDE_DATE_RECUR_NONE); } // Always default the recurInterval to 1. $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1); $remainder = trim($matches[3]); switch ($matches[1]) { case 'D': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'W': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); if (!empty($remainder)) { $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, 'MO' => HORDE_DATE_MASK_MONDAY, 'TU' => HORDE_DATE_MASK_TUESDAY, 'WE' => HORDE_DATE_MASK_WEDNESDAY, 'TH' => HORDE_DATE_MASK_THURSDAY, 'FR' => HORDE_DATE_MASK_FRIDAY, 'SA' => HORDE_DATE_MASK_SATURDAY); $mask = 0; while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) { $day = trim($matches[0]); $remainder = substr($remainder, strlen($matches[0])); $mask |= $maskdays[$day]; } $this->setRecurOnDay($mask); } else { // Recur on the day of the week of the original recurrence. $maskdays = array(HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MP': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); break; case 'MD': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); break; case 'YM': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); break; case 'YD': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); break; } // We don't support modifiers at the moment, strip them. while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) { $remainder = substr($remainder, 1); } if (!empty($remainder)) { if (strpos($remainder, '#') === 0) { $this->setRecurCount(substr($remainder, 1)); } else { list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday))); } } } /** * Creates a vCalendar 1.0 recurrence rule. * * @link http://www.imc.org/pdi/vcal-10.txt * @link http://www.shuchow.com/vCalAddendum.html * * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. * * @return string A vCalendar 1.0 conform RRULE value. */ function toRRule10($calendar) { switch ($this->recurType) { case HORDE_DATE_RECUR_NONE: return ''; case HORDE_DATE_RECUR_DAILY: $rrule = 'D' . $this->recurInterval; break; case HORDE_DATE_RECUR_WEEKLY: $rrule = 'W' . $this->recurInterval; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); for ($i = 0; $i <= 7 ; ++$i) { if ($this->recurOnDay(pow(2, $i))) { $rrule .= ' ' . $vcaldays[$i]; } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()]; break; case HORDE_DATE_RECUR_YEARLY_DATE: $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); break; case HORDE_DATE_RECUR_YEARLY_DAY: $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); break; default: return ''; } if ($this->hasRecurEnd()) { $recurEnd = new Horde_Date($this->recurEnd); $recurEnd->mday++; return $rrule . ' ' . $calendar->_exportDateTime($recurEnd); } return $rrule . ' #' . (int)$this->getRecurCount(); } /** * Parses an iCalendar 2.0 recurrence rule. * * @link http://rfc.net/rfc2445.html#s4.3.10 * @link http://rfc.net/rfc2445.html#s4.8.5 * @link http://www.shuchow.com/vCalAddendum.html * * @param string $rrule An iCalendar 2.0 conform RRULE value. */ function fromRRule20($rrule) { // Parse the recurrence rule into keys and values. $rdata = array(); $parts = explode(';', $rrule); foreach ($parts as $part) { list($key, $value) = explode('=', $part, 2); $rdata[strtoupper($key)] = $value; } if (isset($rdata['FREQ'])) { // Always default the recurInterval to 1. $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, 'MO' => HORDE_DATE_MASK_MONDAY, 'TU' => HORDE_DATE_MASK_TUESDAY, 'WE' => HORDE_DATE_MASK_WEDNESDAY, 'TH' => HORDE_DATE_MASK_THURSDAY, 'FR' => HORDE_DATE_MASK_FRIDAY, 'SA' => HORDE_DATE_MASK_SATURDAY); switch (strtoupper($rdata['FREQ'])) { case 'DAILY': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'WEEKLY': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); if (isset($rdata['BYDAY'])) { $days = explode(',', $rdata['BYDAY']); $mask = 0; foreach ($days as $day) { $mask |= $maskdays[$day]; } $this->setRecurOnDay($mask); } else { // Recur on the day of the week of the original // recurrence. $maskdays = array( HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MONTHLY': if (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { $this->setRecurOnDay($maskdays[$m[2]]); $this->setRecurNthWeekday($m[1]); } } else { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); } break; case 'YEARLY': if (isset($rdata['BYYEARDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); } elseif (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { $this->setRecurOnDay($maskdays[$m[2]]); $this->setRecurNthWeekday($m[1]); } if ($rdata['BYMONTH']) { $months = explode(',', $rdata['BYMONTH']); $this->setRecurByMonth($months); } } else { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); } break; } if (isset($rdata['UNTIL'])) { list($year, $month, $mday) = sscanf($rdata['UNTIL'], '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday))); } if (isset($rdata['COUNT'])) { $this->setRecurCount($rdata['COUNT']); } } else { // No recurrence data - event does not recur. $this->setRecurType(HORDE_DATE_RECUR_NONE); } } /** * Creates an iCalendar 2.0 recurrence rule. * * @link http://rfc.net/rfc2445.html#s4.3.10 * @link http://rfc.net/rfc2445.html#s4.8.5 * @link http://www.shuchow.com/vCalAddendum.html * * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. * * @return string An iCalendar 2.0 conform RRULE value. */ function toRRule20($calendar) { switch ($this->recurType) { case HORDE_DATE_RECUR_NONE: return ''; case HORDE_DATE_RECUR_DAILY: $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_WEEKLY: $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY='; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); for ($i = $flag = 0; $i <= 7 ; ++$i) { if ($this->recurOnDay(pow(2, $i))) { if ($flag) { $rrule .= ','; } $rrule .= $vcaldays[$i]; $flag = true; } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: if ($this->recurNthDay != 0) { $nth_weekday = $this->recurNthDay; $day_of_week = log($this->recurData, 2); } else { $day_of_week = $this->start->dayOfWeek(); $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; break; case HORDE_DATE_RECUR_YEARLY_DATE: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_YEARLY_DAY: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYYEARDAY=' . $this->start->dayOfYear(); break; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: if ($this->recurNthDay != 0) { $nth_weekday = $this->recurNthDay; $day_of_week = log($this->recurData, 2); } else { $day_of_week = $this->start->dayOfWeek(); $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } } $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week] . ';BYMONTH=' . $months; break; } if ($this->hasRecurEnd()) { $recurEnd = new Horde_Date($this->recurEnd); $recurEnd->mday++; $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd); } if ($count = $this->getRecurCount()) { $rrule .= ';COUNT=' . $count; } return $rrule; } /** * Parses the recurrence data from a hash. * * @param array $hash The hash to convert. * * @return boolean True if the hash seemed valid, false otherwise. */ function fromHash($hash) { if (!isset($hash['interval']) || !isset($hash['interval']) || !isset($hash['range-type'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $month2number = array( 'january' => 1, 'february' => 2, 'march' => 3, 'april' => 4, 'may' => 5, 'june' => 6, 'july' => 7, 'august' => 8, 'september' => 9, 'october' => 10, 'november' => 11, 'december' => 12, ); $this->setRecurInterval((int) $hash['interval']); $parse_day = false; $set_daymask = false; $update_month = false; $update_daynumber = false; $update_weekday = false; $nth_weekday = -1; switch ($hash['cycle']) { case 'daily': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'weekly': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); $parse_day = true; $set_daymask = true; break; case 'monthly': if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } switch ($hash['type']) { case 'daynumber': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); $update_daynumber = true; break; case 'weekday': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); $this->setRecurNthWeekday($hash['daynumber']); $parse_day = true; $set_daymask = true; break; } break; case 'yearly': if (!isset($hash['type'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } switch ($hash['type']) { case 'monthday': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); $update_month = true; $update_daynumber = true; break; case 'yearday': if (!isset($hash['month'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); // Start counting days in January. $hash['month'] = 'january'; $update_month = true; $update_daynumber = true; break; case 'weekday': if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); $this->setRecurNthWeekday($hash['daynumber']); $parse_day = true; $set_daymask = true; if ($hash['month'] && isset($month2number[$hash['month']])) { $this->setRecurByMonth($month2number[$hash['month']]); } break; } } switch ($hash['range-type']) { case 'number': if (!isset($hash['range'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurCount((int) $hash['range']); break; case 'date': $recur_end = new Horde_Date($hash['range']); $recur_end->hour = 23; $recur_end->min = 59; $recur_end->sec = 59; $this->setRecurEnd($recur_end); break; } // Need to parse ? $last_found_day = -1; if ($parse_day) { if (!isset($hash['day'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $mask = 0; $bits = array( 'monday' => HORDE_DATE_MASK_MONDAY, 'tuesday' => HORDE_DATE_MASK_TUESDAY, 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, 'thursday' => HORDE_DATE_MASK_THURSDAY, 'friday' => HORDE_DATE_MASK_FRIDAY, 'saturday' => HORDE_DATE_MASK_SATURDAY, 'sunday' => HORDE_DATE_MASK_SUNDAY, ); $days = array( 'monday' => HORDE_DATE_MONDAY, 'tuesday' => HORDE_DATE_TUESDAY, 'wednesday' => HORDE_DATE_WEDNESDAY, 'thursday' => HORDE_DATE_THURSDAY, 'friday' => HORDE_DATE_FRIDAY, 'saturday' => HORDE_DATE_SATURDAY, 'sunday' => HORDE_DATE_SUNDAY, ); foreach ($hash['day'] as $day) { // Validity check. if (empty($day) || !isset($bits[$day])) { continue; } $mask |= $bits[$day]; $last_found_day = $days[$day]; } if ($set_daymask) { $this->setRecurOnDay($mask); } } if ($update_month || $update_daynumber || $update_weekday) { if ($update_month) { if (isset($month2number[$hash['month']])) { $this->start->month = $month2number[$hash['month']]; } } if ($update_daynumber) { if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->start->mday = $hash['daynumber']; } if ($update_weekday) { $this->start->setNthWeekday($last_found_day, $nth_weekday); } $this->start->correct(); } // Exceptions. if (isset($hash['exceptions'])) { $this->exceptions = $hash['exceptions']; } if (isset($hash['completions'])) { $this->completions = $hash['completions']; } return true; } /** * Export this object into a hash. * * @return array The recurrence hash. */ function toHash() { if ($this->getRecurType() == HORDE_DATE_RECUR_NONE) { return array(); } $day2number = array( 0 => 'sunday', 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday' ); $month2number = array( 1 => 'january', 2 => 'february', 3 => 'march', 4 => 'april', 5 => 'may', 6 => 'june', 7 => 'july', 8 => 'august', 9 => 'september', 10 => 'october', 11 => 'november', 12 => 'december' ); $hash = array('interval' => $this->getRecurInterval()); $start = $this->getRecurStart(); switch ($this->getRecurType()) { case HORDE_DATE_RECUR_DAILY: $hash['cycle'] = 'daily'; break; case HORDE_DATE_RECUR_WEEKLY: $hash['cycle'] = 'weekly'; $bits = array( 'monday' => HORDE_DATE_MASK_MONDAY, 'tuesday' => HORDE_DATE_MASK_TUESDAY, 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, 'thursday' => HORDE_DATE_MASK_THURSDAY, 'friday' => HORDE_DATE_MASK_FRIDAY, 'saturday' => HORDE_DATE_MASK_SATURDAY, 'sunday' => HORDE_DATE_MASK_SUNDAY, ); $days = array(); foreach($bits as $name => $bit) { if ($this->recurOnDay($bit)) { $days[] = $name; } } $hash['day'] = $days; break; case HORDE_DATE_RECUR_MONTHLY_DATE: $hash['cycle'] = 'monthly'; $hash['type'] = 'daynumber'; $hash['daynumber'] = $start->mday; break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: $hash['cycle'] = 'monthly'; $hash['type'] = 'weekday'; $hash['daynumber'] = $start->weekOfMonth(); $hash['day'] = array ($day2number[$start->dayOfWeek()]); break; case HORDE_DATE_RECUR_YEARLY_DATE: $hash['cycle'] = 'yearly'; $hash['type'] = 'monthday'; $hash['daynumber'] = $start->mday; $hash['month'] = $month2number[$start->month]; break; case HORDE_DATE_RECUR_YEARLY_DAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'yearday'; $hash['daynumber'] = $start->dayOfYear(); break; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'weekday'; $hash['daynumber'] = $start->weekOfMonth(); $hash['day'] = array ($day2number[$start->dayOfWeek()]); $hash['month'] = $month2number[$start->month]; } if ($this->hasRecurCount()) { $hash['range-type'] = 'number'; $hash['range'] = $this->getRecurCount(); } elseif ($this->hasRecurEnd()) { $date = $this->getRecurEnd(); $hash['range-type'] = 'date'; $hash['range'] = $date->datestamp(); } else { $hash['range-type'] = 'none'; $hash['range'] = ''; } // Recurrence exceptions $hash['exceptions'] = $this->exceptions; $hash['completions'] = $this->completions; return $hash; } }