protected function parse($date, $tz, $format)
 {
     $array = date_format_patterns();
     foreach ($array as $key => $value) {
         $patterns[] = "`(^|[^\\\\\\\\])" . $key . "`";
         // the letter with no preceding '\'
         $repl1[] = '${1}(.)';
         // a single character
         $repl2[] = '${1}(' . $value . ')';
         // the
     }
     $patterns[] = "`\\\\\\\\([" . implode(array_keys($array)) . "])`";
     $repl1[] = '${1}';
     $repl2[] = '${1}';
     $format_regexp = preg_quote($format);
     // extract letters
     $regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
     $regex1 = str_replace('A', '(.)', $regex1);
     $regex1 = str_replace('a', '(.)', $regex1);
     preg_match('`^' . $regex1 . '$`', stripslashes($format), $letters);
     array_shift($letters);
     // extract values
     $regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
     $regex2 = str_replace('A', '(AM|PM)', $regex2);
     $regex2 = str_replace('a', '(am|pm)', $regex2);
     preg_match('`^' . $regex2 . '$`', $date, $values);
     array_shift($values);
     // if we did not find all the values for the patterns in the format, abort
     if (count($letters) != count($values)) {
         return FALSE;
     }
     $this->granularity = array();
     $final_date = array('hour' => 0, 'minute' => 0, 'second' => 0, 'month' => 1, 'day' => 1, 'year' => 0);
     foreach ($letters as $i => $letter) {
         $value = $values[$i];
         switch ($letter) {
             case 'd':
             case 'j':
                 $final_date['day'] = intval($value);
                 $this->addGranularity('day');
                 break;
             case 'n':
             case 'm':
                 $final_date['month'] = intval($value);
                 $this->addGranularity('month');
                 break;
             case 'F':
                 $array_month_long = array_flip(date_month_names());
                 $final_date['month'] = $array_month_long[$value];
                 $this->addGranularity('month');
                 break;
             case 'M':
                 $array_month = array_flip(date_month_names_abbr());
                 $final_date['month'] = $array_month[$value];
                 $this->addGranularity('month');
                 break;
             case 'Y':
                 $final_date['year'] = $value;
                 $this->addGranularity('year');
                 if (strlen($value) < 4) {
                     $this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.');
                 }
                 break;
             case 'y':
                 $year = $value;
                 // if no century, we add the current one ("06" => "2006")
                 $final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT);
                 $this->addGranularity('year');
                 break;
             case 'a':
             case 'A':
                 $ampm = strtolower($value);
                 break;
             case 'g':
             case 'h':
             case 'G':
             case 'H':
                 $final_date['hour'] = intval($value);
                 $this->addGranularity('hour');
                 break;
             case 'i':
                 $final_date['minute'] = intval($value);
                 $this->addGranularity('minute');
                 break;
             case 's':
                 $final_date['second'] = intval($value);
                 $this->addGranularity('second');
                 break;
             case 'U':
                 parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC"));
                 $this->addGranularity('year');
                 $this->addGranularity('month');
                 $this->addGranularity('day');
                 $this->addGranularity('hour');
                 $this->addGranularity('minute');
                 $this->addGranularity('second');
                 return $this;
                 break;
         }
     }
     if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
         $final_date['hour'] += 12;
     } elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
         $final_date['hour'] -= 12;
     }
     // Blank becomes current time, given TZ.
     parent::__construct('', $tz ? $tz : new DateTimeZone("UTC"));
     if ($tz) {
         $this->addGranularity('timezone');
     }
     // SetDate expects an integer value for the year, results can
     // be unexpected if we feed it something like '0100' or '0000';
     $final_date['year'] = intval($final_date['year']);
     $this->errors += $this->arrayErrors($final_date);
     $granularity = drupal_map_assoc($this->granularity);
     // If the input value is '0000-00-00', PHP's date class will later incorrectly convert
     // it to something like '-0001-11-30' if we do setDate() here. If we don't do
     // setDate() here, it will default to the current date and we will lose any way to
     // tell that there was no date in the orignal input values. So set a flag we can use
     // later to tell that this date object was created using only time and that the date
     // values are artifical.
     if (empty($final_date['year']) && empty($final_date['month']) && empty($final_date['day'])) {
         $this->timeOnly = TRUE;
     } elseif (empty($this->errors)) {
         // setDate() expects a valid year, month, and day.
         // Set some defaults for dates that don't use this to
         // keep PHP from interpreting it as the last day of
         // the previous month or last month of the previous year.
         if (empty($granularity['month'])) {
             $final_date['month'] = 1;
         }
         if (empty($granularity['day'])) {
             $final_date['day'] = 1;
         }
         $this->setDate($final_date['year'], $final_date['month'], $final_date['day']);
     }
     if (!isset($final_date['hour']) && !isset($final_date['minute']) && !isset($final_date['second'])) {
         $this->dateOnly = TRUE;
     } elseif (empty($this->errors)) {
         $this->setTime($final_date['hour'], $final_date['minute'], $final_date['second']);
     }
     return $this;
 }
