--- /var/www/html/kronolith/templates/edit/edit.inc	2007-06-19 05:22:11.000000000 +0200
+++ /var/www/html/horde/kronolith/templates/edit/edit.inc	2007-10-27 15:54:33.000000000 +0200
@@ -255,20 +255,26 @@
    </tr>
    <tr>
     <td class="nowrap">
+     <input id="recurmonthend" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_end_of_month_interval');" value="<?php echo HORDE_DATE_RECUR_MONTHLY_END ?>"<?php if ($event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_MONTHLY_END)) echo ' checked="checked"' ?> /><label for="recurmonthend"> <?php echo _("Monthly: Recurs every") ?>&nbsp;</label>
+     <input type="text" id="recur_end_of_month_interval" name="recur_end_of_month_interval" size="2" onkeypress="setRecur(5);" onchange="setRecur(5);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_MONTHLY_END) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_end_of_month_interval', _("month(s)") . ' ' . _("at the end of the month")) ?>
+    </td>
+   </tr>
+   <tr>
+    <td class="nowrap">
      <input id="recuryear" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_interval');" value="<?php echo HORDE_DATE_RECUR_YEARLY_DATE ?>"<?php if ($event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DATE)) echo ' checked="checked"' ?> /><label for="recuryear"> <?php echo _("Yearly: Recurs every") ?>&nbsp;</label>
-     <input type="text" id="recur_yearly_interval" name="recur_yearly_interval" size="2" onkeypress="setRecur(5);" onchange="setRecur(5);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DATE) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_interval', _("year(s) on the same date")) ?>
+     <input type="text" id="recur_yearly_interval" name="recur_yearly_interval" size="2" onkeypress="setRecur(6);" onchange="setRecur(6);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DATE) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_interval', _("year(s) on the same date")) ?>
     </td>
    </tr>
    <tr>
     <td class="nowrap">
      <input id="recuryearday" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_day_interval');" value="<?php echo HORDE_DATE_RECUR_YEARLY_DAY ?>"<?php if ($event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DAY)) echo ' checked="checked"' ?> /><label for="recuryearday"> <?php echo _("Yearly: Recurs every") ?>&nbsp;</label>
-     <input type="text" id="recur_yearly_day_interval" name="recur_yearly_day_interval" size="2" onkeypress="setRecur(6);" onchange="setRecur(6);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DAY) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_day_interval', _("year(s) on the same day of the year")) ?>
+     <input type="text" id="recur_yearly_day_interval" name="recur_yearly_day_interval" size="2" onkeypress="setRecur(7);" onchange="setRecur(7);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_DAY) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_day_interval', _("year(s) on the same day of the year")) ?>
     </td>
    </tr>
    <tr>
     <td class="nowrap">
      <input id="recuryearweekday" type="radio" class="checkbox" name="recur" onclick="setInterval('recur_yearly_weekday_interval');" value="<?php echo HORDE_DATE_RECUR_YEARLY_WEEKDAY ?>"<?php if ($event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY)) echo ' checked="checked"' ?> /><label for="recuryearweekday"> <?php echo _("Yearly: Recurs every") ?>&nbsp;</label>
-     <input type="text" id="recur_yearly_weekday_interval" name="recur_yearly_weekday_interval" size="2" onkeypress="setRecur(7);" onchange="setRecur(7);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_weekday_interval', _("year(s) on the same weekday and month of the year")) ?>
+     <input type="text" id="recur_yearly_weekday_interval" name="recur_yearly_weekday_interval" size="2" onkeypress="setRecur(8);" onchange="setRecur(8);" value="<?php echo $event->recurs() && $event->recurrence->hasRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY) ? $event->recurrence->getRecurInterval() : '' ?>" />&nbsp;<?php echo Horde::label('recur_yearly_weekday_interval', _("year(s) on the same weekday and month of the year")) ?>
     </td>
    </tr>
   </table>
--- /var/www/html/kronolith/lib/Driver.php	2007-09-20 22:15:49.000000000 +0200
+++ /var/www/html/horde/kronolith/lib/Driver.php	2007-10-26 18:50:42.000000000 +0200
@@ -1849,6 +1849,10 @@
                 $this->recurrence->setRecurInterval(Util::getFormData('recur_week_of_month_interval', 1));
                 break;
 
+            case HORDE_DATE_RECUR_MONTHLY_END:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_end_of_month_interval', 1));
+                break;
+
             case HORDE_DATE_RECUR_YEARLY_DATE:
                 $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_interval', 1));
                 break;
