_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; } } }