/**
 * Custom repeat rule theme function for dates.
 *
 * @see date_repeat_rrule_description().
 */
function ringstedtheme_repeat_rrule_description($rrule, $format = 'd.M')
{
    // Empty or invalid value.
    if (empty($rrule) || !strstr($rrule, 'RRULE')) {
        return;
    }
    module_load_include('inc', 'date_api', 'date_api_ical');
    module_load_include('inc', 'date_repeat', 'date_repeat_calc');
    $parts = date_repeat_split_rrule($rrule);
    $rrule = $parts[0];
    if ($rrule['FREQ'] == 'NONE') {
        return;
    }
    // Make sure there will be an empty description for any unused parts.
    $description = array('!interval' => '', '!byday' => '', '!bymonth' => '', '!count' => '', '!until' => '', '!except' => '', '!additional' => '', '!week_starts_on' => '');
    $interval = date_repeat_interval_options();
    switch ($rrule['FREQ']) {
        case 'WEEKLY':
            if (isset($rrule['BYDAY'])) {
                $description['!interval'] = format_plural($rrule['INTERVAL'], '', 'every @count weeks') . ' ';
            } else {
                $description['!interval'] = format_plural($rrule['INTERVAL'], 'every week', 'every @count weeks') . ' ';
            }
            break;
        case 'MONTHLY':
            $description['!interval'] = format_plural($rrule['INTERVAL'], 'every month', 'every @count months') . ' ';
            break;
        case 'YEARLY':
            $description['!interval'] = format_plural($rrule['INTERVAL'], 'every year', 'every @count years') . ' ';
            break;
        default:
            $description['!interval'] = format_plural($rrule['INTERVAL'], 'every day', 'every @count days') . ' ';
            break;
    }
    if (!empty($rrule['BYDAY'])) {
        $days = date_repeat_dow_day_options();
        $counts = date_repeat_dow_count_options();
        $results = array();
        $first_day = $rrule['BYDAY'][0];
        foreach ($rrule['BYDAY'] as $byday) {
            // Get the numeric part of the BYDAY option, i.e. +3 from +3MO.
            $day = substr($byday, -2);
            $count = str_replace($day, '', $byday);
            if (!empty($count)) {
                // See if there is a 'pretty' option for this count, i.e. +1 => First.
                $order = array_key_exists($count, $counts) ? strtolower($counts[$count]) : $count;
                $results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', array('!repeats_every_interval ' => '', '!date_order' => $order, '!day_of_week' => strtolower($days[$day]))));
            } else {
                if ($first_day === $byday) {
                    $results[] = trim(t('!repeats_every_interval every  !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => strtolower($days[$day]))));
                } else {
                    $results[] = trim(t('!repeats_every_interval !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => strtolower($days[$day]))));
                }
            }
        }
        $days_count = count($rrule['BYDAY']);
        if ($days_count > 2) {
            $last_day = array_pop($results);
            $description['!byday'] = implode(' , ', $results);
            $description['!byday'] .= ' ' . t('and') . ' ' . $last_day;
        } else {
            $description['!byday'] = implode(' ' . t('and') . ' ', $results);
        }
    }
    if (!empty($rrule['BYMONTH'])) {
        if (sizeof($rrule['BYMONTH']) < 12) {
            $results = array();
            $months = date_month_names();
            foreach ($rrule['BYMONTH'] as $month) {
                $results[] = $months[$month];
            }
            if (!empty($rrule['BYMONTHDAY'])) {
                $description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', array('!repeats_every_interval ' => '', '!month_days' => implode(', ', $rrule['BYMONTHDAY']), '!month_names' => implode(', ', $results))));
            } else {
                $description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', array('!repeats_every_interval ' => '', '!month_names' => implode(', ', $results))));
            }
        }
    }
    if ($rrule['INTERVAL'] < 1) {
        $rrule['INTERVAL'] = 1;
    }
    if (!empty($rrule['COUNT'])) {
        $description['!count'] = trim(t('!repeats_every_interval !count times', array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT'])));
    }
    if (!empty($rrule['UNTIL'])) {
        $until = date_ical_date($rrule['UNTIL'], 'UTC');
        date_timezone_set($until, date_default_timezone_object());
        $description['!until'] = trim(t('!repeats_every_interval until !until_date', array('!repeats_every_interval ' => '', '!until_date' => strtolower(date_format_date($until, 'custom', $format)))));
    }
    if (!empty($rrule['WKST'])) {
        $day_names = date_repeat_dow_day_options();
        $description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])])));
    }
    return preg_replace('/\\s*\\./', '.', t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description));
}