--- /var/www/html/kronolith/templates/edit/javascript.inc	2007-04-27 00:15:33.000000000 +0200
+++ /var/www/html/horde/kronolith/templates/edit/javascript.inc	2007-10-27 15:51:01.000000000 +0200
@@ -15,7 +15,10 @@
     else if (field == 'recur_weekly_interval') clearFields(2);
     else if (field == 'recur_day_of_month_interval') clearFields(3);
     else if (field == 'recur_week_of_month_interval') clearFields(4);
+    else if (field == 'recur_end_of_month_interval') clearFields(8);
     else if (field == 'recur_yearly_interval') clearFields(5);
+    else if (field == 'recur_yearly_day_interval') clearFields(6);
+    else if (field == 'recur_yearly_weekday_interval') clearFields(7);
 }
 
 function setRecur(index)
@@ -38,7 +41,10 @@
     }
     if (index != 3) f.recur_day_of_month_interval.value = '';
     if (index != 4) f.recur_week_of_month_interval.value = '';
+    if (index != 8) f.recur_end_of_month_interval.value = '';
     if (index != 5) f.recur_yearly_interval.value = '';
+    if (index != 6) f.recur_yearly_day_interval.value = '';
+    if (index != 7) f.recur_yearly_weekday_interval.value = '';
 }
 
 <?php endif; ?>
--- /var/www/html/kronolith/lib/Driver/kolab.php	2007-10-04 08:45:31.000000000 +0200
+++ /var/www/html/horde/kronolith/lib/Driver/kolab.php	2007-10-27 17:07:27.000000000 +0200
@@ -576,6 +576,12 @@
                     $this->_kolab->setElemVal($recurrence, 'day', $days[$start->dayOfWeek()]);
                     break;
 
+                case HORDE_DATE_RECUR_MONTHLY_END:
+                    $recurrence->set_attribute('cycle', 'monthly');
+                    $recurrence->set_attribute('type', 'end');
+                    $this->_kolab->setElemVal($recurrence, 'daynumber', $event->start->mday);
+                    break;
+
                 case HORDE_DATE_RECUR_YEARLY_DATE:
                     $recurrence->set_attribute('cycle', 'yearly');
                     $recurrence->set_attribute('type', 'monthday');
@@ -843,7 +849,11 @@
                     break;
 
                 case 'weekday':
-                    $this->recurrence->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
+                    $this->recurrence->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY);
+                    break;
+                    
+                case 'end':
+                    $this->recurrence->setRecurType(HORDE_DATE_RECUR_MONTHLY_END);
                     break;
                 }
                 break;
@@ -1350,6 +1360,11 @@
                     $update_daynumber = true;
                     $update_weekday = true;
                     break;
+
+                case 'end':
+                    $this->recurrence->setRecurType(HORDE_DATE_RECUR_MONTHLY_END);
+                    $update_daynumber = true;
+                    break;
                 }
                 break;
 
@@ -1643,6 +1658,12 @@
                 $recurrence['daynumber'] = $start->weekOfMonth();
                 $recurrence['day'] = array ($day2number[$start->dayOfWeek()]);
                 break;
+                
+            case HORDE_DATE_RECUR_MONTHLY_END:
+                $recurrence['cycle'] = 'monthly';
+                $recurrence['type'] = 'end';
+                $recurrence['daynumber'] = $start->mday;
+                break;
 
             case HORDE_DATE_RECUR_YEARLY_DATE:
                 $recurrence['cycle'] = 'yearly';
--- /var/www/html/kronolith/lib/Recurrence.php	2007-06-21 19:07:10.000000000 +0200
+++ /var/www/html/horde/kronolith/lib/Recurrence.php	2007-10-27 15:37:04.000000000 +0200
@@ -29,6 +29,8 @@
 define('HORDE_DATE_RECUR_MONTHLY_DATE', 3);
 /** Recurs monthly on the same week day. */
 define('HORDE_DATE_RECUR_MONTHLY_WEEKDAY', 4);
+/** Recurs monthly at the end of the month. */
+define('HORDE_DATE_RECUR_MONTHLY_END', 8);
 /** Recurs yearly on the same date. */
 define('HORDE_DATE_RECUR_YEARLY_DATE', 5);
 /** Recurs yearly on the same day of the year. */
@@ -192,7 +194,8 @@
             case HORDE_DATE_RECUR_DAILY: return _("Daily");
             case HORDE_DATE_RECUR_WEEKLY: return _("Weekly");
             case HORDE_DATE_RECUR_MONTHLY_DATE:
