From: Chuck Hagenbuch Date: Fri, 29 May 2009 22:24:49 +0000 (-0400) Subject: initial horde/date package in git X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=f3baae109f1449d30e0061e3861ceb3e0feb1a90;p=horde.git initial horde/date package in git --- diff --git a/framework/Date/lib/Horde/Date.php b/framework/Date/lib/Horde/Date.php new file mode 100644 index 000000000..564a42633 --- /dev/null +++ b/framework/Date/lib/Horde/Date.php @@ -0,0 +1,963 @@ + self::MASK_YEAR, + 'month' => self::MASK_MONTH, + 'mday' => self::MASK_DAY, + 'hour' => self::MASK_HOUR, + 'min' => self::MASK_MINUTE, + 'sec' => self::MASK_SECOND, + ); + + var $_formatCache = array(); + + /** + * Build a new date object. If $date contains date parts, use them to + * initialize the object. + * + * Recognized formats: + * - arrays with keys 'year', 'month', 'mday', 'day' + * 'hour', 'min', 'minute', 'sec' + * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec' + * - yyyy-mm-dd hh:mm:ss + * - yyyymmddhhmmss + * - yyyymmddThhmmssZ + * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and + * 03 Mar 1973) + * - unix timestamps + */ + function __construct($date = null, $timezone = null) + { + if (!self::$_supportedSpecs) { + self::$_supportedSpecs = self::$_defaultSpecs; + if (function_exists('nl_langinfo')) { self::$_supportedSpecs .= 'bBpxX'; } + } + + if (func_num_args() > 2) { + $args = func_get_args(); + // Handle args in order: year month day hour min sec tz + $this->_initializeFromArgs(func_get_args()); + return; + } + + $this->_initializeTimezone($timezone); + + if (is_null($date)) { + return; + } + + if (is_string($date)) { + $date = trim($date, '"'); + } + + if (is_object($date)) { + $this->_initializeFromObject($date); + } elseif (is_array($date)) { + $this->_initializeFromArray($date); + } elseif (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(Z?)/', $date, $parts) && + empty($parts[7])) { + $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]; + } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) && + $parts[2] > 0 && $parts[2] <= 12 && + $parts[3] > 0 && $parts[3] <= 31) { + $this->_year = (int)$parts[1]; + $this->_month = (int)$parts[2]; + $this->_mday = (int)$parts[3]; + $this->_hour = $this->_min = $this->_sec = 0; + } elseif ((string)(int)$date == $date) { + // 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']; + } + } else { + try { + $date = new DateTime($date); + $date->setTimezone(new DateTimeZone(date_default_timezone_get())); + $this->_year = (int)$date->format('Y'); + $this->_month = (int)$date->format('m'); + $this->_mday = (int)$date->format('d'); + $this->_hour = (int)$date->format('H'); + $this->_min = (int)$date->format('i'); + $this->_sec = (int)$date->format('s'); + } catch (Exception $e) {} + } + } + + /** + * Returns a simple string representation of the date object + * + * @return string This object converted to a string. + */ + function __toString() + { + try { + return $this->format($this->_defaultFormat); + } catch (Exception $e) { + return ''; + } + } + + /** + * Returns a DateTime object representing this object. + * + * @return DateTime + */ + function toDateTime() + { + $date = new DateTime(null, new DateTimeZone($this->_timezone)); + $date->setDate($this->_year, $this->_month, $this->_mday); + $date->setTime($this->_hour, $this->_min, $this->_sec); + return $date; + } + + /** + * Getter for the date and time properties. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * + * @return integer The property value, or null if not set. + */ + function __get($name) + { + if ($name == 'day') { + $name = 'mday'; + } + + return $this->{'_' . $name}; + } + + /** + * Setter for the date and time properties. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * @param integer $value The property value. + */ + function __set($name, $value) + { + if ($name == 'day') { + $name = 'mday'; + } + + if ($name != 'year' && $name != 'month' && $name != 'mday' && + $name != 'hour' && $name != 'min' && $name != 'sec') { + throw new InvalidArgumentException('Undefined property ' . $name); + } + + $this->{'_' . $name} = $value; + $this->_correct(self::$_corrections[$name]); + $this->_formatCache = array(); + } + + /** + * Returns whether a date or time property exists. + * + * @param string $name One of 'year', 'month', 'mday', 'hour', 'min' or + * 'sec'. + * + * @return boolen True if the property exists and is set. + */ + function __isset($name) + { + if ($name == 'day') { + $name = 'mday'; + } + return ($name == 'year' || $name == 'month' || $name == 'mday' || + $name == 'hour' || $name == 'min' || $name == 'sec') && + isset($this->{'_' . $name}); + } + + /** + * Add a number of seconds or units to this date, returning a new Date + * object. + */ + public function add($factor) + { + $d = clone($this); + if (is_array($factor)) { + foreach ($factor as $property => $value) { + $d->$property += $value; + } + } else { + $d->sec += $factor; + } + return $d; + } + + /** + * Subtract a number of seconds or units from this date, returning a new + * Horde_Date object. + */ + public function sub($factor) + { + if (is_array($factor)) { + foreach ($factor as &$value) { + $value *= -1; + } + } else { + $factor *= -1; + } + + return $this->add($factor); + } + + /** + * Converts this object to a different timezone. + * + * @param string $timezone The new timezone. + */ + public function setTimezone($timezone) + { + $date = $this->toDateTime(); + $date->setTimezone(new DateTimeZone($timezone)); + $this->_timezone = $timezone; + $this->_year = (int)$date->format('Y'); + $this->_month = (int)$date->format('m'); + $this->_mday = (int)$date->format('d'); + $this->_hour = (int)$date->format('H'); + $this->_min = (int)$date->format('i'); + $this->_sec = (int)$date->format('s'); + $this->_formatCache = array(); + } + + /** + * Set the default date format used in __toString() + * + * @param string $format + */ + public function setDefaultFormat($format) + { + $this->_defaultFormat = $format; + } + + /** + * Returns whether a year is a leap year. + * + * @static + * + * @param integer $year The year. + * + * @return boolan True if the year is a leap year. + */ + function isLeapYear($year) + { + if (strlen($year) != 4 || preg_match('/\D/', $year)) { + return false; + } + + return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); + } + + /** + * Returns the date of the year that corresponds to the first day of the + * given week. + * + * @param integer $week The week of the year to find the first day of. + * @param integer $year The year to calculate for. + * + * @return Horde_Date The date of the first day of the given week. + */ + function firstDayOfWeek($week, $year) + { + return new Horde_Date(sprintf('%04dW%02d', $year, $week)); + } + + /** + * Returns the number of days in the specified month. + * + * @static + * + * @param integer $month The month + * @param integer $year The year. + * + * @return integer The number of days in the month. + */ + function daysInMonth($month, $year) + { + static $cache = array(); + if (!isset($cache[$year][$month])) { + $date = new DateTime(sprintf('%04d-%02d-01', $year, $month)); + $cache[$year][$month] = $date->format('t'); + } + return $cache[$year][$month]; + } + + /** + * Returns the day of the week (0 = Sunday, 6 = Saturday) of this 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() + { + return $this->format('z') + 1; + } + + /** + * Returns the week of the month. + * + * @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. + $date = new Horde_Date($year . '-12-31'); + while ($date->dayOfWeek() != self::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 < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) { + return; + } + + $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(self::MASK_DAY); + } + + /** + * Is the date currently represented by this object a valid date? + * + * @return boolean Validity, counting leap years, etc. + */ + function isValid() + { + return ($this->_year >= 0 && $this->_year <= 9999); + } + + /** + * Correct any over- or underflows in any of the date's members. + * + * @param integer $mask We may not want to correct some overflows. + */ + protected function _correct($mask = self::MASK_ALLPARTS) + { + if ($mask & self::MASK_SECOND) { + if ($this->_sec < 0 || $this->_sec > 59) { + $mask |= self::MASK_MINUTE; + + $this->_min += (int)($this->_sec / 60); + $this->_sec %= 60; + if ($this->_sec < 0) { + $this->_min--; + $this->_sec += 60; + } + } + } + + if ($mask & self::MASK_MINUTE) { + if ($this->_min < 0 || $this->_min > 59) { + $mask |= self::MASK_HOUR; + + $this->_hour += (int)($this->_min / 60); + $this->_min %= 60; + if ($this->_min < 0) { + $this->_hour--; + $this->_min += 60; + } + } + } + + if ($mask & self::MASK_HOUR) { + if ($this->_hour < 0 || $this->_hour > 23) { + $mask |= self::MASK_DAY; + + $this->_mday += (int)($this->_hour / 24); + $this->_hour %= 24; + if ($this->_hour < 0) { + $this->_mday--; + $this->_hour += 24; + } + } + } + + if ($mask & self::MASK_MONTH) { + $this->_year += (int)($this->_month / 12); + $this->_month %= 12; + if ($this->_month < 1) { + $this->_year--; + $this->_month += 12; + } + } + + if ($mask & self::MASK_DAY) { + while ($this->_mday > 28 && + $this->_mday > Horde_Date::daysInMonth($this->_month, $this->_year)) { + $this->_mday -= Horde_Date::daysInMonth($this->_month, $this->_year); + ++$this->_month; + $this->_correct(self::MASK_MONTH); + } + while ($this->_mday < 1) { + --$this->_month; + $this->_correct(self::MASK_MONTH); + $this->_mday += Horde_Date::daysInMonth($this->_month, $this->_year); + } + } + } + + /** + * Compare this date to another date object to see which one is + * greater (later). Assumes that the dates are in the same + * timezone. + * + * @param mixed $other The date to compare to. + * + * @return integer == 0 if they are on the same date + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) + */ + function compareDate($other) + { + if (!is_a($other, 'Horde_Date')) { + $other = new Horde_Date($other); + } + + if ($this->_year != $other->year) { + return $this->_year - $other->year; + } + if ($this->_month != $other->month) { + return $this->_month - $other->month; + } + + return $this->_mday - $other->mday; + } + + /** + * Returns whether this date is after the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is after the other. + */ + function after($other) + { + return $this->compareDate($other) > 0; + } + + /** + * Returns whether this date is before the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is before the other. + */ + function before($other) + { + return $this->compareDate($other) < 0; + } + + /** + * Returns whether this date is the same like the other. + * + * @param mixed $other The date to compare to. + * + * @return boolean True if this date is the same like the other. + */ + function equals($other) + { + return $this->compareDate($other) == 0; + } + + /** + * 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 $other The date to compare to. + * + * @return integer == 0 if they are at the same time + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) + */ + function compareTime($other) + { + if (!is_a($other, 'Horde_Date')) { + $other = new Horde_Date($other); + } + + if ($this->_hour != $other->hour) { + return $this->_hour - $other->hour; + } + if ($this->_min != $other->min) { + return $this->_min - $other->min; + } + + return $this->_sec - $other->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 $other The date to compare to. + * + * @return integer == 0 if they are equal + * >= 1 if $this is greater (later) + * <= -1 if $other is greater (later) + */ + function compareDateTime($other) + { + if (!is_a($other, 'Horde_Date')) { + $other = new Horde_Date($other); + } + + if ($diff = $this->compareDate($other)) { + return $diff; + } + + return $this->compareTime($other); + } + + /** + * 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) + { + return $colon ? $this->format('P') : $this->format('O'); + } + + /** + * Return the unix timestamp representation of this date. + * + * @return integer A unix timestamp. + */ + function timestamp() + { + if ($this->_year >= 1970 && $this->_year < 2038) { + return mktime($this->_hour, $this->_min, $this->_sec, + $this->_month, $this->_mday, $this->_year); + } + return $this->format('U'); + } + + /** + * Return the unix timestamp representation of this date, 12:00am. + * + * @return integer A unix timestamp. + */ + function datestamp() + { + if ($this->_year >= 1970 && $this->_year < 2038) { + return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year); + } + $date = new DateTime($this->format('Y-m-d')); + return $date->format('U'); + } + + /** + * Format date and time to be passed around as a short url parameter. + * + * @return string Date and time. + */ + function dateString() + { + return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday); + } + + /** + * Format date and time to the ISO format used by JSON. + * + * @return string Date and time. + */ + function toJson() + { + return $this->format(self::DATE_JSON); + } + + /** + * Format time using the specifiers available in date() or in the DateTime + * class' format() method. + * + * @param string $format + * + * @return string Formatted time. + */ + function format($format) + { + if (!isset($this->_formatCache[$format])) { + $this->_formatCache[$format] = $this->toDateTime()->format($format); + } + return $this->_formatCache[$format]; + } + + /** + * Format date and time using strftime() format. + * + * @return string strftime() formatted date and time. + */ + function strftime($format) + { + if (preg_match('/%[^' . self::$_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); + } + + /** + * Handle args in order: year month day hour min sec tz + */ + protected function _initializeFromArgs($args) + { + $tz = (isset($args[6])) ? array_pop($args) : null; + $this->_initializeTimezone($tz); + + $args = array_slice($args, 0, 6); + $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0); + $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args); + $date = array_merge($keys, $date); + + $this->_initializeFromArray($date); + } + + protected function _initializeFromArray($date) + { + if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) { + if ($date['year'] > 70) { + $date['year'] += 1900; + } else { + $date['year'] += 2000; + } + } + + 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 (isset($date['day']) && + (string)(int)$date['day'] == $date['day']) { + $this->_mday = (int)$date['day']; + } + // 'minute' key also from Horde_Form_datetime + if (isset($date['minute']) && + (string)(int)$date['minute'] == $date['minute']) { + $this->_min = (int)$date['minute']; + } + $this->_correct(); + } + + protected function _initializeFromObject($date) + { + if ($date instanceof DateTime) { + $this->_year = (int)$date->format('Y'); + $this->_month = (int)$date->format('m'); + $this->_mday = (int)$date->format('d'); + $this->_hour = (int)$date->format('H'); + $this->_min = (int)$date->format('i'); + $this->_sec = (int)$date->format('s'); + } else { + $is_horde_date = $date instanceof Horde_Date; + foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) { + if ($is_horde_date || isset($date->$key)) { + $this->{'_' . $key} = (int)$date->$key; + } + } + if (!$is_horde_date) { + $this->_correct(); + } + } + } + + protected function _initializeTimezone($timezone) + { + if (empty($timezone)) { + $timezone = date_default_timezone_get(); + } + $this->_timezone = $timezone; + } + +} diff --git a/framework/Date/lib/Horde/Date/Recurrence.php b/framework/Date/lib/Horde/Date/Recurrence.php new file mode 100644 index 000000000..fcee1f6f3 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Recurrence.php @@ -0,0 +1,1475 @@ + + * @since Horde 3.2 + * @category Horde + * @package Horde_Date + */ +class Horde_Date_Recurrence +{ + /** No Recurrence **/ + const RECUR_NONE = 0; + + /** Recurs daily. */ + const RECUR_DAILY = 1; + + /** Recurs weekly. */ + const RECUR_WEEKLY = 2; + + /** Recurs monthly on the same date. */ + const RECUR_MONTHLY_DATE = 3; + + /** Recurs monthly on the same week day. */ + const RECUR_MONTHLY_WEEKDAY = 4; + + /** Recurs yearly on the same date. */ + const RECUR_YEARLY_DATE = 5; + + /** Recurs yearly on the same day of the year. */ + const RECUR_YEARLY_DAY = 6; + + /** Recurs yearly on the same week day. */ + const RECUR_YEARLY_WEEKDAY = 7; + + /** + * 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. RECUR_* constant. + * + * @var integer + */ + var $recurType = self::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; + + /** + * 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 __construct($start) + { + $this->start = is_a($start, 'Horde_Date') ? clone $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; + } + + /** + * Returns whether this event has a specific recurrence type. + * + * @param integer $recurrence 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 RECUR_* constant. + */ + function setRecurType($recurrence) + { + $this->recurType = $recurrence; + } + + /** + * Returns recurrence type of this event. + * + * @return integer A 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 self::RECUR_NONE: return _("No recurrence"); + case self::RECUR_DAILY: return _("Daily"); + case self::RECUR_WEEKLY: return _("Weekly"); + case self::RECUR_MONTHLY_DATE: + case self::RECUR_MONTHLY_WEEKDAY: return _("Monthly"); + case self::RECUR_YEARLY_DATE: + case self::RECUR_YEARLY_DAY: + case self::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 = clone $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 = clone $end; + } else { + $this->recurEnd = $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 $after 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($after) + { + if (!is_a($after, 'Horde_Date')) { + $after = new Horde_Date($after); + } + + if ($this->start->compareDateTime($after) >= 0) { + return clone $this->start; + } + + if ($this->recurInterval == 0) { + return false; + } + + switch ($this->getRecurType()) { + case self::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 = clone $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 $next; + } + break; + + case self::RECUR_WEEKLY: + if (empty($this->recurData)) { + return false; + } + + $start_week = Horde_Date::firstDayOfWeek($this->start->format('W'), + $this->start->year); + $start_week->hour = $this->start->hour; + $start_week->min = $this->start->min; + $start_week->sec = $this->start->sec; + + // Make sure we are not at the ISO-8601 first week of year while + // still in month 12...and adjust the year ahead if we are. + $week = $after->format('W'); + if ($week == 1 && $after->month == 12) { + $theYear = $after->year + 1; + } else { + $theYear = $after->year; + } + $after_week = Horde_Date::firstDayOfWeek($week, $theYear); + $after_week_end = clone $after_week; + $after_week_end->mday += 7; + + $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, + $after_week->mday, $after_week->month, $after_week->year); + $recur = $diff + ($diff % ($this->recurInterval * 7)); + if ($this->recurCount && + ceil($recur / 7) / $this->recurInterval >= $this->recurCount) { + return false; + } + $next = clone $start_week; + $next->mday += $recur; + while ($next->compareDateTime($after) < 0 && + $next->compareDateTime($after_week_end) < 0) { + ++$next->mday; + } + 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; + } + 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 self::RECUR_MONTHLY_DATE: + $start = clone $this->start; + if ($after->compareDateTime($start) < 0) { + $after = clone $start; + } else { + $after = clone $after; + } + + // 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; + } + + // 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; + } + + // 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 self::RECUR_MONTHLY_WEEKDAY: + // Start with the start date of the event. + $estart = clone $this->start; + + // What day of the week, and week of the month, do we recur on? + $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; + + $next = clone $estart; + $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 self::RECUR_YEARLY_DATE: + // Start with the start date of the event. + $estart = clone $this->start; + $after = clone $after; + + 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 self::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 = clone $this->start; + $estart->year += floor($count - 1) * $this->recurInterval; + + // Now add the difference to the required day of year. + $estart->mday += $dayofyear - $estart->dayOfYear(); + + // Add an interval if the estimation was wrong. + if ($estart->compareDate($after) < 0) { + $estart->year += $this->recurInterval; + $estart->mday += $dayofyear - $estart->dayOfYear(); + } + + // We've gone past the end of recurrence; give up. + if ($this->hasRecurEnd() && + $this->recurEnd->compareDateTime($estart) < 0) { + return false; + } + + return $estart; + + case self::RECUR_YEARLY_WEEKDAY: + // Start with the start date of the event. + $estart = clone $this->start; + + // What day of the week, and week of the month, do we recur on? + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); + + // 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; + + $next = clone $estart; + $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(self::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(self::RECUR_DAILY); + break; + + case 'W': + $this->setRecurType(self::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::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, + Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, + Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, + Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, + Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, + Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, + Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY); + $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); + } + break; + + case 'MP': + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); + break; + + case 'MD': + $this->setRecurType(self::RECUR_MONTHLY_DATE); + break; + + case 'YM': + $this->setRecurType(self::RECUR_YEARLY_DATE); + break; + + case 'YD': + $this->setRecurType(self::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, '#') !== false) { + $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 self::RECUR_NONE: + return ''; + + case self::RECUR_DAILY: + $rrule = 'D' . $this->recurInterval; + break; + + case self::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 self::RECUR_MONTHLY_DATE: + $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); + break; + + case self::RECUR_MONTHLY_WEEKDAY: + $next_week = new Horde_Date($this->start); + $next_week->mday += 7; + + if ($this->start->month != $next_week->month) { + $p = 5; + } else { + $p = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $p++; + } + } + + $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $rrule = 'MP' . $this->recurInterval . ' ' . $p . '+ ' . $vcaldays[$this->start->dayOfWeek()]; + break; + + case self::RECUR_YEARLY_DATE: + $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); + break; + + case self::RECUR_YEARLY_DAY: + $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); + break; + + default: + return ''; + } + + if ($this->hasRecurEnd()) { + $recurEnd = clone $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[String::upper($key)] = $value; + } + + if (isset($rdata['FREQ'])) { + // Always default the recurInterval to 1. + $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); + + switch (String::upper($rdata['FREQ'])) { + case 'DAILY': + $this->setRecurType(self::RECUR_DAILY); + break; + + case 'WEEKLY': + $this->setRecurType(self::RECUR_WEEKLY); + if (isset($rdata['BYDAY'])) { + $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); + $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::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, + Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, + Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, + Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, + Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, + Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, + Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY); + $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); + } + break; + + case 'MONTHLY': + if (isset($rdata['BYDAY'])) { + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); + } else { + $this->setRecurType(self::RECUR_MONTHLY_DATE); + } + break; + + case 'YEARLY': + if (isset($rdata['BYYEARDAY'])) { + $this->setRecurType(self::RECUR_YEARLY_DAY); + } elseif (isset($rdata['BYDAY'])) { + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); + } else { + $this->setRecurType(self::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(self::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 self::RECUR_NONE: + return ''; + + case self::RECUR_DAILY: + $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; + break; + + case self::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 self::RECUR_MONTHLY_DATE: + $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; + break; + + case self::RECUR_MONTHLY_WEEKDAY: + $next_week = new Horde_Date($this->start); + $next_week->mday += 7; + if ($this->start->month != $next_week->month) { + $p = 5; + } else { + $p = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $p++; + } + } + $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval + . ';BYDAY=' . $p . $vcaldays[$this->start->dayOfWeek()]; + break; + + case self::RECUR_YEARLY_DATE: + $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; + break; + + case self::RECUR_YEARLY_DAY: + $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval + . ';BYYEARDAY=' . $this->start->dayOfYear(); + break; + + case self::RECUR_YEARLY_WEEKDAY: + $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $weekday = new Horde_Date(array('month' => $this->start->month, + 'mday' => 1, + 'year' => $this->start->year)); + $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval + . ';BYDAY=' + . ($this->start->weekOfYear() - $weekday->weekOfYear() + 1) + . $vcaldays[$this->start->dayOfWeek()] + . ';BYMONTH=' . $this->start->month; + break; + } + + if ($this->hasRecurEnd()) { + $recurEnd = clone $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(self::RECUR_NONE); + return false; + } + + $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(self::RECUR_DAILY); + break; + + case 'weekly': + $this->setRecurType(self::RECUR_WEEKLY); + $parse_day = true; + $set_daymask = true; + break; + + case 'monthly': + if (!isset($hash['daynumber'])) { + $this->setRecurType(self::RECUR_NONE); + return false; + } + + switch ($hash['type']) { + case 'daynumber': + $this->setRecurType(self::RECUR_MONTHLY_DATE); + $update_daynumber = true; + break; + + case 'weekday': + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); + $nth_weekday = (int) $hash['daynumber']; + $hash['daynumber'] = 1; + $parse_day = true; + $update_daynumber = true; + $update_weekday = true; + break; + } + break; + + case 'yearly': + if (!isset($hash['type'])) { + $this->setRecurType(self::RECUR_NONE); + return false; + } + + switch ($hash['type']) { + case 'monthday': + $this->setRecurType(self::RECUR_YEARLY_DATE); + $update_month = true; + $update_daynumber = true; + break; + + case 'yearday': + if (!isset($hash['month'])) { + $this->setRecurType(self::RECUR_NONE); + return false; + } + + $this->setRecurType(self::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(self::RECUR_NONE); + return false; + } + + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); + $nth_weekday = (int) $hash['daynumber']; + $hash['daynumber'] = 1; + $parse_day = true; + $update_month = true; + $update_daynumber = true; + $update_weekday = true; + break; + } + } + + switch ($hash['range-type']) { + case 'number': + if (!isset($hash['range'])) { + $this->setRecurType(self::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(self::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::DATE_MONDAY, + 'tuesday' => Horde_Date::DATE_TUESDAY, + 'wednesday' => Horde_Date::DATE_WEDNESDAY, + 'thursday' => Horde_Date::DATE_THURSDAY, + 'friday' => Horde_Date::DATE_FRIDAY, + 'saturday' => Horde_Date::DATE_SATURDAY, + 'sunday' => Horde_Date::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) { + $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, + ); + + if (isset($month2number[$hash['month']])) { + $this->start->month = $month2number[$hash['month']]; + } + } + + if ($update_daynumber) { + if (!isset($hash['daynumber'])) { + $this->setRecurType(self::RECUR_NONE); + return false; + } + + $this->start->mday = $hash['daynumber']; + } + + if ($update_weekday) { + $this->start->setNthWeekday($last_found_day, $nth_weekday); + } + } + + // 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() == self::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 self::RECUR_DAILY: + $hash['cycle'] = 'daily'; + break; + + case self::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 self::RECUR_MONTHLY_DATE: + $hash['cycle'] = 'monthly'; + $hash['type'] = 'daynumber'; + $hash['daynumber'] = $start->mday; + break; + + case self::RECUR_MONTHLY_WEEKDAY: + $hash['cycle'] = 'monthly'; + $hash['type'] = 'weekday'; + $hash['daynumber'] = $start->weekOfMonth(); + $hash['day'] = array ($day2number[$start->dayOfWeek()]); + break; + + case self::RECUR_YEARLY_DATE: + $hash['cycle'] = 'yearly'; + $hash['type'] = 'monthday'; + $hash['daynumber'] = $start->mday; + $hash['month'] = $month2number[$start->month]; + break; + + case self::RECUR_YEARLY_DAY: + $hash['cycle'] = 'yearly'; + $hash['type'] = 'yearday'; + $hash['daynumber'] = $start->dayOfYear(); + break; + + case self::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; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater.php b/framework/Date/lib/Horde/Date/Repeater.php new file mode 100644 index 000000000..5fdd9641b --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater.php @@ -0,0 +1,46 @@ +now)) { + throw new Horde_Date_Repeater_Exception('Start point must be set before calling next()'); + } + + if (!in_array($pointer, array('future', 'none', 'past'))) { + throw new Horde_Date_Repeater_Exception("First argument 'pointer' must be one of 'past', 'future', 'none'"); + } + } + + public function this($pointer) + { + if (is_null($this->now)) { + throw new Horde_Date_Repeater_Exception('Start point must be set before calling this()'); + } + + if (!in_array($pointer, array('future', 'none', 'past'))) { + throw new Horde_Date_Repeater_Exception("First argument 'pointer' must be one of 'past', 'future', 'none'"); + } + } + + public function __toString() + { + return 'repeater'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Day.php b/framework/Date/lib/Horde/Date/Repeater/Day.php new file mode 100644 index 000000000..7cc544b02 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Day.php @@ -0,0 +1,66 @@ +currentDayStart) { + $this->currentDayStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + } + + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentDayStart->day += $direction; + + $end = clone $this->currentDayStart; + $end->day += 1; + + return new Horde_Date_Span($this->currentDayStart, $end); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $dayBegin = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour + 1)); + $dayEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1)); + break; + + case 'past': + $dayBegin = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + $dayEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour)); + break; + + case 'none': + $dayBegin = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + $dayEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1)); + break; + } + + return new Horde_Date_Span($dayBegin, $dayEnd); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('day' => $direction * $amount)); + } + + public function width() + { + return self::DAY_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-day'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/DayName.php b/framework/Date/lib/Horde/Date/Repeater/DayName.php new file mode 100644 index 000000000..578b96680 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/DayName.php @@ -0,0 +1,74 @@ +type = $type; + } + + public function next($pointer) + { + parent::next($pointer); + + $direction = ($pointer == 'future') ? 1 : -1; + + if (!$this->currentDayStart) { + $this->currentDayStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + $direction)); + + $dayNum = $this->_dayNumber($this->type); + while ($this->currentDayStart->dayOfWeek() != $dayNum) { + $this->currentDayStart->day += $direction; + } + } else { + $this->currentDayStart->day += $direction * 7; + } + + $end = clone $this->currentDayStart; + $end->day++; + return new Horde_Date_Span($this->currentDayStart, $end); + } + + public function this($pointer = 'future') + { + parent::next($pointer); + + if ($pointer == 'none') { + $pointer = 'future'; + } + return $this->next($pointer); + } + + public function width() + { + return self::DAY_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-dayname-' . $this->type; + } + + protected function _dayNumber($dayName) + { + $days = array( + 'monday' => Horde_Date::DATE_MONDAY, + 'tuesday' => Horde_Date::DATE_TUESDAY, + 'wednesday' => Horde_Date::DATE_WEDNESDAY, + 'thursday' => Horde_Date::DATE_THURSDAY, + 'friday' => Horde_Date::DATE_FRIDAY, + 'saturday' => Horde_Date::DATE_SATURDAY, + 'sunday' => Horde_Date::DATE_SUNDAY, + ); + if (!isset($days[$dayName])) { + throw new InvalidArgumentException('Invalid day name "' . $dayName . '"'); + } + return $days[$dayName]; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/DayPortion.php b/framework/Date/lib/Horde/Date/Repeater/DayPortion.php new file mode 100644 index 000000000..ee63cc241 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/DayPortion.php @@ -0,0 +1,146 @@ +type = $type; + + if (is_int($type)) { + $this->range = array(($type * 3600), (($type + 12) * 3600)); + } else { + $lookup = array( + 'am' => array(0, (12 * 3600 - 1)), + 'pm' => array((12 * 3600), (24 * 3600 - 1)), + 'morning' => self::$morning, + 'afternoon' => self::$afternoon, + 'evening' => self::$evening, + 'night' => self::$night, + ); + if (!isset($lookup[$type])) { + throw new InvalidArgumentException("Invalid type '$type' for Repeater_DayPortion"); + } + $this->range = $lookup[$type]; + } + } + + public function next($pointer) + { + parent::next($pointer); + + $fullDay = 60 * 60 * 24; + + if (!$this->currentSpan) { + $nowSeconds = $this->now->hour * 3600 + $this->now->min * 60 + $this->now->sec; + if ($nowSeconds < $this->range[0]) { + switch ($pointer) { + case 'future': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'sec' => $this->range[0])); + break; + + case 'past': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day - 1, 'sec' => $this->range[0])); + break; + } + } elseif ($nowSeconds > $this->range[1]) { + switch ($pointer) { + case 'future': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1, 'sec' => $this->range[0])); + break; + + case 'past': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'sec' => $this->range[0])); + break; + } + } else { + switch ($pointer) { + case 'future': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1, 'sec' => $this->range[0])); + break; + + case 'past': + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day - 1, 'sec' => $this->range[0])); + break; + } + } + + $rangeEnd = $rangeStart->add($this->range[1] - $this->range[0]); + $this->currentSpan = new Horde_Date_Span($rangeStart, $rangeEnd); + } else { + switch ($pointer) { + case 'future': + $this->currentSpan = $this->currentSpan->add(array('day' => 1)); + break; + + case 'past': + $this->currentSpan = $this->currentSpan->add(array('day' => -1)); + break; + } + } + + return $this->currentSpan; + } + + public function this($context = 'future') + { + parent::this($context); + + $rangeStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'sec' => $this->range[0])); + $this->currentSpan = new Horde_Date_Span($rangeStart, $rangeStart->add($this->range[1] - $this->range[0])); + return $this->currentSpan; + } + + public function offset($span, $amount, $pointer) + { + $this->now = $span->begin; + $portionSpan = $this->next($pointer); + $direction = ($pointer == 'future') ? 1 : -1; + return $portionSpan->add(array('day' => $direction * ($amount - 1))); + } + + public function width() + { + if (!$this->range) { + throw new Horde_Date_Repeater_Exception('Range has not been set'); + } + + if ($this->currentSpan) { + return $this->currentSpan->width(); + } + + if (is_int($this->type)) { + return (12 * 3600); + } else { + return $this->range[1] - $this->range[0]; + } + } + + public function __toString() + { + return parent::__toString() . '-dayportion-' . $this->type; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Exception.php b/framework/Date/lib/Horde/Date/Repeater/Exception.php new file mode 100644 index 000000000..0fb573e7a --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Exception.php @@ -0,0 +1,4 @@ +currentFortnightStart) { + switch ($pointer) { + case 'future': + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $nextSundaySpan = $sundayRepeater->next('future'); + $this->currentFortnightStart = $nextSundaySpan->begin; + break; + + case 'past': + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = clone $this->now; + $sundayRepeater->now->day++; + $sundayRepeater->next('past'); + $sundayRepeater->next('past'); + $lastSundaySpan = $sundayRepeater->next('past'); + $this->currentFortnightStart = $lastSundaySpan->begin; + break; + } + } else { + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentFortnightStart->add($direction * self::FORTNIGHT_SECONDS); + } + + return new Horde_Date_Span($this->currentFortnightStart, $this->currentFortnightStart->add(self::FORTNIGHT_SECONDS)); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + case 'none': + $thisFortnightStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour + 1)); + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $sundayRepeater->this('future'); + $thisSundaySpan = $sundayRepeater->this('future'); + $thisFortnightEnd = $thisSundaySpan->begin; + return new Horde_Date_Span($thisFortnightStart, $thisFortnightEnd); + + case 'past': + $thisFortnightEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour)); + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $lastSundaySpan = $sundayRepeater->next('past'); + $thisFortnightStart = $lastSundaySpan->begin; + return new Horde_Date_Span($thisFortnightStart, $thisFortnightEnd); + } + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add($direction * $amount * self::FORTNIGHT_SECONDS); + } + + public function width() + { + return self::FORTNIGHT_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-fortnight'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Hour.php b/framework/Date/lib/Horde/Date/Repeater/Hour.php new file mode 100644 index 000000000..31b30b2f9 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Hour.php @@ -0,0 +1,61 @@ +currentHourStart) { + $this->currentHourStart = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour)); + } + $this->currentHourStart->hour += $direction; + + $end = clone $this->currentHourStart; + $end->hour++; + return new Horde_Date_Span($this->currentHourStart, $end); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $hourStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min + 1)); + $hourEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour + 1)); + break; + + case 'past': + $hourStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour)); + $hourEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min)); + break; + + case 'none': + $hourStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour)); + $hourEnd = $hourStart->add(array('hour' => 1)); + break; + } + + return new Horde_Date_Span($hourStart, $hourEnd); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('hour' => $direction * $amount)); + } + + public function width() + { + return 3600; + } + + public function __toString() + { + return parent::__toString() . '-hour'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Minute.php b/framework/Date/lib/Horde/Date/Repeater/Minute.php new file mode 100644 index 000000000..0f5ff0ac5 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Minute.php @@ -0,0 +1,61 @@ +currentMinuteStart) { + $this->currentMinuteStart = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min)); + } + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentMinuteStart->min += $direction; + + $end = clone $this->currentMinuteStart; + $end->min++; + return new Horde_Date_Span($this->currentMinuteStart, $end); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $minuteBegin = clone $this->now; + $minuteEnd = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min)); + break; + + case 'past': + $minuteBegin = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min)); + $minuteEnd = clone $this->now; + break; + + case 'none': + $minuteBegin = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min)); + $minuteEnd = new Horde_Date(array('month' => $this->now->month, 'year' => $this->now->year, 'day' => $this->now->day, 'hour' => $this->now->hour, 'min' => $this->now->min + 1)); + break; + } + + return new Horde_Date_Span($minuteBegin, $minuteEnd); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('min' => $direction * $amount)); + } + + public function width() + { + return 60; + } + + public function __toString() + { + return parent::__toString() . '-minute'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Month.php b/framework/Date/lib/Horde/Date/Repeater/Month.php new file mode 100644 index 000000000..9afba94e1 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Month.php @@ -0,0 +1,66 @@ +currentMonthStart) { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => 1)); + } + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentMonthStart->month += $direction; + + $end = clone $this->currentMonthStart; + $end->month++; + return new Horde_Date_Span($this->currentMonthStart, $end); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $monthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1)); + $monthEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month + 1, 'day' => 1)); + break; + + case 'past': + $monthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => 1)); + $monthEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + break; + + case 'none': + $monthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => 1)); + $monthEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month + 1, 'day' => 1)); + break; + } + + return new Horde_Date_Span($monthStart, $monthEnd); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('month' => $amount * $direction)); + } + + public function width() + { + return self::MONTH_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-month'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/MonthName.php b/framework/Date/lib/Horde/Date/Repeater/MonthName.php new file mode 100644 index 000000000..d7b355e9b --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/MonthName.php @@ -0,0 +1,109 @@ +type = $type; + } + + public function next($pointer) + { + parent::next($pointer); + + if (!$this->currentMonthStart) { + $targetMonth = $this->_monthNumber($this->type); + switch ($pointer) { + case 'future': + if ($this->now->month < $targetMonth) { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $targetMonth, 'day' => 1)); + } else { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year + 1, 'month' => $targetMonth, 'day' => 1)); + } + break; + + case 'none': + if ($this->now->month <= $targetMonth) { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $targetMonth, 'day' => 1)); + } else { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year + 1, 'month' => $targetMonth, 'day' => 1)); + } + break; + + case 'past': + if ($this->now->month > $targetMonth) { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year, 'month' => $targetMonth, 'day' => 1)); + } else { + $this->currentMonthStart = new Horde_Date(array('year' => $this->now->year - 1, 'month' => $targetMonth, 'day' => 1)); + } + break; + } + } else { + switch ($pointer) { + case 'future': + $this->currentMonthStart->year++; + break; + + case 'past': + $this->currentMonthStart->year--; + break; + } + } + + return new Horde_Date_Span($this->currentMonthStart, $this->currentMonthStart->add(array('month' => 1))); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'past': + return $this->next($pointer); + + case 'future': + case 'none': + return $this->next('none'); + } + } + + public function width() + { + return Horde_Date_Repeater_Month::MONTH_SECONDS; + } + + public function index() + { + return $this->_monthNumber($this->type); + } + + public function __toString() + { + return parent::__toString() . '-monthname-' . $this->type; + } + + protected function _monthNumber($monthName) + { + $months = 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, + ); + if (!isset($months[$monthName])) { + throw new InvalidArgumentException('Invalid month name "' . $monthName . '"'); + } + return $months[$monthName]; + } + +} \ No newline at end of file diff --git a/framework/Date/lib/Horde/Date/Repeater/Season.php b/framework/Date/lib/Horde/Date/Repeater/Season.php new file mode 100644 index 000000000..2ad200b95 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Season.php @@ -0,0 +1,31 @@ +type = $type; + } + + public function next($pointer) + { + parent::next($pointer); + throw new Horde_Date_Repeater_Exception('Not implemented'); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + throw new Horde_Date_Repeater_Exception('Not implemented'); + } + + public function width() + { + return self::SEASON_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-season-' . $this->type; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Second.php b/framework/Date/lib/Horde/Date/Repeater/Second.php new file mode 100644 index 000000000..1e16b7ed6 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Second.php @@ -0,0 +1,49 @@ +secondStart) { + $this->secondStart = clone $this->now; + $this->secondStart->sec += $direction; + } else { + $this->secondStart += $direction; + } + + $end = clone $this->secondStart; + $end->sec++; + return new Horde_Date_Span($this->secondStart, $end); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + $end = clone $this->now; + $end->sec++; + return new Horde_Date_Span($this->now, $end); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add($direction * $amount); + } + + public function width() + { + return 1; + } + + public function __toString() + { + return parent::__toString() . '-second'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Time.php b/framework/Date/lib/Horde/Date/Repeater/Time.php new file mode 100644 index 000000000..405cd66c8 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Time.php @@ -0,0 +1,136 @@ +ambiguous = true; + $this->type = ($hours == 12) ? 0 : $hours * 3600; + break; + + case 3: + $this->ambiguous = true; + $this->type = $t[0] * 3600 + (int)substr($t, 1, 2) * 60; + break; + + case 4: + $this->ambiguous = (strpos($time, ':') !== false) && ($t[0] != 0) && ((int)substr($t, 0, 2) <= 12); + $hours = (int)substr($t, 0, 2); + $this->type = ($hours == 12) ? + ((int)substr($t, 2, 2) * 60) : + ($hours * 60 * 60 + (int)substr($t, 2, 2) * 60); + break; + + case 5: + $this->ambiguous = true; + $this->type = $t[0] * 3600 + (int)substr($t, 1, 2) * 60 + (int)substr($t, 3, 2); + break; + + case 6: + $this->ambiguous = (strpos($time, ':') !== false) && ($t[0] != 0) && ((int)substr($t, 0, 2) <= 12); + $hours = (int)substr($t, 0, 2); + $this->type = ($hours == 12) ? + ((int)substr($t, 2, 2) * 60 + (int)substr($t, 4, 2)) : + ($hours * 60 * 60 + (int)substr($t, 2, 2) * 60 + (int)substr($t, 4, 2)); + break; + + default: + throw new Horde_Date_Repeater_Exception('Time cannot exceed six digits'); + } + } + + /** + * Return the next past or future Span for the time that this Repeater represents + * pointer - Symbol representing which temporal direction to fetch the next day + * must be either :past or :future + */ + public function next($pointer) + { + parent::next($pointer); + + $halfDay = 3600 * 12; + $fullDay = 3600 * 24; + + $first = false; + + if (!$this->currentTime) { + $first = true; + $midnight = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + $yesterdayMidnight = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day - 1)); + $tomorrowMidnight = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1)); + + if ($pointer == 'future') { + if ($this->ambiguous) { + foreach (array($midnight->add($this->type), $midnight->add($halfDay + $this->type), $tomorrowMidnight->add($this->type)) as $t) { + if ($t->compareDateTime($this->now) >= 0) { + $this->currentTime = $t; + break; + } + } + } else { + foreach (array($midnight->add($this->type), $tomorrowMidnight->add($this->type)) as $t) { + if ($t->compareDateTime($this->now) >= 0) { + $this->currentTime = $t; + break; + } + } + } + } elseif ($pointer == 'past') { + if ($this->ambiguous) { + foreach (array($midnight->add($halfDay + $this->type), $midnight->add($this->type), $yesterdayMidnight->add($this->type * 2)) as $t) { + if ($t->compareDateTime($this->now) <= 0) { + $this->currentTime = $t; + break; + } + } + } else { + foreach (array($midnight->add($this->type), $yesterdayMidnight->add($this->type)) as $t) { + if ($t->compareDateTime($this->now) <= 0) { + $this->currentTime = $t; + break; + } + } + } + } + + if (!$this->currentTime) { + throw new Horde_Date_Repeater_Exception('Current time cannot be null at this point'); + } + } + + if (!$first) { + $increment = $this->ambiguous ? $halfDay : $fullDay; + $this->currentTime->sec += ($pointer == 'future') ? $increment : -$increment; + } + + return new Horde_Date_Span($this->currentTime, $this->currentTime->add(1)); + } + + public function this($context = 'future') + { + parent::this($context); + + if ($context == 'none') { $context = 'future'; } + return $this->next($context); + } + + public function width() + { + return 1; + } + + public function __toString() + { + return parent::__toString() . '-time-' . $this->type; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Week.php b/framework/Date/lib/Horde/Date/Repeater/Week.php new file mode 100644 index 000000000..ff16a6eb2 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Week.php @@ -0,0 +1,89 @@ +currentWeekStart) { + switch ($pointer) { + case 'future': + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $nextSundaySpan = $sundayRepeater->next('future'); + $this->currentWeekStart = $nextSundaySpan->begin; + break; + + case 'past': + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = clone $this->now; + $sundayRepeater->now->day++; + $sundayRepeater->next('past'); + $lastSundaySpan = $sundayRepeater->next('past'); + $this->currentWeekStart = $lastSundaySpan->begin; + break; + } + } else { + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentWeekStart->day += $direction * 7; + } + + return new Horde_Date_Span($this->currentWeekStart, $this->currentWeekStart->add(array('day' => 7))); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $thisWeekStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour + 1)); + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $thisSundaySpan = $sundayRepeater->this('future'); + $thisWeekEnd = $thisSundaySpan->begin; + return new Horde_Date_Span($thisWeekStart, $thisWeekEnd); + + case 'past': + $thisWeekEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day, 'hour' => $this->now->hour)); + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $lastSundaySpan = $sundayRepeater->next('past'); + $thisWeekStart = $lastSundaySpan->begin; + return new Horde_Date_Span($thisWeekStart, $thisWeekEnd); + + case 'none': + $sundayRepeater = new Horde_Date_Repeater_DayName('sunday'); + $sundayRepeater->now = $this->now; + $lastSundaySpan = $sundayRepeater->next('past'); + $thisWeekStart = $lastSundaySpan->begin; + $thisWeekEnd = clone $thisWeekStart; + $thisWeekEnd->day += 7; + return new Horde_Date_Span($thisWeekStart, $thisWeekEnd); + } + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('day' => $direction * $amount * 7)); + } + + public function width() + { + return self::WEEK_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-week'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Weekend.php b/framework/Date/lib/Horde/Date/Repeater/Weekend.php new file mode 100644 index 000000000..0116f97b9 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Weekend.php @@ -0,0 +1,80 @@ +currentWeekStart) { + switch ($pointer) { + case 'future': + $saturdayRepeater = new Horde_Date_Repeater_DayName('saturday'); + $saturdayRepeater->now = $this->now; + $nextSaturdaySpan = $saturdayRepeater->next('future'); + $this->currentWeekStart = $nextSaturdaySpan->begin; + break; + + case 'past': + $saturdayRepeater = new Horde_Date_Repeater_DayName('saturday'); + $saturdayRepeater->now = $this->now; + $saturdayRepeater->now->day++; + $lastSaturdaySpan = $saturdayRepeater->next('past'); + $this->currentWeekStart = $lastSaturdaySpan->begin; + break; + } + } else { + $direction = ($pointer == 'future') ? 1 : -1; + $this->currentWeekStart->day += $direction * 7; + } + + return new Horde_Date_Span($this->currentWeekStart, $this->currentWeekStart->add(array('day' => 2))); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + case 'none': + $saturdayRepeater = new Horde_Date_Repeater_DayName('saturday'); + $saturdayRepeater->now = $this->now; + $thisSaturdaySpan = $saturdayRepeater->this('future'); + return new Horde_Date_Span($thisSaturdaySpan->begin, $thisSaturdaySpan->begin->add(array('day' => 2))); + + case 'past': + $saturdayRepeater = new Horde_Date_Repeater_DayName('saturday'); + $saturdayRepeater->now = $this->now; + $lastSaturdaySpan = $saturdayRepeater->this('past'); + return new Horde_Date_Span($lastSaturdaySpan->begin, $lastSaturdaySpan->begin->add(array('day' => 2))); + } + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + $weekend = new self(); + $weekend->now = clone $span->begin; + $start = $weekend->next($pointer)->begin; + $start->day += ($amount - 1) * $direction * 7; + return new Horde_Date_Span($start, $start->add($span->width())); + } + + public function width() + { + return self::WEEKEND_SECONDS; + } + + public function __toString() + { + return parent::__toString() . '-weekend'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Repeater/Year.php b/framework/Date/lib/Horde/Date/Repeater/Year.php new file mode 100644 index 000000000..e31207d96 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Repeater/Year.php @@ -0,0 +1,60 @@ +currentYearStart) { + $this->currentYearStart = new Horde_Date(array('year' => $this->now->year, 'month' => 1, 'day' => 1)); + } + + $diff = ($pointer == 'future') ? 1 : -1; + $this->currentYearStart->year += $diff; + + return new Horde_Date_Span($this->currentYearStart, $this->currentYearStart->add(array('year' => 1))); + } + + public function this($pointer = 'future') + { + parent::this($pointer); + + switch ($pointer) { + case 'future': + $thisYearStart = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day + 1)); + $thisYearEnd = new Horde_Date(array('year' => $this->now->year + 1, 'month' => 1, 'day' => 1)); + break; + + case 'past': + $thisYearStart = new Horde_Date(array('year' => $this->now->year, 'month' => 1, 'day' => 1)); + $thisYearEnd = new Horde_Date(array('year' => $this->now->year, 'month' => $this->now->month, 'day' => $this->now->day)); + break; + + case 'none': + $thisYearStart = new Horde_Date(array('year' => $this->now->year, 'month' => 1, 'day' => 1)); + $thisYearEnd = new Horde_Date(array('year' => $this->now->year + 1, 'month' => 1, 'day' => 1)); + break; + } + + return new Horde_Date_Span($thisYearStart, $thisYearEnd); + } + + public function offset($span, $amount, $pointer) + { + $direction = ($pointer == 'future') ? 1 : -1; + return $span->add(array('year' => ($amount * $direction))); + } + + public function width() + { + return (365 * 24 * 60 * 60); + } + + public function __toString() + { + return parent::__toString() . '-year'; + } + +} diff --git a/framework/Date/lib/Horde/Date/Span.php b/framework/Date/lib/Horde/Date/Span.php new file mode 100644 index 000000000..ad5900c82 --- /dev/null +++ b/framework/Date/lib/Horde/Date/Span.php @@ -0,0 +1,77 @@ +begin = $begin; + $this->end = $end; + } + + /** + * Return the width of this span in seconds + */ + public function width() + { + return abs($this->end->timestamp() - $this->begin->timestamp()); + } + + /** + * Is a Horde_Date within this span? + * + * @param Horde_Date $date + */ + public function includes($date) + { + return ($this->begin->compareDateTime($date) <= 0) && ($this->end->compareDateTime($date) >= 0); + } + + /** + * Add a number of seconds to this span, returning the new span + */ + public function add($factor) + { + return new Horde_Date_Span($this->begin->add($factor), $this->end->add($factor)); + } + + /** + * Subtract a number of seconds from this span, returning the new span. + */ + public function sub($factor) + { + return new Horde_Date_Span($this->begin->sub($factor), $this->end->sub($factor)); + } + + public function __toString() + { + return '(' . $this->begin . '..' . $this->end . ')'; + } + +} diff --git a/framework/Date/package.xml b/framework/Date/package.xml new file mode 100644 index 000000000..29df44d77 --- /dev/null +++ b/framework/Date/package.xml @@ -0,0 +1,101 @@ + + + Date + pear.horde.org + Horde Date package + Package for creating and manipulating dates. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Jan Schneider + jan + jan@horde.org + yes + + 2009-05-29 + + 0.3.0 + 0.3.0 + + + beta + beta + + LGPL + * Add Horde_Date_Repeater +* Add Horde_Date_Span + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.7.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/Date/test/Horde/Date/AllTests.php b/framework/Date/test/Horde/Date/AllTests.php new file mode 100644 index 000000000..c68efa643 --- /dev/null +++ b/framework/Date/test/Horde/Date/AllTests.php @@ -0,0 +1,54 @@ +isFile() && preg_match('/Test.php$/', $file->getFilename())) { + $pathname = $file->getPathname(); + require $pathname; + + $class = str_replace(DIRECTORY_SEPARATOR, '_', + preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname)); + $suite->addTestSuite('Horde_Date_' . $class); + } + } + + return $suite; + } + +} + +if (PHPUnit_MAIN_METHOD == 'Horde_Date_AllTests::main') { + Horde_Date_AllTests::main(); +} diff --git a/framework/Date/test/Horde/Date/DateTest.php b/framework/Date/test/Horde/Date/DateTest.php new file mode 100644 index 000000000..2f5b0b4dd --- /dev/null +++ b/framework/Date/test/Horde/Date/DateTest.php @@ -0,0 +1,47 @@ +month -= 2; + $this->assertEquals(2007, $d->year); + + $d = new Horde_Date('2008-01-01 00:00:00'); + $d->day -= 1; + $this->assertEquals(2007, $d->year); + $this->assertEquals(12, $d->month); + + $d = new Horde_Date('2008-01-01 00:00:00'); + $d->day += 370; + $this->assertEquals(2009, $d->year); + $this->assertEquals(1, $d->month); + + $d = new Horde_Date('2008-01-01 00:00:00'); + $d->sec += 14400; + $this->assertEquals(0, $d->sec); + $this->assertEquals(0, $d->min); + $this->assertEquals(4, $d->hour); + } + + public function testDateMath() + { + $d = new Horde_Date('2008-01-01 00:00:00'); + + $this->assertEquals('2007-12-31 00:00:00', (string)$d->sub(array('day' => 1))); + $this->assertEquals('2009-01-01 00:00:00', (string)$d->add(array('year' => 1))); + $this->assertEquals('2008-01-01 04:00:00', (string)$d->add(14400)); + } + +} diff --git a/framework/Date/test/Horde/Date/RecurrenceTest.php b/framework/Date/test/Horde/Date/RecurrenceTest.php new file mode 100644 index 000000000..bba5aaad0 --- /dev/null +++ b/framework/Date/test/Horde/Date/RecurrenceTest.php @@ -0,0 +1,113 @@ +setRecurType(Horde_Date_Recurrence::RECUR_DAILY); + $r->addException(1970, 1, 1); + $r->addException(1970, 1, 3); + $r->addException(1970, 1, 4); + + $r->setRecurEnd(new Horde_Date(86400*3)); + + $s = &new Horde_Date_Recurrence(0); + $s->fromHash($r->toHash()); + + $this->assertTrue($s->hasRecurEnd()); + + $next = $s->nextRecurrence(new Horde_Date($s->start)); + $this->assertEquals(1, $next->mday); + $this->assertTrue($s->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertFalse($s->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertTrue($s->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertTrue($s->hasException($next->year, $next->month, $next->mday)); + + $this->assertEquals(3, count($s->getExceptions())); + $this->assertTrue($s->hasActiveRecurrence()); + $s->addException(1970, 1, 2); + $this->assertFalse($s->hasActiveRecurrence()); + } + + /** + */ + public function testCompletions() + { + $r = &new Horde_Date_Recurrence(0); + $r->setRecurType(Horde_Date_Recurrence::RECUR_DAILY); + $r->addCompletion(1970, 1, 2); + $this->assertTrue($r->hasCompletion(1970, 1, 2)); + $this->assertEquals(1, count($r->getCompletions())); + $r->addCompletion(1970, 1, 4); + $this->assertEquals(2, count($r->getCompletions())); + $r->deleteCompletion(1970, 1, 2); + $this->assertEquals(1, count($r->getCompletions())); + $this->assertFalse($r->hasCompletion(1970, 1, 2)); + $r->addCompletion(1970, 1, 2); + $r->addException(1970, 1, 1); + $r->addException(1970, 1, 3); + + $next = $r->nextRecurrence(new Horde_Date($r->start)); + $this->assertEquals(1, $next->mday); + $this->assertTrue($r->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $r->nextRecurrence($next); + $this->assertTrue($r->hasCompletion($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $r->nextRecurrence($next); + $this->assertTrue($r->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $r->nextRecurrence($next); + $this->assertTrue($r->hasCompletion($next->year, $next->month, $next->mday)); + + $r->setRecurEnd(new Horde_Date(86400*3)); + $this->assertTrue($r->hasRecurEnd()); + + $this->assertFalse($r->hasActiveRecurrence()); + + $s = &new Horde_Date_Recurrence(0); + $s->fromHash($r->toHash()); + + $this->assertTrue($s->hasRecurEnd()); + + $next = $s->nextRecurrence(new Horde_Date($s->start)); + $this->assertEquals(1, $next->mday); + $this->assertTrue($s->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertTrue($s->hasCompletion($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertTrue($s->hasException($next->year, $next->month, $next->mday)); + $next->mday++; + $next = $s->nextRecurrence($next); + $this->assertTrue($s->hasCompletion($next->year, $next->month, $next->mday)); + + $this->assertEquals(2, count($s->getCompletions())); + $this->assertEquals(2, count($s->getExceptions())); + $this->assertFalse($s->hasActiveRecurrence()); + + $this->assertEquals(2, count($s->getCompletions())); + $s->deleteCompletion(1970, 1, 2); + $this->assertEquals(1, count($s->getCompletions())); + $s->deleteCompletion(1970, 1, 4); + $this->assertEquals(0, count($s->getCompletions())); + } +} diff --git a/framework/Date/test/Horde/Date/Repeater/DayNameTest.php b/framework/Date/test/Horde/Date/Repeater/DayNameTest.php new file mode 100644 index 000000000..21231eaf8 --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/DayNameTest.php @@ -0,0 +1,48 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $mondays = new Horde_Date_Repeater_DayName('monday'); + $mondays->now = $this->now; + + $span = $mondays->next('future'); + $this->assertEquals('2006-08-21', $span->begin->format('Y-m-d')); + $this->assertEquals('2006-08-22', $span->end->format('Y-m-d')); + + $span = $mondays->next('future'); + $this->assertEquals('2006-08-28', $span->begin->format('Y-m-d')); + $this->assertEquals('2006-08-29', $span->end->format('Y-m-d')); + } + + public function testNextPast() + { + $mondays = new Horde_Date_Repeater_DayName('monday'); + $mondays->now = $this->now; + + $span = $mondays->next('past'); + $this->assertEquals('2006-08-14', $span->begin->format('Y-m-d')); + $this->assertEquals('2006-08-15', $span->end->format('Y-m-d')); + + $span = $mondays->next('past'); + $this->assertEquals('2006-08-07', $span->begin->format('Y-m-d')); + $this->assertEquals('2006-08-08', $span->end->format('Y-m-d')); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/DayTest.php b/framework/Date/test/Horde/Date/Repeater/DayTest.php new file mode 100644 index 000000000..ffbcedec4 --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/DayTest.php @@ -0,0 +1,29 @@ +now = new Horde_Date('2009-01-01'); + $this->assertEquals('(2009-01-02 00:00:00..2009-01-03 00:00:00)', (string)$repeater->next('future')); + } + + public function testNextPast() + { + $repeater = new Horde_Date_Repeater_Day(); + $repeater->now = new Horde_Date('2009-01-01'); + $this->assertEquals('(2008-12-31 00:00:00..2009-01-01 00:00:00)', (string)$repeater->next('past')); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/HourTest.php b/framework/Date/test/Horde/Date/Repeater/HourTest.php new file mode 100644 index 000000000..aed7910ea --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/HourTest.php @@ -0,0 +1,76 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $hours = new Horde_Date_Repeater_Hour(); + $hours->now = $this->now; + + $nextHour = $hours->next('future'); + $this->assertEquals('2006-08-16 15:00:00', (string)$nextHour->begin); + $this->assertEquals('2006-08-16 16:00:00', (string)$nextHour->end); + + $nextNextHour = $hours->next('future'); + $this->assertEquals('2006-08-16 16:00:00', (string)$nextNextHour->begin); + $this->assertEquals('2006-08-16 17:00:00', (string)$nextNextHour->end); + } + + public function testNextPast() + { + $hours = new Horde_Date_Repeater_Hour(); + $hours->now = $this->now; + + $pastHour = $hours->next('past'); + $this->assertEquals('2006-08-16 13:00:00', (string)$pastHour->begin); + $this->assertEquals('2006-08-16 14:00:00', (string)$pastHour->end); + + $pastPastHour = $hours->next('past'); + $this->assertEquals('2006-08-16 12:00:00', (string)$pastPastHour->begin); + $this->assertEquals('2006-08-16 13:00:00', (string)$pastPastHour->end); + } + + public function testThis() + { + $hours = new Horde_Date_Repeater_Hour(); + $hours->now = new Horde_Date('2006-08-16 14:30:00'); + + $thisHour = $hours->this('future'); + $this->assertEquals('2006-08-16 14:31:00', (string)$thisHour->begin); + $this->assertEquals('2006-08-16 15:00:00', (string)$thisHour->end); + + $thisHour = $hours->this('past'); + $this->assertEquals('2006-08-16 14:00:00', (string)$thisHour->begin); + $this->assertEquals('2006-08-16 14:30:00', (string)$thisHour->end); + } + + public function testOffset() + { + $span = new Horde_Date_Span($this->now, $this->now->add(1)); + $hours = new Horde_Date_Repeater_Hour(); + + $offsetSpan = $hours->offset($span, 3, 'future'); + $this->assertEquals('2006-08-16 17:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-08-16 17:00:01', (string)$offsetSpan->end); + + $offsetSpan = $hours->offset($span, 24, 'past'); + $this->assertEquals('2006-08-15 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-08-15 14:00:01', (string)$offsetSpan->end); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/MonthNameTest.php b/framework/Date/test/Horde/Date/Repeater/MonthNameTest.php new file mode 100644 index 000000000..2e7d0058c --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/MonthNameTest.php @@ -0,0 +1,67 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $mays = new Horde_Date_Repeater_MonthName('may'); + $mays->now = $this->now; + + $nextMay = $mays->next('future'); + $this->assertEquals('2007-05-01 00:00:00', (string)$nextMay->begin); + $this->assertEquals('2007-06-01 00:00:00', (string)$nextMay->end); + + $nextNextMay = $mays->next('future'); + $this->assertEquals('2008-05-01 00:00:00', (string)$nextNextMay->begin); + $this->assertEquals('2008-06-01 00:00:00', (string)$nextNextMay->end); + + $decembers = new Horde_Date_Repeater_MonthName('december'); + $decembers->now = $this->now; + + $nextDecember = $decembers->next('future'); + $this->assertEquals('2006-12-01 00:00:00', (string)$nextDecember->begin); + $this->assertEquals('2007-01-01 00:00:00', (string)$nextDecember->end); + } + + public function testNextPast() + { + $mays = new Horde_Date_Repeater_MonthName('may'); + $mays->now = $this->now; + + $this->assertEquals('2006-05-01 00:00:00', (string)$mays->next('past')->begin); + $this->assertEquals('2005-05-01 00:00:00', (string)$mays->next('past')->begin); + } + + public function testThis() + { + $octobers = new Horde_Date_Repeater_MonthName('october'); + $octobers->now = $this->now; + + $thisOctober = $octobers->this('future'); + $this->assertEquals('2006-10-01 00:00:00', (string)$thisOctober->begin); + $this->assertEquals('2006-11-01 00:00:00', (string)$thisOctober->end); + + $aprils = new Horde_Date_Repeater_MonthName('april'); + $aprils->now = $this->now; + + $thisApril = $aprils->this('past'); + $this->assertEquals('2006-04-01 00:00:00', (string)$thisApril->begin); + $this->assertEquals('2006-05-01 00:00:00', (string)$thisApril->end); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/MonthTest.php b/framework/Date/test/Horde/Date/Repeater/MonthTest.php new file mode 100644 index 000000000..61395f265 --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/MonthTest.php @@ -0,0 +1,40 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testOffsetFuture() + { + $span = new Horde_Date_Span($this->now, $this->now->add(60)); + $repeater = new Horde_Date_Repeater_Month(); + $offsetSpan = $repeater->offset($span, 1, 'future'); + + $this->assertEquals('2006-09-16 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-09-16 14:01:00', (string)$offsetSpan->end); + } + + public function testOffsetPast() + { + $span = new Horde_Date_Span($this->now, $this->now->add(60)); + $repeater = new Horde_Date_Repeater_Month(); + $offsetSpan = $repeater->offset($span, 1, 'past'); + + $this->assertEquals('2006-07-16 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-07-16 14:01:00', (string)$offsetSpan->end); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/TimeTest.php b/framework/Date/test/Horde/Date/Repeater/TimeTest.php new file mode 100644 index 000000000..e345455ad --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/TimeTest.php @@ -0,0 +1,81 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $t = new Horde_Date_Repeater_Time('4:00'); + $t->now = $this->now; + + $this->assertEquals('2006-08-16 16:00:00', (string)$t->next('future')->begin); + $this->assertEquals('2006-08-17 04:00:00', (string)$t->next('future')->begin); + + $t = new Horde_Date_Repeater_Time('13:00'); + $t->now = $this->now; + + $this->assertEquals('2006-08-17 13:00:00', (string)$t->next('future')->begin); + $this->assertEquals('2006-08-18 13:00:00', (string)$t->next('future')->begin); + + $t = new Horde_Date_Repeater_Time('0400'); + $t->now = $this->now; + + $this->assertEquals('2006-08-17 04:00:00', (string)$t->next('future')->begin); + $this->assertEquals('2006-08-18 04:00:00', (string)$t->next('future')->begin); + } + + public function testNextPast() + { + $t = new Horde_Date_Repeater_Time('4:00'); + $t->now = $this->now; + $this->assertEquals('2006-08-16 04:00:00', (string)$t->next('past')->begin); + $this->assertEquals('2006-08-15 16:00:00', (string)$t->next('past')->begin); + + $t = new Horde_Date_Repeater_Time('13:00'); + $t->now = $this->now; + $this->assertEquals('2006-08-16 13:00:00', (string)$t->next('past')->begin); + $this->assertEquals('2006-08-15 13:00:00', (string)$t->next('past')->begin); + } + + public function testType() + { + $t = new Horde_Date_Repeater_Time('4'); + $this->assertEquals(14400, $t->type); + + $t = new Horde_Date_Repeater_Time('14'); + $this->assertEquals(50400, $t->type); + + $t = new Horde_Date_Repeater_Time('4:00'); + $this->assertEquals(14400, $t->type); + + $t = new Horde_Date_Repeater_Time('4:30'); + $this->assertEquals(16200, $t->type); + + $t = new Horde_Date_Repeater_Time('1400'); + $this->assertEquals(50400, $t->type); + + $t = new Horde_Date_Repeater_Time('0400'); + $this->assertEquals(14400, $t->type); + + $t = new Horde_Date_Repeater_Time('04'); + $this->assertEquals(14400, $t->type); + + $t = new Horde_Date_Repeater_Time('400'); + $this->assertEquals(14400, $t->type); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/WeekTest.php b/framework/Date/test/Horde/Date/Repeater/WeekTest.php new file mode 100644 index 000000000..8d7865a81 --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/WeekTest.php @@ -0,0 +1,78 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $weeks = new Horde_Date_Repeater_Week(); + $weeks->now = $this->now; + + $nextWeek = $weeks->next('future'); + $this->assertEquals('2006-08-20 00:00:00', (string)$nextWeek->begin); + $this->assertEquals('2006-08-27 00:00:00', (string)$nextWeek->end); + + $nextNextWeek = $weeks->next('future'); + $this->assertEquals('2006-08-27 00:00:00', (string)$nextNextWeek->begin); + $this->assertEquals('2006-09-03 00:00:00', (string)$nextNextWeek->end); + } + + public function testNextPast() + { + $weeks = new Horde_Date_Repeater_Week(); + $weeks->now = $this->now; + + $lastWeek = $weeks->next('past'); + $this->assertEquals('2006-08-06 00:00:00', (string)$lastWeek->begin); + $this->assertEquals('2006-08-13 00:00:00', (string)$lastWeek->end); + + $lastLastWeek = $weeks->next('past'); + $this->assertEquals('2006-07-30 00:00:00', (string)$lastLastWeek->begin); + $this->assertEquals('2006-08-06 00:00:00', (string)$lastLastWeek->end); + } + + public function testThisFuture() + { + $weeks = new Horde_Date_Repeater_Week(); + $weeks->now = $this->now; + + $thisWeek = $weeks->this('future'); + $this->assertEquals('2006-08-16 15:00:00', (string)$thisWeek->begin); + $this->assertEquals('2006-08-20 00:00:00', (string)$thisWeek->end); + } + + public function testThisPast() + { + $weeks = new Horde_Date_Repeater_Week(); + $weeks->now = $this->now; + + $thisWeek = $weeks->this('past'); + $this->assertEquals('2006-08-13 00:00:00', (string)$thisWeek->begin); + $this->assertEquals('2006-08-16 14:00:00', (string)$thisWeek->end); + } + + public function testOffset() + { + $weeks = new Horde_Date_Repeater_Week(); + $span = new Horde_Date_Span($this->now, $this->now->add(1)); + + $offsetSpan = $weeks->offset($span, 3, 'future'); + $this->assertEquals('2006-09-06 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-09-06 14:00:01', (string)$offsetSpan->end); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/WeekendTest.php b/framework/Date/test/Horde/Date/Repeater/WeekendTest.php new file mode 100644 index 000000000..ff053749f --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/WeekendTest.php @@ -0,0 +1,88 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $weekend->now = $this->now; + + $nextWeekend = $weekend->next('future'); + $this->assertEquals('2006-08-19 00:00:00', (string)$nextWeekend->begin); + $this->assertEquals('2006-08-21 00:00:00', (string)$nextWeekend->end); + } + + public function testNextPast() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $weekend->now = $this->now; + + $lastWeekend = $weekend->next('past'); + $this->assertEquals('2006-08-12 00:00:00', (string)$lastWeekend->begin); + $this->assertEquals('2006-08-14 00:00:00', (string)$lastWeekend->end); + } + + public function testThisFuture() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $weekend->now = $this->now; + + $thisWeekend = $weekend->this('future'); + $this->assertEquals('2006-08-19 00:00:00', (string)$thisWeekend->begin); + $this->assertEquals('2006-08-21 00:00:00', (string)$thisWeekend->end); + } + + public function testThisPast() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $weekend->now = $this->now; + + $thisWeekend = $weekend->this('past'); + $this->assertEquals('2006-08-12 00:00:00', (string)$thisWeekend->begin); + $this->assertEquals('2006-08-14 00:00:00', (string)$thisWeekend->end); + } + + public function testThisNone() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $weekend->now = $this->now; + + $thisWeekend = $weekend->this('none'); + $this->assertEquals('2006-08-19 00:00:00', (string)$thisWeekend->begin); + $this->assertEquals('2006-08-21 00:00:00', (string)$thisWeekend->end); + } + + public function testOffset() + { + $weekend = new Horde_Date_Repeater_Weekend(); + $span = new Horde_Date_Span($this->now, $this->now->add(1)); + + $offsetSpan = $weekend->offset($span, 3, 'future'); + $this->assertEquals('2006-09-02 00:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-09-02 00:00:01', (string)$offsetSpan->end); + + $offsetSpan = $weekend->offset($span, 1, 'past'); + $this->assertEquals('2006-08-12 00:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-08-12 00:00:01', (string)$offsetSpan->end); + + $offsetSpan = $weekend->offset($span, 0, 'future'); + $this->assertEquals('2006-08-12 00:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2006-08-12 00:00:01', (string)$offsetSpan->end); + } + +} diff --git a/framework/Date/test/Horde/Date/Repeater/YearTest.php b/framework/Date/test/Horde/Date/Repeater/YearTest.php new file mode 100644 index 000000000..0cedf7895 --- /dev/null +++ b/framework/Date/test/Horde/Date/Repeater/YearTest.php @@ -0,0 +1,76 @@ +now = new Horde_Date('2006-08-16 14:00:00'); + } + + public function testNextFuture() + { + $years = new Horde_Date_Repeater_Year(); + $years->now = $this->now; + + $nextYear = $years->next('future'); + $this->assertEquals('2007-01-01', $nextYear->begin->format('Y-m-d')); + $this->assertEquals('2008-01-01', $nextYear->end->format('Y-m-d')); + + $nextNextYear = $years->next('future'); + $this->assertEquals('2008-01-01', $nextNextYear->begin->format('Y-m-d')); + $this->assertEquals('2009-01-01', $nextNextYear->end->format('Y-m-d')); + } + + public function testNextPast() + { + $years = new Horde_Date_Repeater_Year(); + $years->now = $this->now; + + $lastYear = $years->next('past'); + $this->assertEquals('2005-01-01', $lastYear->begin->format('Y-m-d')); + $this->assertEquals('2006-01-01', $lastYear->end->format('Y-m-d')); + + $lastLastYear = $years->next('past'); + $this->assertEquals('2004-01-01', $lastLastYear->begin->format('Y-m-d')); + $this->assertEquals('2005-01-01', $lastLastYear->end->format('Y-m-d')); + } + + public function testThis() + { + $years = new Horde_Date_Repeater_Year(); + $years->now = $this->now; + + $thisYear = $years->this('future'); + $this->assertEquals('2006-08-17', $thisYear->begin->format('Y-m-d')); + $this->assertEquals('2007-01-01', $thisYear->end->format('Y-m-d')); + + $thisYear = $years->this('past'); + $this->assertEquals('2006-01-01', $thisYear->begin->format('Y-m-d')); + $this->assertEquals('2006-08-16', $thisYear->end->format('Y-m-d')); + } + + public function testOffset() + { + $span = new Horde_Date_Span($this->now, $this->now->add(1)); + $years = new Horde_Date_Repeater_Year(); + + $offsetSpan = $years->offset($span, 3, 'future'); + $this->assertEquals('2009-08-16 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('2009-08-16 14:00:01', (string)$offsetSpan->end); + + $offsetSpan = $years->offset($span, 10, 'past'); + $this->assertEquals('1996-08-16 14:00:00', (string)$offsetSpan->begin); + $this->assertEquals('1996-08-16 14:00:01', (string)$offsetSpan->end); + } + +} diff --git a/framework/Date/test/Horde/Date/SpanTest.php b/framework/Date/test/Horde/Date/SpanTest.php new file mode 100644 index 000000000..c6424bbe1 --- /dev/null +++ b/framework/Date/test/Horde/Date/SpanTest.php @@ -0,0 +1,38 @@ +assertEquals(60 * 60 * 24, $s->width()); + } + + public function testIncludes() + { + $s = new Horde_Date_Span(new Horde_Date('2006-08-16 00:00:00'), new Horde_Date('2006-08-17 00:00:00')); + $this->assertTrue($s->includes(new Horde_Date('2006-08-16 12:00:00'))); + $this->assertFalse($s->includes(new Horde_Date('2006-08-15 00:00:00'))); + $this->assertFalse($s->includes(new Horde_Date('2006-08-18 00:00:00'))); + } + + public function testSpanMath() + { + $s = new Horde_Date_Span(new Horde_Date(1), new Horde_Date(2)); + $this->assertEquals(2, $s->add(1)->begin->timestamp()); + $this->assertEquals(3, $s->add(1)->end->timestamp()); + $this->assertEquals(0, $s->sub(1)->begin->timestamp()); + $this->assertEquals(1, $s->sub(1)->end->timestamp()); + } + +} diff --git a/framework/Date/test/Horde/Date/fixtures/bug2813.ics b/framework/Date/test/Horde/Date/fixtures/bug2813.ics new file mode 100644 index 000000000..2f6371fb2 --- /dev/null +++ b/framework/Date/test/Horde/Date/fixtures/bug2813.ics @@ -0,0 +1,34 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME:Kronolith Bug +PRODID:-//Apple Computer\, Inc//iCal 2.0//EN +X-WR-RELCALID:6478A81B-4979-4F9D-B9DD-E3F8DD360B96 +X-WR-TIMEZONE:US/Eastern +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VTIMEZONE +TZID:US/Eastern +LAST-MODIFIED:20060616T214021Z +BEGIN:DAYLIGHT +DTSTART:20060402T070000 +TZOFFSETTO:-0400 +TZOFFSETFROM:+0000 +TZNAME:EDT +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:20061029T020000 +TZOFFSETTO:-0500 +TZOFFSETFROM:-0400 +TZNAME:EST +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=US/Eastern:20060616T180000 +DTEND;TZID=US/Eastern:20060616T190000 +SUMMARY:Test 2 +UID:3496A948-E0AB-49FF-B76F-45B700B86AF2 +SEQUENCE:8 +DTSTAMP:20060616T212821Z +RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20060618T035959Z +END:VEVENT +END:VCALENDAR