diff --git a/plugins/libkolab/bin/Date_Recurrence_weekday.diff b/plugins/libkolab/bin/Date_Recurrence_weekday.diff new file mode 100644 index 00000000..e8b767df --- /dev/null +++ b/plugins/libkolab/bin/Date_Recurrence_weekday.diff @@ -0,0 +1,325 @@ +--- Date/Recurrence.php.orig 2012-07-10 19:54:48.000000000 +0200 ++++ Date/Recurrence.php 2012-07-10 19:55:38.000000000 +0200 +@@ -95,6 +95,20 @@ + public $recurData = null; + + /** ++ * BYDAY recurrence number ++ * ++ * @var integer ++ */ ++ public $recurNthDay = null; ++ ++ /** ++ * BYMONTH recurrence data ++ * ++ * @var array ++ */ ++ public $recurMonths = array(); ++ ++ /** + * All the exceptions from recurrence for this event. + * + * @var array +@@ -157,6 +171,44 @@ + } + + /** ++ * ++ * @param integer $nthDay The nth weekday of month to repeat events on ++ */ ++ public function setRecurNthWeekday($nth) ++ { ++ $this->recurNthDay = (int)$nth; ++ } ++ ++ /** ++ * ++ * @return integer The nth weekday of month to repeat events. ++ */ ++ public function getRecurNthWeekday() ++ { ++ return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7); ++ } ++ ++ /** ++ * Specifies the months for yearly (weekday) recurrence ++ * ++ * @param array $months List of months (integers) this event recurs on. ++ */ ++ function setRecurByMonth($months) ++ { ++ $this->recurMonths = (array)$months; ++ } ++ ++ /** ++ * Returns a list of months this yearly event recurs on ++ * ++ * @return array List of months (integers) this event recurs on. ++ */ ++ function getRecurByMonth() ++ { ++ return $this->recurMonths; ++ } ++ ++ /** + * Returns the days this event recurs on. + * + * @return integer A mask consisting of Horde_Date::MASK_* constants +@@ -546,8 +598,13 @@ + $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(); ++ if (isset($this->recurNthDay)) { ++ $nth = $this->recurNthDay; ++ $weekday = log($this->recurData, 2); ++ } else { ++ $nth = ceil($this->start->mday / 7); ++ $weekday = $estart->dayOfWeek(); ++ } + + // Adjust $estart to be the first candidate. + $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; +@@ -660,8 +717,13 @@ + $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(); ++ if (isset($this->recurNthDay)) { ++ $nth = $this->recurNthDay; ++ $weekday = log($this->recurData, 2); ++ } else { ++ $nth = ceil($this->start->mday / 7); ++ $weekday = $estart->dayOfWeek(); ++ } + + // Adjust $estart to be the first candidate. + $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; +@@ -894,15 +956,6 @@ + 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]); +@@ -953,7 +1006,10 @@ + list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); + $this->setRecurEnd(new Horde_Date(array('year' => $year, + 'month' => $month, +- 'mday' => $mday))); ++ 'mday' => $mday, ++ 'hour' => 23, ++ 'min' => 59, ++ 'sec' => 59))); + } + } + } +@@ -1049,6 +1105,16 @@ + // Always default the recurInterval to 1. + $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); + ++ $maskdays = array( ++ 'SU' => Horde_Date::MASK_SUNDAY, ++ 'MO' => Horde_Date::MASK_MONDAY, ++ 'TU' => Horde_Date::MASK_TUESDAY, ++ 'WE' => Horde_Date::MASK_WEDNESDAY, ++ 'TH' => Horde_Date::MASK_THURSDAY, ++ 'FR' => Horde_Date::MASK_FRIDAY, ++ 'SA' => Horde_Date::MASK_SATURDAY, ++ ); ++ + switch (Horde_String::upper($rdata['FREQ'])) { + case 'DAILY': + $this->setRecurType(self::RECUR_DAILY); +@@ -1057,15 +1123,6 @@ + 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) { +@@ -1090,6 +1147,10 @@ + case 'MONTHLY': + if (isset($rdata['BYDAY'])) { + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); ++ if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { ++ $this->setRecurOnDay($maskdays[$m[2]]); ++ $this->setRecurNthWeekday($m[1]); ++ } + } else { + $this->setRecurType(self::RECUR_MONTHLY_DATE); + } +@@ -1100,6 +1161,14 @@ + $this->setRecurType(self::RECUR_YEARLY_DAY); + } elseif (isset($rdata['BYDAY'])) { + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); ++ if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { ++ $this->setRecurOnDay($maskdays[$m[2]]); ++ $this->setRecurNthWeekday($m[1]); ++ } ++ if ($rdata['BYMONTH']) { ++ $months = explode(',', $rdata['BYMONTH']); ++ $this->setRecurByMonth($months); ++ } + } else { + $this->setRecurType(self::RECUR_YEARLY_DATE); + } +@@ -1163,13 +1232,19 @@ + break; + + case self::RECUR_MONTHLY_WEEKDAY: +- $nth_weekday = (int)($this->start->mday / 7); +- if (($this->start->mday % 7) > 0) { +- $nth_weekday++; ++ if (isset($this->recurNthDay)) { ++ $nth_weekday = $this->recurNthDay; ++ $day_of_week = log($this->recurData, 2); ++ } else { ++ $day_of_week = $this->start->dayOfWeek(); ++ $nth_weekday = (int)($this->start->mday / 7); ++ if (($this->start->mday % 7) > 0) { ++ $nth_weekday++; ++ } + } + $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval +- . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; ++ . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; + break; + + case self::RECUR_YEARLY_DATE: +@@ -1182,15 +1257,22 @@ + break; + + case self::RECUR_YEARLY_WEEKDAY: +- $nth_weekday = (int)($this->start->mday / 7); +- if (($this->start->mday % 7) > 0) { +- $nth_weekday++; +- } ++ if (isset($this->recurNthDay)) { ++ $nth_weekday = $this->recurNthDay; ++ $day_of_week = log($this->recurData, 2); ++ } else { ++ $day_of_week = $this->start->dayOfWeek(); ++ $nth_weekday = (int)($this->start->mday / 7); ++ if (($this->start->mday % 7) > 0) { ++ $nth_weekday++; ++ } ++ } ++ $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; + $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval + . ';BYDAY=' + . $nth_weekday +- . $vcaldays[$this->start->dayOfWeek()] ++ . $vcaldays[$day_of_week] + . ';BYMONTH=' . $this->start->month; + break; + } +@@ -1223,6 +1305,21 @@ + + $this->setRecurInterval((int)$hash['interval']); + ++ $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, ++ ); ++ + $parse_day = false; + $set_daymask = false; + $update_month = false; +@@ -1255,11 +1352,9 @@ + + case 'weekday': + $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); +- $nth_weekday = (int)$hash['daynumber']; +- $hash['daynumber'] = 1; ++ $this->setRecurNthWeekday($hash['daynumber']); + $parse_day = true; +- $update_daynumber = true; +- $update_weekday = true; ++ $set_daymask = true; + break; + } + break; +@@ -1297,12 +1392,13 @@ + } + + $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); +- $nth_weekday = (int)$hash['daynumber']; +- $hash['daynumber'] = 1; ++ $this->setRecurNthWeekday($hash['daynumber']); + $parse_day = true; +- $update_month = true; +- $update_daynumber = true; +- $update_weekday = true; ++ $set_daymask = true; ++ ++ if ($hash['month'] && isset($month2number[$hash['month']])) { ++ $this->setRecurByMonth($month2number[$hash['month']]); ++ } + break; + } + } +@@ -1368,21 +1464,6 @@ + + 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']]; + } +@@ -1398,7 +1479,7 @@ + } + + if ($update_weekday) { +- $this->start->setNthWeekday($last_found_day, $nth_weekday); ++ $this->setNthWeekday($nth_weekday); + } + } + diff --git a/plugins/libkolab/bin/Date_last_weekday.diff b/plugins/libkolab/bin/Date_last_weekday.diff new file mode 100644 index 00000000..d2603609 --- /dev/null +++ b/plugins/libkolab/bin/Date_last_weekday.diff @@ -0,0 +1,37 @@ +--- Date.php.orig 2012-07-10 19:14:26.000000000 +0200 ++++ Date.php 2012-07-10 19:16:22.000000000 +0200 +@@ -627,16 +627,25 @@ + return; + } + +- $this->_mday = 1; +- $first = $this->dayOfWeek(); +- if ($weekday < $first) { +- $this->_mday = 8 + $weekday - $first; +- } else { +- $this->_mday = $weekday - $first + 1; ++ if ($nth < 0) { // last $weekday of month ++ $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year); ++ $last = $this->dayOfWeek(); ++ $this->_mday += ($weekday - $last); ++ if ($this->_mday > $lastday) ++ $this->_mday -= 7; ++ } ++ else { ++ $this->_mday = 1; ++ $first = $this->dayOfWeek(); ++ if ($weekday < $first) { ++ $this->_mday = 8 + $weekday - $first; ++ } else { ++ $this->_mday = $weekday - $first + 1; ++ } ++ $diff = 7 * $nth - 7; ++ $this->_mday += $diff; ++ $this->_correct(self::MASK_DAY, $diff < 0); + } +- $diff = 7 * $nth - 7; +- $this->_mday += $diff; +- $this->_correct(self::MASK_DAY, $diff < 0); + } + + /** diff --git a/plugins/libkolab/bin/get_horde_date.sh b/plugins/libkolab/bin/get_horde_date.sh index c75b7fd0..b8e663d4 100755 --- a/plugins/libkolab/bin/get_horde_date.sh +++ b/plugins/libkolab/bin/get_horde_date.sh @@ -1,24 +1,46 @@ #!/bin/sh -# Copy Horde_Date_Recurrence classes and dependencies to stdout. +# Copy Horde_Date_Recurrence classes and dependencies to the given target directory. # This will create a standalone copy of the classes requried for date recurrence computation. SRCDIR=$1 +DESTDIR=$2 +BINDIR=`dirname $0` -if [ ! -d "$SRCDIR" ]; then - echo "Usage: get_horde_date.sh SRCDIR" - echo "Please enter a valid source directory of the Horde libs" +if [ ! -d "$SRCDIR" -o ! -d "$DESTDIR" ]; then + echo "Usage: get_horde_date.sh SRCDIR DESTDIR" + echo "Please enter valid source and destination directories for the Horde libs" exit 1 fi + +# concat Date.php and Date/Utils.php +HORDE_DATE="$DESTDIR/Horde_Date.php" + echo " $HORDE_DATE + +patch $SRCDIR/Date.php $BINDIR/Date_last_weekday.diff --output=$HORDE_DATE.patched +sed 's///' $HORDE_DATE.patched >> $HORDE_DATE +sed 's///' $SRCDIR/Date/Utils.php >> $HORDE_DATE + +# copy and patch Date/Recurrence.php +HORDE_DATE_RECURRENCE="$DESTDIR/Horde_Date_Recurrence.php" + +echo " 1 ? \$plur : \$sing); } } -" +" > $HORDE_DATE_RECURRENCE + +patch $SRCDIR/Date/Recurrence.php $BINDIR/Date_Recurrence_weekday.diff --output=$HORDE_DATE_RECURRENCE.patched +sed 's///' $HORDE_DATE_RECURRENCE.patched >> $HORDE_DATE_RECURRENCE + +# remove dependency to Horde_String +sed -i '' "s/Horde_String::/strto/" $HORDE_DATE_RECURRENCE + +rm $DESTDIR/Horde_Date*.patched -sed 's///' $SRCDIR/Date/Utils.php -echo "\n" -sed 's///' $SRCDIR/Date/Recurrence.php | sed -E "s/Horde_String::/strto/" -echo "\n" diff --git a/plugins/libkolab/lib/Horde_Date.php b/plugins/libkolab/lib/Horde_Date.php index 4ddd9d5e..9197f846 100644 --- a/plugins/libkolab/lib/Horde_Date.php +++ b/plugins/libkolab/lib/Horde_Date.php @@ -1,4 +1,13 @@ _mday = 1; - $first = $this->dayOfWeek(); - if ($weekday < $first) { - $this->_mday = 8 + $weekday - $first; - } else { - $this->_mday = $weekday - $first + 1; + if ($nth < 0) { // last $weekday of month + $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year); + $last = $this->dayOfWeek(); + $this->_mday += ($weekday - $last); + if ($this->_mday > $lastday) + $this->_mday -= 7; + } + else { + $this->_mday = 1; + $first = $this->dayOfWeek(); + if ($weekday < $first) { + $this->_mday = 8 + $weekday - $first; + } else { + $this->_mday = $weekday - $first + 1; + } + $diff = 7 * $nth - 7; + $this->_mday += $diff; + $this->_correct(self::MASK_DAY, $diff < 0); } - $diff = 7 * $nth - 7; - $this->_mday += $diff; - $this->_correct(self::MASK_DAY, $diff < 0); } /** @@ -1110,3 +1128,177 @@ class Horde_Date } } + +/** + * @category Horde + * @package Date + */ + +/** + * Horde Date wrapper/logic class, including some calculation + * functions. + * + * @category Horde + * @package Date + */ +class Horde_Date_Utils +{ + /** + * Returns whether a year is a leap year. + * + * @param integer $year The year. + * + * @return boolean True if the year is a leap year. + */ + public static function isLeapYear($year) + { + if (strlen($year) != 4 || preg_match('/\D/', $year)) { + return false; + } + + return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); + } + + /** + * Returns the 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. + */ + public static function firstDayOfWeek($week, $year) + { + return new Horde_Date(sprintf('%04dW%02d', $year, $week)); + } + + /** + * Returns the number of days in the specified month. + * + * @param integer $month The month + * @param integer $year The year. + * + * @return integer The number of days in the month. + */ + public static 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 a relative, natural language representation of a timestamp + * + * @todo Wider range of values ... maybe future time as well? + * @todo Support minimum resolution parameter. + * + * @param mixed $time The time. Any format accepted by Horde_Date. + * @param string $date_format Format to display date if timestamp is + * more then 1 day old. + * @param string $time_format Format to display time if timestamp is 1 + * day old. + * + * @return string The relative time (i.e. 2 minutes ago) + */ + public static function relativeDateTime($time, $date_format = '%x', + $time_format = '%X') + { + $date = new Horde_Date($time); + + $delta = time() - $date->timestamp(); + if ($delta < 60) { + return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta); + } + + $delta = round($delta / 60); + if ($delta < 60) { + return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta); + } + + $delta = round($delta / 60); + if ($delta < 24) { + return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta); + } + + if ($delta > 24 && $delta < 48) { + $date = new Horde_Date($time); + return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format)); + } + + $delta = round($delta / 24); + if ($delta < 7) { + return sprintf(Horde_Date_Translation::t("%d days ago"), $delta); + } + + if (round($delta / 7) < 5) { + $delta = round($delta / 7); + return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta); + } + + // Default to the user specified date format. + return $date->strftime($date_format); + } + + /** + * Tries to convert strftime() formatters to date() formatters. + * + * Unsupported formatters will be removed. + * + * @param string $format A strftime() formatting string. + * + * @return string A date() formatting string. + */ + public static function strftime2date($format) + { + $replace = array( + '/%a/' => 'D', + '/%A/' => 'l', + '/%d/' => 'd', + '/%e/' => 'j', + '/%j/' => 'z', + '/%u/' => 'N', + '/%w/' => 'w', + '/%U/' => '', + '/%V/' => 'W', + '/%W/' => '', + '/%b/' => 'M', + '/%B/' => 'F', + '/%h/' => 'M', + '/%m/' => 'm', + '/%C/' => '', + '/%g/' => '', + '/%G/' => 'o', + '/%y/' => 'y', + '/%Y/' => 'Y', + '/%H/' => 'H', + '/%I/' => 'h', + '/%i/' => 'g', + '/%M/' => 'i', + '/%p/' => 'A', + '/%P/' => 'a', + '/%r/' => 'h:i:s A', + '/%R/' => 'H:i', + '/%S/' => 's', + '/%T/' => 'H:i:s', + '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))', + '/%z/' => 'O', + '/%Z/' => '', + '/%c/' => '', + '/%D/' => 'm/d/y', + '/%F/' => 'Y-m-d', + '/%s/' => 'U', + '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))', + '/%n/' => "\n", + '/%t/' => "\t", + '/%%/' => '%' + ); + + return preg_replace(array_keys($replace), array_values($replace), $format); + } + +} diff --git a/plugins/libkolab/lib/Horde_Date_Recurrence.php b/plugins/libkolab/lib/Horde_Date_Recurrence.php index 2171a474..bbdf13df 100644 --- a/plugins/libkolab/lib/Horde_Date_Recurrence.php +++ b/plugins/libkolab/lib/Horde_Date_Recurrence.php @@ -1,9 +1,8 @@ format('t'); - } - return $cache[$year][$month]; - } - - /** - * Returns a relative, natural language representation of a timestamp - * - * @todo Wider range of values ... maybe future time as well? - * @todo Support minimum resolution parameter. - * - * @param mixed $time The time. Any format accepted by Horde_Date. - * @param string $date_format Format to display date if timestamp is - * more then 1 day old. - * @param string $time_format Format to display time if timestamp is 1 - * day old. - * - * @return string The relative time (i.e. 2 minutes ago) - */ - public static function relativeDateTime($time, $date_format = '%x', - $time_format = '%X') - { - $date = new Horde_Date($time); - - $delta = time() - $date->timestamp(); - if ($delta < 60) { - return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta); - } - - $delta = round($delta / 60); - if ($delta < 60) { - return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta); - } - - $delta = round($delta / 60); - if ($delta < 24) { - return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta); - } - - if ($delta > 24 && $delta < 48) { - $date = new Horde_Date($time); - return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format)); - } - - $delta = round($delta / 24); - if ($delta < 7) { - return sprintf(Horde_Date_Translation::t("%d days ago"), $delta); - } - - if (round($delta / 7) < 5) { - $delta = round($delta / 7); - return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta); - } - - // Default to the user specified date format. - return $date->strftime($date_format); - } - - /** - * Tries to convert strftime() formatters to date() formatters. - * - * Unsupported formatters will be removed. - * - * @param string $format A strftime() formatting string. - * - * @return string A date() formatting string. - */ - public static function strftime2date($format) - { - $replace = array( - '/%a/' => 'D', - '/%A/' => 'l', - '/%d/' => 'd', - '/%e/' => 'j', - '/%j/' => 'z', - '/%u/' => 'N', - '/%w/' => 'w', - '/%U/' => '', - '/%V/' => 'W', - '/%W/' => '', - '/%b/' => 'M', - '/%B/' => 'F', - '/%h/' => 'M', - '/%m/' => 'm', - '/%C/' => '', - '/%g/' => '', - '/%G/' => 'o', - '/%y/' => 'y', - '/%Y/' => 'Y', - '/%H/' => 'H', - '/%I/' => 'h', - '/%i/' => 'g', - '/%M/' => 'i', - '/%p/' => 'A', - '/%P/' => 'a', - '/%r/' => 'h:i:s A', - '/%R/' => 'H:i', - '/%S/' => 's', - '/%T/' => 'H:i:s', - '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))', - '/%z/' => 'O', - '/%Z/' => '', - '/%c/' => '', - '/%D/' => 'm/d/y', - '/%F/' => 'Y-m-d', - '/%s/' => 'U', - '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))', - '/%n/' => "\n", - '/%t/' => "\t", - '/%%/' => '%' - ); - - return preg_replace(array_keys($replace), array_values($replace), $format); - } - -} - - - /** * This file contains the Horde_Date_Recurrence class and according constants. * @@ -289,6 +112,20 @@ class Horde_Date_Recurrence */ public $recurData = null; + /** + * BYDAY recurrence number + * + * @var integer + */ + public $recurNthDay = null; + + /** + * BYMONTH recurrence data + * + * @var array + */ + public $recurMonths = array(); + /** * All the exceptions from recurrence for this event. * @@ -351,6 +188,44 @@ class Horde_Date_Recurrence $this->recurData = $dayMask; } + /** + * + * @param integer $nthDay The nth weekday of month to repeat events on + */ + public function setRecurNthWeekday($nth) + { + $this->recurNthDay = (int)$nth; + } + + /** + * + * @return integer The nth weekday of month to repeat events. + */ + public function getRecurNthWeekday() + { + return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7); + } + + /** + * Specifies the months for yearly (weekday) recurrence + * + * @param array $months List of months (integers) this event recurs on. + */ + function setRecurByMonth($months) + { + $this->recurMonths = (array)$months; + } + + /** + * Returns a list of months this yearly event recurs on + * + * @return array List of months (integers) this event recurs on. + */ + function getRecurByMonth() + { + return $this->recurMonths; + } + /** * Returns the days this event recurs on. * @@ -741,8 +616,13 @@ class Horde_Date_Recurrence $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(); + if (isset($this->recurNthDay)) { + $nth = $this->recurNthDay; + $weekday = log($this->recurData, 2); + } else { + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); + } // Adjust $estart to be the first candidate. $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; @@ -855,8 +735,13 @@ class Horde_Date_Recurrence $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(); + if (isset($this->recurNthDay)) { + $nth = $this->recurNthDay; + $weekday = log($this->recurData, 2); + } else { + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); + } // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; @@ -1089,15 +974,6 @@ class Horde_Date_Recurrence 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]); @@ -1148,7 +1024,10 @@ class Horde_Date_Recurrence list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, - 'mday' => $mday))); + 'mday' => $mday, + 'hour' => 23, + 'min' => 59, + 'sec' => 59))); } } } @@ -1244,6 +1123,16 @@ class Horde_Date_Recurrence // Always default the recurInterval to 1. $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); + $maskdays = array( + 'SU' => Horde_Date::MASK_SUNDAY, + 'MO' => Horde_Date::MASK_MONDAY, + 'TU' => Horde_Date::MASK_TUESDAY, + 'WE' => Horde_Date::MASK_WEDNESDAY, + 'TH' => Horde_Date::MASK_THURSDAY, + 'FR' => Horde_Date::MASK_FRIDAY, + 'SA' => Horde_Date::MASK_SATURDAY, + ); + switch (strtoupper($rdata['FREQ'])) { case 'DAILY': $this->setRecurType(self::RECUR_DAILY); @@ -1252,15 +1141,6 @@ class Horde_Date_Recurrence 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) { @@ -1285,6 +1165,10 @@ class Horde_Date_Recurrence case 'MONTHLY': if (isset($rdata['BYDAY'])) { $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); + if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { + $this->setRecurOnDay($maskdays[$m[2]]); + $this->setRecurNthWeekday($m[1]); + } } else { $this->setRecurType(self::RECUR_MONTHLY_DATE); } @@ -1295,6 +1179,14 @@ class Horde_Date_Recurrence $this->setRecurType(self::RECUR_YEARLY_DAY); } elseif (isset($rdata['BYDAY'])) { $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); + if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { + $this->setRecurOnDay($maskdays[$m[2]]); + $this->setRecurNthWeekday($m[1]); + } + if ($rdata['BYMONTH']) { + $months = explode(',', $rdata['BYMONTH']); + $this->setRecurByMonth($months); + } } else { $this->setRecurType(self::RECUR_YEARLY_DATE); } @@ -1358,13 +1250,19 @@ class Horde_Date_Recurrence break; case self::RECUR_MONTHLY_WEEKDAY: - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; + if (isset($this->recurNthDay)) { + $nth_weekday = $this->recurNthDay; + $day_of_week = log($this->recurData, 2); + } else { + $day_of_week = $this->start->dayOfWeek(); + $nth_weekday = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $nth_weekday++; + } } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; + . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; break; case self::RECUR_YEARLY_DATE: @@ -1377,15 +1275,22 @@ class Horde_Date_Recurrence break; case self::RECUR_YEARLY_WEEKDAY: - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; - } + if (isset($this->recurNthDay)) { + $nth_weekday = $this->recurNthDay; + $day_of_week = log($this->recurData, 2); + } else { + $day_of_week = $this->start->dayOfWeek(); + $nth_weekday = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $nth_weekday++; + } + } + $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday - . $vcaldays[$this->start->dayOfWeek()] + . $vcaldays[$day_of_week] . ';BYMONTH=' . $this->start->month; break; } @@ -1418,6 +1323,21 @@ class Horde_Date_Recurrence $this->setRecurInterval((int)$hash['interval']); + $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, + ); + $parse_day = false; $set_daymask = false; $update_month = false; @@ -1450,11 +1370,9 @@ class Horde_Date_Recurrence case 'weekday': $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY); - $nth_weekday = (int)$hash['daynumber']; - $hash['daynumber'] = 1; + $this->setRecurNthWeekday($hash['daynumber']); $parse_day = true; - $update_daynumber = true; - $update_weekday = true; + $set_daymask = true; break; } break; @@ -1492,12 +1410,13 @@ class Horde_Date_Recurrence } $this->setRecurType(self::RECUR_YEARLY_WEEKDAY); - $nth_weekday = (int)$hash['daynumber']; - $hash['daynumber'] = 1; + $this->setRecurNthWeekday($hash['daynumber']); $parse_day = true; - $update_month = true; - $update_daynumber = true; - $update_weekday = true; + $set_daymask = true; + + if ($hash['month'] && isset($month2number[$hash['month']])) { + $this->setRecurByMonth($month2number[$hash['month']]); + } break; } } @@ -1563,21 +1482,6 @@ class Horde_Date_Recurrence 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']]; } @@ -1593,7 +1497,7 @@ class Horde_Date_Recurrence } if ($update_weekday) { - $this->start->setNthWeekday($last_found_day, $nth_weekday); + $this->setNthWeekday($nth_weekday); } } @@ -1764,5 +1668,3 @@ class Horde_Date_Recurrence } } - -