-            case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: return _("Monthly");
+            case HORDE_DATE_RECUR_MONTHLY_WEEKDAY:
+            case HORDE_DATE_RECUR_MONTHLY_END: return _("Monthly");
             case HORDE_DATE_RECUR_YEARLY_DATE:
             case HORDE_DATE_RECUR_YEARLY_DAY:
             case HORDE_DATE_RECUR_YEARLY_WEEKDAY: return _("Yearly");
@@ -505,6 +508,58 @@
 
             return $next;
 
+        case HORDE_DATE_RECUR_MONTHLY_END:
+            $start = new Horde_Date($this->start);
+            if ($after->compareDateTime($start) < 0) {
+                $after = $start;
+            }
+                        
+            // 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;
+            }
+            // Set start day to last day of current month
+            // If we roll over to the next year, adjust month and year for last day calculation
+            $start->month += $offset;
+            if ($start->month > 12) {
+	            $newyear = $start->year + floor($start->month / 12);
+	            $newmonth = $start->month - (floor($start->month / 12))*12;
+            } else {
+	            $newyear = $start->year;
+	            $newmonth = $start->month;            
+            }
+            $start->mday = Date_Calc::daysInMonth($newmonth,$newyear);
+            $count = $offset / $this->recurInterval;
+
+            do {
+                if ($this->recurCount &&
+                    $count++ >= $this->recurCount) {
+                    return false;
+                }
+
+                // Don't correct for day overflow; we just skip February 30th,
+                // for example.
+                $start->correct(HORDE_DATE_MASK_MONTH);
+
+                // Bail if we've gone past the end of recurrence.
+                if ($this->hasRecurEnd() &&
+                    $this->recurEnd->compareDateTime($start) < 0) {
+                    return false;
+                }
+                if ($start->isValid()) {
+                    return $start;
+                }
+
+                // Add the recurrence interval.
+                $start->month += $this->recurInterval;
+            } while (true);
+
+            break;
+            
         case HORDE_DATE_RECUR_YEARLY_DATE:
             // Start with the start date of the event.
             $estart = new Horde_Date($this->start);
@@ -654,8 +709,8 @@
     /**
      * 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 $year   The year of the exception.
+     * @param integer $month  The month of the exception.
      * @param integer $mday   The day of the month of the exception.
      */
     function addException($year, $month, $mday)
@@ -666,8 +721,8 @@
     /**
      * 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 $year   The year of the exception.
+     * @param integer $month  The month of the exception.
      * @param integer $mday   The day of the month of the exception.
      */
     function deleteException($year, $month, $mday)
@@ -679,11 +734,11 @@
     }
 
     /**
-     * Checks if an exception exists for a given reccurence of an event.
+     * Checks if an exception exists for a given recurrence 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.
+     * @param integer $year   The year of the recurrence.
+     * @param integer $month  The month of the recurrence.
+     * @param integer $mday   The day of the month of the recurrence.
      *
      * @return boolean  True if an exception exists for the given date.
      */
@@ -770,6 +825,8 @@
         case 'MD':
             $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
             break;
+            
+        // Missing HORDE_DATE_RECUR_MONTHLY_END because there is no definite case for that situation
 
         case 'YM':
             $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE);
@@ -849,6 +906,11 @@
             $rrule = 'MP' . $this->recurInterval . ' ' . $p . '+ ' . $vcaldays[$this->start->dayOfWeek()];
             break;
 
+        // We do have HORDE_DATE_RECUR_MONTHLY_END when exporting
+        case HORDE_DATE_RECUR_MONTHLY_END:
+            $rrule = 'MD' . $this->recurInterval . ' ' . 'LD';
+            break;
+            
         case HORDE_DATE_RECUR_YEARLY_DATE:
             $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month);
             break;
@@ -928,9 +990,10 @@
                 if (isset($rdata['BYDAY'])) {
                     $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY);
                 } else {
-                    $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
+	                $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE);
                 }
                 break;
+            // Missing HORDE_DATE_RECUR_MONTHLY_END because there is no definite case for that situation
 
             case 'YEARLY':
                 if (isset($rdata['BYYEARDAY'])) {
@@ -1011,6 +1074,11 @@
                 . $vcaldays[$this->start->dayOfWeek()];
             break;
 
+        // We do have HORDE_DATE_RECUR_MONTHLY_END when exporting
+        case HORDE_DATE_RECUR_MONTHLY_END:
+            $rrule = 'FREQ=MONTHLY;BYMONTHDAY=-1';
+            break;
+
         case HORDE_DATE_RECUR_YEARLY_DATE:
             $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval;
             break;