Ignore:
Timestamp:
12/17/12 14:22:24 (11 years ago)
Author:
cristiano
Message:

Ticket #3239 - Inconsistência na importação de eventos com repetição

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/prototype/plugins/icalcreator/iCalcreator.class.php

    r7655 r7660  
    22/*********************************************************************************/ 
    33/** 
    4  * iCalcreator class v2.10.5 
    5  * copyright (c) 2007-2011 Kjell-Inge Gustafsson kigkonsult 
    6  * www.kigkonsult.se/iCalcreator/index.php 
     4 * iCalcreator v2.16.1 
     5 * copyright (c) 2007-2012 Kjell-Inge Gustafsson kigkonsult 
     6 * kigkonsult.se/iCalcreator/index.php 
    77 * ical@kigkonsult.se 
    88 * 
    99 * Description: 
    10  * This file is a PHP implementation of RFC 2445. 
     10 * This file is a PHP implementation of rfc2445/rfc5545. 
    1111 * 
    1212 * This library is free software; you can redistribute it and/or 
     
    4545*/ 
    4646/*********************************************************************************/ 
    47 /*         only for phpversion 5.1 and later,                                    */ 
    48 /*         date management, default timezone setting                             */ 
    49 /*         since 2.6.36 - 2010-12-31 */ 
    50 if( substr( phpversion(), 0, 3 ) >= '5.1' ) 
    51   // && ( 'UTC' == date_default_timezone_get())) 
    52   date_default_timezone_set( 'Europe/Stockholm' ); 
    53 /*********************************************************************************/ 
    54 /*         since 2.6.22 - 2010-09-25, do NOT remove!!                            */ 
    55 require_once ROOTPATH.'/plugins/icalcreator/iCalUtilityFunctions.class.php'; 
    56 /*********************************************************************************/ 
    5747/*         version, do NOT remove!!                                              */ 
    58 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.10.5' ); 
     48define( 'ICALCREATOR_VERSION', 'iCalcreator 2.16.1' ); 
    5949/*********************************************************************************/ 
    6050/*********************************************************************************/ 
     
    6555 * @since 2.9.6 - 2011-05-14 
    6656 */ 
    67 class icalCreator { 
     57class vcalendar { 
    6858            //  calendar property variables 
    6959  var $calscale; 
     
    9888 * @return void 
    9989 */ 
    100   function icalCreator ( $config = array()) { 
     90  function vcalendar ( $config = array()) { 
    10191    $this->_makeVersion(); 
    10292    $this->calscale   = null; 
     
    132122 * 
    133123 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    134  * @since 2.4.8 - 2008-10-21 
     124 * @since 2.10.16 - 2011-10-28 
    135125 * @return string 
    136126 */ 
     
    139129    switch( $this->format ) { 
    140130      case 'xcal': 
    141         return ' calscale="'.$this->calscale.'"'.$this->nl; 
     131        return $this->nl.' calscale="'.$this->calscale.'"'; 
    142132        break; 
    143133      default: 
     
    166156 * 
    167157 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    168  * @since 0.9.7 - 2006-11-20 
     158 * @since 2.10.16 - 2011-10-28 
    169159 * @return string 
    170160 */ 
     
    173163    switch( $this->format ) { 
    174164      case 'xcal': 
    175         return ' method="'.$this->method.'"'.$this->nl; 
     165        return $this->nl.' method="'.$this->method.'"'; 
    176166        break; 
    177167      default: 
     
    205195 * 
    206196 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    207  * @since 0.9.7 - 2006-11-20 
     197 * @since 2.12.11 - 2012-05-13 
    208198 * @return string 
    209199 */ 
     
    213203    switch( $this->format ) { 
    214204      case 'xcal': 
    215         return ' prodid="'.$this->prodid.'"'.$this->nl; 
     205        return $this->nl.' prodid="'.$this->prodid.'"'; 
    216206        break; 
    217207      default: 
    218         return 'PRODID:'.$this->prodid.$this->nl; 
     208        $toolbox = new calendarComponent(); 
     209        $toolbox->setConfig( $this->getConfig()); 
     210        return $toolbox->_createElement( 'PRODID', '', $this->prodid ); 
    219211        break; 
    220212    } 
     
    257249 * 
    258250 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    259  * @since 0.9.7 - 2006-11-20 
     251 * @since 2.10.16 - 2011-10-28 
    260252 * @return string 
    261253 */ 
     
    265257    switch( $this->format ) { 
    266258      case 'xcal': 
    267         return ' version="'.$this->version.'"'.$this->nl; 
     259        return $this->nl.' version="'.$this->version.'"'; 
    268260        break; 
    269261      default: 
     
    303295 * 
    304296 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    305  * @since 2.9.3 - 2011-05-14 
     297 * @since 2.10.16 - 2011-11-01 
    306298 * @return string 
    307299 */ 
    308300  function createXprop() { 
    309     if( 'xcal' == $this->format ) 
    310       return false; 
    311301    if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE; 
    312302    $output = null; 
     
    327317        $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] ); 
    328318      $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] ); 
     319      if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) { 
     320        foreach( $toolbox->xcaldecl as $localxcaldecl ) 
     321          $this->xcaldecl[] = $localxcaldecl; 
     322      } 
    329323    } 
    330324    return $output; 
     
    334328 * 
    335329 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    336  * @since 2.9.3 - 2011-05-14 
     330 * @since 2.11.9 - 2012-01-16 
    337331 * @param string $label 
    338332 * @param string $value 
     
    341335 */ 
    342336  function setXprop( $label, $value, $params=FALSE ) { 
    343     if( empty( $label )) return FALSE; 
     337    if( empty( $label )) 
     338      return FALSE; 
     339    if( 'X-' != strtoupper( substr( $label, 0, 2 ))) 
     340      return FALSE; 
    344341    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; 
    345342    $xprop           = array( 'value' => $value ); 
     
    393390            if( $propix != $xpropno ) 
    394391              $reduced[$xpropkey] = $xpropvalue; 
    395             ++$xpropno; 
     392            $xpropno++; 
    396393          } 
    397394        } 
     
    409406 * 
    410407 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    411  * @since 2.8.8 - 2011-04-16 
     408 * @since 2.13.4 - 2012-08-08 
    412409 * @param string $propName, optional 
    413410 * @param int $propix, optional, if specific property is wanted in case of multiply occurences 
     
    419416    if( 'X-PROP' == $propName ) { 
    420417      if( !$propix ) 
    421         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1; 
     418        $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1; 
    422419      $this->propix[$propName] = --$propix; 
    423420    } 
     421    else 
     422      $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' ); 
    424423    switch( $propName ) { 
    425424      case 'ATTENDEE': 
    426425      case 'CATEGORIES': 
     426      case 'CONTACT': 
    427427      case 'DTSTART': 
     428      case 'GEOLOCATION': 
    428429      case 'LOCATION': 
    429430      case 'ORGANIZER': 
     
    433434      case 'SUMMARY': 
    434435      case 'RECURRENCE-ID-UID': 
     436      case 'RELATED-TO': 
    435437      case 'R-UID': 
    436438      case 'UID': 
    437         $output = array(); 
     439      case 'URL': 
     440        $output  = array(); 
    438441        foreach ( $this->components as $cix => $component) { 
    439442          if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) 
    440443            continue; 
    441           if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { 
     444          if( in_array( strtoupper( $propName ), $mProps )) { 
    442445            $component->_getProperties( $propName, $output ); 
    443446            continue; 
     
    447450              $content = $component->getProperty( 'UID' ); 
    448451          } 
     452          elseif( 'GEOLOCATION' == $propName ) { 
     453            $content = $component->getProperty( 'LOCATION' ); 
     454            $content = ( !empty( $content )) ? $content.' ' : ''; 
     455            if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo )) 
     456              continue; 
     457            if( 0.0 < $geo['latitude'] ) 
     458              $sign   = '+'; 
     459            else 
     460              $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : ''; 
     461            $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] )); 
     462            $content  = rtrim( rtrim( $content, '0' ), '.' ); 
     463            if( 0.0 < $geo['longitude'] ) 
     464              $sign   = '+'; 
     465            else 
     466              $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : ''; 
     467            $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/'; 
     468          } 
    449469          elseif( FALSE === ( $content = $component->getProperty( $propName ))) 
    450470            continue; 
    451           if( FALSE === $content ) 
     471          if(( FALSE === $content ) || empty( $content )) 
    452472            continue; 
    453473          elseif( is_array( $content )) { 
     
    477497        return $output; 
    478498        break; 
    479  
    480499      case 'CALSCALE': 
    481500        return ( !empty( $this->calscale )) ? $this->calscale : FALSE; 
     
    506525                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] ); 
    507526            else 
    508               ++$xpropno; 
     527              $xpropno++; 
    509528          } 
    510529          unset( $this->propix[$propName] ); 
     
    549568 * 
    550569 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    551  * @since 2.9.6 - 2011-05-14 
     570 * @since 2.11.7 - 2012-01-12 
    552571 * @param mixed $config 
    553572 * @return value 
     
    593612        break; 
    594613      case 'DIRECTORY': 
    595         if( empty( $this->directory )) 
     614        if( empty( $this->directory ) && ( '0' != $this->directory )) 
    596615          $this->directory = '.'; 
    597616        return $this->directory; 
     
    606625        break; 
    607626      case 'FILENAME': 
    608         if( empty( $this->filename )) { 
     627        if( empty( $this->filename ) && ( '0' != $this->filename )) { 
    609628          if( 'xcal' == $this->format ) 
    610629            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. . 
     
    653672 * 
    654673 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    655  * @since 2.9.6 - 2011-05-14 
     674 * @since 2.12.12 - 2012-05-13 
    656675 * @param mixed  $config 
    657676 * @param string $value 
     
    660679  function setConfig( $config, $value = FALSE) { 
    661680    if( is_array( $config )) { 
     681      $ak = array_keys( $config ); 
     682      foreach( $ak as $k ) { 
     683        if( 'DIRECTORY' == strtoupper( $k )) { 
     684          if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] )) 
     685            return FALSE; 
     686          unset( $config[$k] ); 
     687        } 
     688        elseif( 'NEWLINECHAR' == strtoupper( $k )) { 
     689          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] )) 
     690            return FALSE; 
     691          unset( $config[$k] ); 
     692        } 
     693      } 
    662694      foreach( $config as $cKey => $cValue ) { 
    663695        if( FALSE === $this->setConfig( $cKey, $cValue )) 
     
    733765        $res = TRUE; 
    734766        break; 
    735       case 'LANGUAGE': 
    736          // set language for calendar component as defined in [RFC 1766] 
     767      case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766] 
    737768        $value   = trim( $value ); 
    738769        $this->language = $value; 
     770        $this->_makeProdid(); 
    739771        $subcfg  = array( 'LANGUAGE' => $value ); 
    740772        $res = TRUE; 
     
    743775      case 'NEWLINECHAR': 
    744776        $this->nl = $value; 
     777        if( 'xcal' == $value ) { 
     778          $this->attributeDelimiter = $this->nl; 
     779          $this->valueInit          = null; 
     780        } 
     781        else { 
     782          $this->attributeDelimiter = ';'; 
     783          $this->valueInit          = ':'; 
     784        } 
    745785        $subcfg  = array( 'NL' => $value ); 
    746786        $res = TRUE; 
     
    754794        $value   = trim( $value ); 
    755795        $this->unique_id = $value; 
     796        $this->_makeProdid(); 
    756797        $subcfg  = array( 'UNIQUE_ID' => $value ); 
    757798        $res = TRUE; 
     
    829870          return TRUE; 
    830871        } 
    831         ++$cix1dC; 
     872        $cix1dC++; 
    832873      } 
    833874      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { 
     
    842883 * 
    843884 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    844  * @since 2.9.1 - 2011-04-16 
     885 * @since 2.13.5 - 2012-08-08 
    845886 * @param mixed $arg1 optional, ordno/component type/ component uid 
    846887 * @param mixed $arg2 optional, ordno if arg1 = component type 
     
    862903      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1; 
    863904      $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' ); 
    864       $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' ); 
     905      $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' ); 
     906      $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' ); 
    865907    } 
    866908    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name 
     
    891933        if( $index == $cix1gC ) 
    892934          return $component->copy(); 
    893         ++$cix1gC; 
     935        $cix1gC++; 
    894936      } 
    895937      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) 
    896         $hit = FALSE; 
     938        $hit = array(); 
    897939        foreach( $arg1 as $pName => $pValue ) { 
    898940          $pName = strtoupper( $pName ); 
    899941          if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps )) 
    900942            continue; 
    901           if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur 
     943          if( in_array( $pName, $mProps )) { // multiple occurrence 
    902944            $propValues = array(); 
    903945            $component->_getProperties( $pName, $propValues ); 
    904946            $propValues = array_keys( $propValues ); 
    905             $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE; 
     947            $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE; 
    906948            continue; 
    907           } // end   if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur 
    908           if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency 
    909             $hit = FALSE; // missing property 
     949          } // end   if(.. .// multiple occurrence 
     950          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence 
     951            $hit[] = FALSE; // missing property 
    910952            continue; 
    911953          } 
    912954          if( 'SUMMARY' == $pName ) { // exists within (any case) 
    913             $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE; 
     955            $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE; 
    914956            continue; 
    915957          } 
     
    925967                $pValue = substr( $pValue, 0, 8 ); 
    926968            } 
    927             $hit = ( $pValue == $valuedate ) ? TRUE : FALSE; 
     969            $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE; 
    928970            continue; 
    929971          } 
     
    934976            foreach( $part as $subPart ) { 
    935977              if( $pValue == $subPart ) { 
    936                 $hit = TRUE; 
    937                 continue 2; 
     978                $hit[] = TRUE; 
     979                continue 3; 
    938980              } 
    939981            } 
    940           } 
    941           $hit = FALSE; // no hit in property 
     982          } // end foreach( $value as $part ) 
     983          $hit[] = FALSE; // no hit in property 
    942984        } // end  foreach( $arg1 as $pName => $pValue ) 
    943         if( $hit ) { 
     985        if( in_array( TRUE, $hit )) { 
    944986          if( $index == $cix1gC ) 
    945987            return $component->copy(); 
    946           ++$cix1gC; 
     988          $cix1gC++; 
    947989        } 
    948990      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) 
     
    950992        if( $index == $cix1gC ) 
    951993          return $component->copy(); 
    952         ++$cix1gC; 
     994        $cix1gC++; 
    953995      } 
    954996    } // end foreach ( $this->components.. . 
     
    10031045 * 
    10041046 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1005  * @since 2.9.7 - 2011-06-04 
    1006  * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions 
    1007  * @param int $startM optional, start Month, default current Month 
    1008  * @param int $startD optional,  start Day, default current Day 
    1009  * @param int $endY optional,    end Year, default $startY 
    1010  * @param int $endY optional,    end Month, default $startM 
    1011  * @param int $endY optional,    end Day, default $startD 
    1012  * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s) 
    1013  * @param bool $flat optional,  FALSE (default) => output : array[Year][Month][Day][] 
    1014  *                               TRUE => output : array[] (ignores split) 
    1015  * @param bool $any optional,    TRUE (default) - select component that take place within period 
    1016  *                               FALSE - only components that starts within period 
    1017  * @param bool $split optional,  TRUE (default) - one component copy every day it take place during the 
    1018  *                                       period (implies flat=FALSE) 
    1019  *                               FALSE - one occurance of component only in output array 
     1047 * @since 2.11.22 - 2012-02-13 
     1048 * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] ) 
     1049 * @param int   $startM optional, start Month, default current Month 
     1050 * @param int   $startD optional, start Day,  default current Day 
     1051 * @param int   $endY   optional, end   Year, default $startY 
     1052 * @param int   $endY   optional, end  Month, default $startM 
     1053 * @param int   $endY   optional, end   Day,  default $startD 
     1054 * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s) 
     1055 * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][] 
     1056 *                                TRUE            => output : array[] (ignores split) 
     1057 * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period 
     1058 *                                FALSE          - only component(-s) that starts within period 
     1059 * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the 
     1060 *                                                 period (implies flat=FALSE) 
     1061 *                                FALSE          - one occurance of component only in output array 
    10201062 * @return array or FALSE 
    10211063 */ 
     
    10561098    if( 0 >= count( $cType )) 
    10571099      $cType = $validTypes; 
     1100    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination 
     1101      $split = FALSE; 
    10581102    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination 
    10591103      $split = FALSE; 
     
    10641108      unset( $start ); 
    10651109            /* deselect unvalid type components */ 
    1066       if( !in_array( $component->objName, $cType )) continue; 
     1110      if( !in_array( $component->objName, $cType )) 
     1111        continue; 
    10671112      $start = $component->getProperty( 'dtstart' ); 
    10681113            /* select due when dtstart is missing */ 
    10691114      if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' )))) 
    10701115        continue; 
    1071       $dtendExist = $dueExist = $durationExist = $endAllDayEvent = FALSE; 
     1116      if( empty( $start )) 
     1117        continue; 
     1118      $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE; 
    10721119      unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up 
    10731120      $startWdate = iCalUtilityFunctions::_date2timestamp( $start ); 
     
    11131160        $endWdate = iCalUtilityFunctions::_date2timestamp( $end ); 
    11141161      } 
    1115       $rdurWsecs  = $endWdate - $startWdate; // compute component duration in seconds 
     1162      $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds 
    11161163            /* make a list of optional exclude dates for component occurence from exrule and exdate */ 
    11171164      $exdatelist = array(); 
     
    11231170        foreach( $exdate as $theExdate ) { 
    11241171          $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate ); 
    1125           $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate ) ); // on a day-basis !!! 
     1172          $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!! 
    11261173          if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate )) 
    11271174            $exdatelist[$exWdate] = TRUE; 
    1128         } 
    1129       } 
    1130             /* if 'any' components, check repeating components, removing all excluding dates */ 
     1175        } // end - foreach( $exdate as $theExdate ) 
     1176      }  // end - check exdate 
     1177      $compUID    = $component->getProperty( 'UID' ); 
     1178            /* check recurrence-id (with sequence), remove hit with reccurr-id date */ 
     1179      if(( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) && 
     1180         ( FALSE !== ( $sequence = $component->getProperty( 'sequence' )))   ) { 
     1181        $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid ); 
     1182        $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!! 
     1183        $endD     = $recurrid + $rdurWsecs; 
     1184        do { 
     1185          if( date( 'Ymd', $startWdate ) != date( 'Ymd', $recurrid )) 
     1186            $exdatelist[$recurrid] = TRUE; // exclude all other days than startdate 
     1187          $wd = getdate( $recurrid ); 
     1188          if( isset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] )) 
     1189              unset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ); // remove from output, dtstart etc added below 
     1190          if( $split && ( $recurrid <= $endD )) 
     1191            $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ) + 1, date( 'Y', $recurrid )); // step one day 
     1192          else 
     1193            break; 
     1194        } while( TRUE ); 
     1195      } // end recurrence-id/sequence test 
     1196            /* select only components with.. . */ 
     1197      if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period 
     1198         (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period 
     1199            /* add the selected component (WITHIN valid dates) to output array */ 
     1200        if( $flat ) { // any=true/false, ignores split 
     1201          if( !$recurrid ) 
     1202            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id) 
     1203        } 
     1204        elseif( $split ) { // split the original component 
     1205          if( $endWdate > $endDate ) 
     1206            $endWdate = $endDate;     // use period end date 
     1207          $rstart   = $startWdate; 
     1208          if( $rstart < $startDate ) 
     1209            $rstart = $startDate; // use period start date 
     1210          $startYMD = date( 'Ymd', $rstart ); 
     1211          $endYMD   = date( 'Ymd', $endWdate ); 
     1212          $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
     1213          while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate 
     1214            $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
     1215            if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist 
     1216              $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day 
     1217              continue; 
     1218            } 
     1219            if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart 
     1220              $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ))); 
     1221            else 
     1222              $datestring = date( $startDateFormat, $rstart ); 
     1223            if( isset( $start['tz'] )) 
     1224              $datestring .= ' '.$start['tz']; 
     1225// echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component->setProperty( 'X-CNT', $tcnt ); // test ### 
     1226            $component->setProperty( 'X-CURRENT-DTSTART', $datestring ); 
     1227            if( $dtendExist || $dueExist || $durationExist ) { 
     1228              if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day 
     1229                $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); 
     1230              else 
     1231                $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
     1232              if( $endAllDayEvent && $dtendExist ) 
     1233                $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day 
     1234              $datestring = date( $endDateFormat, $tend ); 
     1235              if( isset( $end['tz'] )) 
     1236                $datestring .= ' '.$end['tz']; 
     1237              $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; 
     1238              $component->setProperty( $propName, $datestring ); 
     1239            } // end if( $dtendExist || $dueExist || $durationExist ) 
     1240            $wd = getdate( $rstart ); 
     1241            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output 
     1242            $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day 
     1243          } // end while( $rstart <= $endWdate ) 
     1244        } // end if( $split )   -  else use component date 
     1245        elseif( $recurrid && !$flat && !$any && !$split ) 
     1246          $continue = TRUE; 
     1247        else { // !$flat && !$split, i.e. no flat array and DTSTART within period 
     1248          $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!! 
     1249          if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist 
     1250            $wd = getdate( $startWdate ); 
     1251            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output 
     1252          } 
     1253        } 
     1254      } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) 
     1255 
     1256            /* if 'any' components, check components with reccurrence rules, removing all excluding dates */ 
    11311257      if( TRUE === $any ) { 
    11321258            /* make a list of optional repeating dates for component occurence, rrule, rdate */ 
     
    11601286            } 
    11611287          } 
    1162         } 
     1288        }  // end - check rdate 
    11631289        if( 0 < count( $recurlist )) { 
    11641290          ksort( $recurlist ); 
    11651291          $xRecurrence = 1; 
     1292          $component2  = $component->copy(); 
     1293          $compUID     = $component2->getProperty( 'UID' ); 
    11661294          foreach( $recurlist as $recurkey => $durvalue ) { 
    11671295// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###; 
     
    11731301            if( $startWdate >= $recurkey ) // exclude component start date 
    11741302              continue; 
    1175             $component2   = $component->copy(); 
    11761303            $rstart = $recurkey; 
    11771304            $rend   = $recurkey + $durvalue; 
    11781305           /* add repeating components within valid dates to output array, only start date set */ 
    11791306            if( $flat ) { 
    1180               $datestring = date( $startDateFormat, $recurkey ); 
    1181               if( isset( $start['tz'] )) 
    1182                 $datestring .= ' '.$start['tz']; 
    1183 // echo "X-CURRENT-DTSTART 0 =$datestring tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ### 
    1184               $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); 
    1185               if( $dtendExist || $dueExist || $durationExist ) { 
    1186                 $datestring = date( $endDateFormat, $recurkey + $durvalue );   // fixa korrekt sluttid 
    1187                 if( isset( $end['tz'] )) 
    1188                   $datestring .= ' '.$end['tz']; 
    1189                 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; 
    1190                 $component2->setProperty( $propName, $datestring ); 
    1191               } // end if( $dtendExist || $dueExist || $durationExist ) 
    1192               $component2->setProperty( 'X-RECURRENCE', ++$xRecurrence ); 
    1193               $result[$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output 
     1307              if( !isset( $result[$compUID] )) // only one comp 
     1308                $result[$compUID] = $component2->copy(); // copy to output 
    11941309            } 
    11951310           /* add repeating components within valid dates to output array, one each day */ 
     
    12191334                    else 
    12201335                      $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
     1336                    if( $endAllDayEvent && $dtendExist ) 
     1337                      $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day 
    12211338                    $datestring = date( $endDateFormat, $tend ); 
    12221339                    if( isset( $end['tz'] )) 
     
    12271344                  $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); 
    12281345                  $wd = getdate( $rstart ); 
    1229                   $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output 
     1346                  $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output 
    12301347                } // end if( $checkDate > $startYMD ) {    // date after dtstart 
    12311348                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day 
     
    12331350              $xRecurrence += 1; 
    12341351            } // end elseif( $split ) 
    1235             elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE *// 
     1352            elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *// 
    12361353              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
    12371354              if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist 
     
    12431360                $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); 
    12441361                if( $dtendExist || $dueExist || $durationExist ) { 
    1245                   $rstart += $rdurWsecs; 
    1246                   if( date( 'Ymd', $rstart ) < date( 'Ymd', $endWdate )) 
    1247                     $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); 
     1362                  $tend = $rstart + $rdurWsecs; 
     1363                  if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate )) 
     1364                    $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend )); 
    12481365                  else 
    1249                     $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
     1366                    $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!! 
     1367                  if( $endAllDayEvent && $dtendExist ) 
     1368                    $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day 
    12501369                  $datestring = date( $endDateFormat, $tend ); 
    12511370                  if( isset( $end['tz'] )) 
     
    12561375                $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); 
    12571376                $wd = getdate( $rstart ); 
    1258                 $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output 
     1377                $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output 
    12591378              } // end if( !isset( $exdatelist[$checkDate] )) 
    12601379            } // end elseif( $rstart >= $startDate ) 
     
    12651384          continue; 
    12661385      } // end if( TRUE === $any ) 
    1267             /* deselect components with startdate not within period */ 
    1268       elseif(( $startWdate < $startDate ) || ( $startWdate > $endDate )) 
    1269         continue; 
    1270             /* add the selected component (WITHIN valid dates) to output array */ 
    1271       if( $flat ) 
    1272         $result[$component->getProperty( 'UID' )] = $component->copy(); // copy to output; 
    1273       elseif( $split ) { // split the original component 
    1274         if( $endWdate > $endDate ) 
    1275           $endWdate = $endDate;     // use period end date 
    1276         $rstart = $startWdate; 
    1277         if( $rstart < $startDate ) 
    1278           $rstart = $startDate; // use period start date 
    1279         $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
    1280         if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist 
    1281           foreach( array( 'X-CURRENT-DTSTART','X-CURRENT-DTEND','X-CURRENT-DUE','X-RECURRENCE' ) as $propName ) 
    1282             $component->deleteProperty( $propName ); // remove any x-props, if set 
    1283           while( $rstart <= $endWdate ) { // iterate 
    1284             if( $rstart > $startWdate ) { // if NOT startdate, set X-properties 
    1285               $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ))); 
    1286               if( isset( $start['tz'] )) 
    1287                 $datestring .= ' '.$start['tz']; 
    1288 // echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component->setProperty( 'X-CNT', $tcnt ); // test ### 
    1289               $component->setProperty( 'X-CURRENT-DTSTART', $datestring ); 
    1290               if( $dtendExist || $dueExist || $durationExist ) { 
    1291                 if( date( 'Ymd', $rstart ) < date( 'Ymd', $endWdate )) 
    1292                   $tend = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); 
    1293                 else 
    1294                   $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! 
    1295                 $datestring = date( $endDateFormat, $tend ); 
    1296                 if( isset( $end['tz'] )) 
    1297                   $datestring .= ' '.$end['tz']; 
    1298                 $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; 
    1299                 $component->setProperty( $propName, $datestring ); 
    1300               } // end if( $dtendExist || $dueExist || $durationExist ) 
    1301             } // end if( $rstart > $startWdate ) 
    1302             $wd = getdate( $rstart ); 
    1303             $result[$wd['year']][$wd['mon']][$wd['mday']][$component->getProperty( 'UID' )] = $component->copy(); // copy to output 
    1304             $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day 
    1305           } // end while( $rstart <= $endWdate ) 
    1306         } // end if( !isset( $exdatelist[$checkDate] )) 
    1307       } // end if( $split )   -  else use component date 
    1308       elseif( $startWdate >= $startDate ) {          // within period 
    1309         $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!! 
    1310         if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist 
    1311           foreach( array( 'X-CURRENT-DTSTART','X-CURRENT-DTEND','X-CURRENT-DUE','X-RECURRENCE' ) as $propName ) 
    1312             $component->deleteProperty( $propName ); // remove any x-props, if set 
    1313           $wd = getdate( $startWdate ); 
    1314           $result[$wd['year']][$wd['mon']][$wd['mday']][$component->getProperty( 'UID' )] = $component->copy(); // copy to output 
    1315         } 
    1316       } 
    13171386    } // end foreach ( $this->components as $cix => $component ) 
    13181387    if( 0 >= count( $result )) return FALSE; 
     
    13201389      foreach( $result as $y => $yeararr ) { 
    13211390        foreach( $yeararr as $m => $montharr ) { 
    1322           foreach( $montharr as $d => $dayarr ) 
    1323             $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index 
    1324           ksort( $result[$y][$m] ); 
    1325         } 
    1326         ksort( $result[$y] ); 
    1327       } 
    1328       ksort( $result ); 
     1391          foreach( $montharr as $d => $dayarr ) { 
     1392            if( empty( $result[$y][$m][$d] )) 
     1393                unset( $result[$y][$m][$d] ); 
     1394            else 
     1395              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. . 
     1396          } 
     1397          if( empty( $result[$y][$m] )) 
     1398              unset( $result[$y][$m] ); 
     1399          else 
     1400            ksort( $result[$y][$m] ); 
     1401        } 
     1402        if( empty( $result[$y] )) 
     1403            unset( $result[$y] ); 
     1404        else 
     1405          ksort( $result[$y] ); 
     1406      } 
     1407      if( empty( $result )) 
     1408          unset( $result ); 
     1409      else 
     1410        ksort( $result ); 
    13291411    } // end elseif( !$flat ) 
     1412    if( 0 >= count( $result )) 
     1413      return FALSE; 
    13301414    return $result; 
    13311415  } 
    13321416/** 
    1333  * select components from calendar on based on Categories, Location, Resources and/or Summary 
    1334  * 
    1335  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1336  * @since 2.8.8 - 2011-05-03 
     1417 * select components from calendar on based on specific property value(-s) 
     1418 * 
     1419 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     1420 * @since 2.13.4 - 2012-08-07 
    13371421 * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName) 
    13381422 * @return array 
     
    13401424  function selectComponents2( $selectOptions ) { 
    13411425    $output = array(); 
    1342     $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' ); 
     1426    $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ); 
     1427    $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' ); 
    13431428    foreach( $this->components as $cix => $component3 ) { 
    1344       if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) 
     1429      if( !in_array( $component3->objName, $allowedComps )) 
    13451430        continue; 
    13461431      $uid = $component3->getProperty( 'UID' ); 
     
    13551440          continue; 
    13561441        } 
    1357         elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { 
     1442        elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence? 
    13581443          $propValues = array(); 
    13591444          $component3->_getProperties( $propName, $propValues ); 
     
    13661451          } 
    13671452          continue; 
    1368         } // end   elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) 
    1369         elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence 
     1453        } // end   elseif( // multiple occurrence? 
     1454        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence 
    13701455          continue; 
    13711456        if( is_array( $d )) { 
     
    14361521          return TRUE; 
    14371522        } 
    1438         ++$cix1sC; 
     1523        $cix1sC++; 
    14391524      } 
    14401525      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace 
     
    14561541 * 
    14571542 * ascending sort on properties (if exist) x-current-dtstart, dtstart, 
    1458  * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid 
    1459  * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES 
    1460  * 
    1461  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1462  * @since 2.8.4 - 2011-06-02 
     1543 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments, 
     1544 * otherwise sorting on specific (argument) property values 
     1545 * 
     1546 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     1547 * @since 2.13.4 - 2012-08-07 
    14631548 * @param string $sortArg, optional 
    14641549 * @return void 
     
    14691554      if( $sortArg ) { 
    14701555        $sortArg = strtoupper( $sortArg ); 
    1471         if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' ))) 
     1556        if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'URL' ))) 
    14721557          $sortArg = FALSE; 
    14731558      } 
     
    14811566        } 
    14821567        elseif( $sortArg ) { 
    1483           if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES' == $sortArg )) { 
     1568          if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) { 
    14841569            $propValues = array(); 
    14851570            $c->_getProperties( $sortArg, $propValues ); 
    1486             $c->srtk[0] = reset( array_keys( $propValues )); 
     1571            if( !empty( $propValues )) { 
     1572              $sk         = array_keys( $propValues ); 
     1573              $c->srtk[0] = $sk[0]; 
     1574              if( 'RELATED-TO'  == $sortArg ) 
     1575                $c->srtk[0] .= $c->getProperty( 'uid' ); 
     1576            } 
     1577            elseif( 'RELATED-TO'  == $sortArg ) 
     1578              $c->srtk[0] = $c->getProperty( 'uid' ); 
    14871579          } 
    14881580          elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) 
     
    14901582          continue; 
    14911583        } 
    1492         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) 
    1493           $c->srtk[0] = iCalUtilityFunctions::_date_time_string( $d[1] ); 
     1584        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) { 
     1585          $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] ); 
     1586          unset( $c->srtk[0]['unparsedtext'] ); 
     1587        } 
    14941588        elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' ))) 
    14951589          $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart 
    1496         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) 
    1497           $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration) 
     1590        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) { 
     1591          $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration) 
     1592          unset( $c->srtk[1]['unparsedtext'] ); 
     1593        } 
    14981594        elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) { 
    1499           if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) 
    1500             $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] ); 
     1595          if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) { 
     1596            $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] ); 
     1597            unset( $c->srtk[1]['unparsedtext'] ); 
     1598          } 
    15011599          elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' ))) 
    15021600            if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE ))) 
     
    15231621    elseif( 'vtimezone' == $b->objName )          return  1; 
    15241622    $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' ); 
    1525     for( $k = 0; $k < 4 ; ++$k ) { 
     1623    for( $k = 0; $k < 4 ; $k++ ) { 
    15261624      if(        empty( $a->srtk[$k] ))           return -1; 
    15271625      elseif(    empty( $b->srtk[$k] ))           return  1; 
     
    15291627        if( is_array( $b->srtk[$k] )) { 
    15301628          foreach( $sortkeys as $key ) { 
     1629            if    ( !isset( $a->srtk[$k][$key] )) return -1; 
     1630            elseif( !isset( $b->srtk[$k][$key] )) return  1; 
    15311631            if    (  empty( $a->srtk[$k][$key] )) return -1; 
    15321632            elseif(  empty( $b->srtk[$k][$key] )) return  1; 
     
    15511651 * 
    15521652 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1553  * @since 2.8.2 - 2011-05-21 
     1653 * @since 2.15.10 - 2012-10-28 
    15541654 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings 
    15551655 * @return bool FALSE if error occurs during parsing 
     
    15701670    else 
    15711671      $rows = & $unparsedtext; 
    1572             /* identify BEGIN:VCALENDAR, MUST be first row */ 
    1573     if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 ))) 
    1574       return FALSE;                   /* err 8 */ 
    15751672            /* fix line folding */ 
    1576     $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings 
    1577     $EOLmark = FALSE; 
    1578     foreach( $eolchars as $eolchar ) { 
    1579       if( !$EOLmark  && ( FALSE !== strpos( $rows, $eolchar ))) { 
    1580         $rows = str_replace( $eolchar." ",  '',  $rows ); 
    1581         $rows = str_replace( $eolchar."\t", '',  $rows ); 
    1582         if( $eolchar != $nl ) 
    1583           $rows = str_replace( $eolchar,    $nl, $rows ); 
    1584         $EOLmark = TRUE; 
    1585       } 
    1586     } 
    1587     $tmp = explode( $nl, $rows ); 
    1588     $rows = array(); 
    1589     foreach( $tmp as $tmpr ) 
    1590       if( !empty( $tmpr )) 
    1591         $rows[] = $tmpr; 
    1592             /* skip trailing empty lines */ 
    1593     $lix = count( $rows ) - 1; 
    1594     while( empty( $rows[$lix] ) && ( 0 < $lix )) 
    1595       $lix -= 1; 
    1596             /* identify ending END:VCALENDAR row, MUST  be last row */ 
    1597     if( 'END:VCALENDAR'   != strtoupper( substr( $rows[$lix], 0, 13 ))) 
    1598       return FALSE;                   /* err 9 */ 
    1599     if( 3 > count( $rows )) 
    1600       return FALSE;                   /* err 10 */ 
     1673    $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl )); 
     1674            /* skip leading (empty/invalid) lines */ 
     1675    foreach( $rows as $lix => $line ) { 
     1676      if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' )) 
     1677        break; 
     1678      unset( $rows[$lix] ); 
     1679    } 
     1680    $rcnt = count( $rows ); 
     1681    if( 3 > $rcnt )                  /* err 10 */ 
     1682      return FALSE; 
     1683            /* skip trailing empty lines and ensure an end row */ 
     1684    $lix  = array_keys( $rows ); 
     1685    $lix  = end( $lix ); 
     1686    while( 3 < $lix ) { 
     1687      $tst = trim( $rows[$lix] ); 
     1688      if(( '\n' == $tst ) || empty( $tst )) { 
     1689        unset( $rows[$lix] ); 
     1690        $lix--; 
     1691        continue; 
     1692      } 
     1693      if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' )) 
     1694        $rows[] = 'END:VCALENDAR'; 
     1695      break; 
     1696    } 
    16011697    $comp    = & $this; 
    1602     $calsync = 0; 
     1698    $calsync = $compsync = 0; 
    16031699            /* identify components and update unparsed data within component */ 
    16041700    $config = $this->getConfig(); 
    1605     foreach( $rows as $line ) { 
    1606       if( '' == trim( $line )) 
     1701    $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ); 
     1702    foreach( $rows as $lix => $line ) { 
     1703      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) { 
     1704        $calsync++; 
    16071705        continue; 
    1608       if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) { 
    1609         ++$calsync; 
    1610         continue; 
    16111706      } 
    16121707      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) { 
     1708        if( 0 < $compsync ) 
     1709          $this->components[] = $comp->copy(); 
     1710        $compsync--; 
    16131711        $calsync--; 
    16141712        break; 
     
    16161714      elseif( 1 != $calsync ) 
    16171715        return FALSE;                 /* err 20 */ 
    1618       elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) { 
     1716      elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) { 
    16191717        $this->components[] = $comp->copy(); 
     1718        $compsync--; 
    16201719        continue; 
    16211720      } 
    1622  
    1623       if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) 
     1721      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) { 
    16241722        $comp = new vevent( $config ); 
    1625       elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) 
     1723        $compsync++; 
     1724      } 
     1725      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) { 
    16261726        $comp = new vfreebusy( $config ); 
    1627       elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) 
     1727        $compsync++; 
     1728      } 
     1729      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) { 
    16281730        $comp = new vjournal( $config ); 
    1629       elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) 
     1731        $compsync++; 
     1732      } 
     1733      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) { 
    16301734        $comp = new vtodo( $config ); 
    1631       elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) 
     1735        $compsync++; 
     1736      } 
     1737      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) { 
    16321738        $comp = new vtimezone( $config ); 
    1633       else  /* update component with unparsed data */ 
     1739        $compsync++; 
     1740      } 
     1741      else { /* update component with unparsed data */ 
    16341742        $comp->unparsed[] = $line; 
    1635     } // end - foreach( rows.. . 
    1636     unset( $config ); 
     1743      } 
     1744    } // end foreach( $rows as $line ) 
     1745    unset( $config, $endtxt ); 
    16371746            /* parse data for calendar (this) object */ 
    16381747    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) { 
    16391748            /* concatenate property values spread over several lines */ 
    1640       $lastix    = -1; 
    16411749      $propnames = array( 'calscale','method','prodid','version','x-' ); 
    16421750      $proprows  = array(); 
    1643       foreach( $this->unparsed as $line ) { 
    1644         if( '' == trim( $line )) 
    1645           continue; 
    1646         $newProp = FALSE; 
    1647         foreach ( $propnames as $propname ) { 
    1648           if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) { 
    1649             $newProp = TRUE; 
    1650             break; 
    1651           } 
    1652         } 
    1653         if( $newProp ) { 
    1654           $newProp = FALSE; 
    1655           ++$lastix; 
    1656           $proprows[$lastix]  = $line; 
    1657         } 
    1658         else 
    1659           $proprows[$lastix] .= '!"#€%&/()=?'.$line; 
    1660       } 
     1751      for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines 
     1752        $line = rtrim( $this->unparsed[$i], $nl ); 
     1753        while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} )) 
     1754          $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl ); 
     1755        $proprows[] = $line; 
     1756      } 
     1757      $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' ); 
     1758      $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ); 
     1759      $paramProto4 = array( 'crid:', 'news:', 'pres:' ); 
    16611760      foreach( $proprows as $line ) { 
    1662         $line = str_replace( '!"#€%&/()=? ', '', $line ); 
    1663         $line = str_replace( '!"#€%&/()=?', '', $line ); 
    16641761        if( '\n' == substr( $line, -2 )) 
    1665           $line = substr( $line, 0, strlen( $line ) - 2 ); 
     1762          $line = substr( $line, 0, -2 ); 
    16661763            /* get property name */ 
    1667         $cix = $propname = null; 
    1668         for( $cix=0, $clen = strlen( $line ); $cix < $clen; ++$cix ) { 
    1669           if( in_array( $line[$cix], array( ':', ';' ))) 
     1764        $propname  = ''; 
     1765        $cix       = 0; 
     1766        while( FALSE !== ( $char = substr( $line, $cix, 1 ))) { 
     1767          if( in_array( $char, array( ':', ';' ))) 
    16701768            break; 
    16711769          else 
    1672             $propname .= $line[$cix]; 
    1673         } 
     1770            $propname .= $char; 
     1771          $cix++; 
     1772        } 
     1773            /* skip non standard property names */ 
     1774        if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames )) 
     1775          continue; 
    16741776            /* ignore version/prodid properties */ 
    1675         if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' ))) 
     1777        if( in_array( strtolower( $propname ), array( 'version', 'prodid' ))) 
    16761778          continue; 
     1779            /* rest of the line is opt.params and value */ 
    16771780        $line = substr( $line, $cix); 
    16781781            /* separate attributes from value */ 
    1679         $attr   = array(); 
    1680         $attrix = -1; 
    1681         $strlen = strlen( $line ); 
    1682         for( $cix=0; $cix < $strlen; ++$cix ) { 
    1683           if((       ':'   == $line[$cix] )             && 
    1684                    ( '://' != substr( $line, $cix, 3 )) && 
    1685              ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) && 
    1686              ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) && 
    1687              ( 'mailto:'   != strtolower( substr( $line, $cix - 6, 7 )))) { 
     1782        $attr         = array(); 
     1783        $attrix       = -1; 
     1784        $strlen       = strlen( $line ); 
     1785        $WithinQuotes = FALSE; 
     1786        $cix          = 0; 
     1787        while( FALSE !== substr( $line, $cix, 1 )) { 
     1788          if(                       ( ':'  == $line[$cix] )                         && 
     1789                                    ( substr( $line,$cix,     3 )  != '://' )       && 
     1790             ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   && 
     1791             ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) && 
     1792             ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) && 
     1793                        ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   && 
     1794               !$WithinQuotes ) { 
    16881795            $attrEnd = TRUE; 
    16891796            if(( $cix < ( $strlen - 4 )) && 
     
    16971804            } 
    16981805            if( $attrEnd) { 
    1699               $line = substr( $line, $cix + 1 ); 
     1806              $line = substr( $line, ( $cix + 1 )); 
    17001807              break; 
    17011808            } 
    17021809          } 
     1810          if( '"' == $line[$cix] ) 
     1811            $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE; 
    17031812          if( ';' == $line[$cix] ) 
    17041813            $attr[++$attrix] = null; 
    17051814          else 
    17061815            $attr[$attrix] .= $line[$cix]; 
    1707         } 
    1708  
     1816          $cix++; 
     1817        } 
    17091818            /* make attributes in array format */ 
    17101819        $propattr = array(); 
     
    17181827            /* update Property */ 
    17191828        if( FALSE !== strpos( $line, ',' )) { 
    1720           $content  = explode( ',', $line ); 
    1721           $clen     = count( $content ); 
    1722           for( $cix = 0; $cix < $clen; ++$cix ) { 
    1723             if( "\\" == substr( $content[$cix], -1 )) { 
    1724               $content[$cix] .= ','.$content[$cix + 1]; 
    1725               unset( $content[$cix + 1] ); 
    1726               ++$cix;            } 
     1829          $content  = array( 0 => '' ); 
     1830          $cix = $lix = 0; 
     1831          while( FALSE !== substr( $line, $lix, 1 )) { 
     1832            if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) { 
     1833              $cix++; 
     1834              $content[$cix] = ''; 
     1835            } 
     1836            else 
     1837              $content[$cix] .= $line[$lix]; 
     1838            $lix++; 
    17271839          } 
    17281840          if( 1 < count( $content )) { 
     
    17361848          $line = calendarComponent::_strunrep( $line ); 
    17371849        } 
    1738         $this->setProperty( $propname, trim( $line ), $propattr ); 
     1850        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr ); 
    17391851      } // end - foreach( $this->unparsed.. . 
    17401852    } // end - if( is_array( $this->unparsed.. . 
     
    17581870 * 
    17591871 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1760  * @since 2.8.1 - 2011-03-12 
     1872 * @since 2.10.16 - 2011-10-28 
    17611873 * @return string 
    17621874 */ 
    17631875  function createCalendar() { 
    1764     $calendarInit1 = $calendarInit2 = $calendarxCaldecl = $calendarStart = $calendar = null; 
     1876    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = ''; 
    17651877    switch( $this->format ) { 
    17661878      case 'xcal': 
    1767         $calendarInit1 = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl. 
    1768                          '<!DOCTYPE iCalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl. 
     1879        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl. 
     1880                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl. 
    17691881                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"'; 
    1770         $calendarInit2 = '>'.$this->nl; 
    1771         $calendarStart = '<vcalendar'; 
     1882        $calendarStart = '>'.$this->nl.'<vcalendar'; 
    17721883        break; 
    17731884      default: 
     
    17791890    $calendarStart .= $this->createCalscale(); 
    17801891    $calendarStart .= $this->createMethod(); 
    1781     switch( $this->format ) { 
    1782       case 'xcal': 
    1783         $nlstrlen = strlen( $this->nl ); 
    1784         if( $this->nl == substr( $calendarStart, ( 0 - $nlstrlen ))) 
    1785           $calendarStart = substr( $calendarStart, 0, ( strlen( $calendarStart ) - $nlstrlen )); 
    1786         $calendarStart .= '>'.$this->nl; 
    1787         break; 
    1788       default: 
    1789         break; 
    1790     } 
     1892    if( 'xcal' == $this->format ) 
     1893      $calendarStart .= '>'.$this->nl; 
    17911894    $calendar .= $this->createXprop(); 
     1895 
    17921896    foreach( $this->components as $component ) { 
    17931897      if( empty( $component )) continue; 
     
    17951899      $calendar .= $component->createComponent( $this->xcaldecl ); 
    17961900    } 
    1797     if(( 0 < count( $this->xcaldecl )) && ( 'xcal' == $this->format )) { // xCal only 
    1798       $calendarInit1 .= $this->nl.'['.$this->nl; 
    1799       $old_xcaldecl = array(); 
     1901    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only 
     1902      $calendarInit .= ' ['; 
     1903      $old_xcaldecl  = array(); 
    18001904      foreach( $this->xcaldecl as $declix => $declPart ) { 
    1801         if(( 0 < count( $old_xcaldecl)) && 
    1802            ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] )) && 
     1905        if(( 0 < count( $old_xcaldecl))    && 
     1906             isset( $declPart['uri'] )     && isset( $declPart['external'] )     && 
     1907             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) && 
     1908           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            && 
    18031909           ( in_array( $declPart['external'], $old_xcaldecl['external'] ))) 
    18041910          continue; // no duplicate uri and ext. references 
    1805         $calendarxCaldecl .= '<!'; 
     1911        if(( 0 < count( $old_xcaldecl))    && 
     1912            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         && 
     1913             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      && 
     1914           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] ))) 
     1915          continue; // no duplicate element declarations 
     1916        $calendarxCaldecl .= $this->nl.'<!'; 
    18061917        foreach( $declPart as $declKey => $declValue ) { 
    18071918          switch( $declKey ) {                    // index 
     
    18151926            case 'ref':                           // no 3 
    18161927              $calendarxCaldecl .= $declValue.' '; 
     1928              $old_xcaldecl['ref'][] = $declValue; 
    18171929              break; 
    18181930            case 'external':                      // no 4 
     
    18281940          } 
    18291941        } 
    1830         $calendarxCaldecl .= '>'.$this->nl; 
    1831       } 
    1832       $calendarInit2 = ']'.$calendarInit2; 
     1942        $calendarxCaldecl .= '>'; 
     1943      } 
     1944      $calendarxCaldecl .= $this->nl.']'; 
    18331945    } 
    18341946    switch( $this->format ) { 
     
    18401952        break; 
    18411953    } 
    1842     return $calendarInit1.$calendarxCaldecl.$calendarInit2.$calendarStart.$calendar; 
     1954    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar; 
    18431955  } 
    18441956/** 
     
    18461958 * 
    18471959 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    1848  * @since 2.9.12 - 2011-07-13 
     1960 * @since 2.10.24 - 2011-12-23 
    18491961 * @param bool $utf8Encode 
    18501962 * @param bool $gzip 
     
    18581970    if( $gzip ) { 
    18591971      $output = gzencode( $output, 9 ); 
    1860       header( 'Content-Encoding: gzip'); 
    1861       header( 'Vary: *'); 
    1862     } 
    1863     $filesize = strlen( $output ); 
     1972      header( 'Content-Encoding: gzip' ); 
     1973      header( 'Vary: *' ); 
     1974      header( 'Content-Length: '.strlen( $output )); 
     1975    } 
    18641976    if( 'xcal' == $this->format ) 
    18651977      header( 'Content-Type: application/calendar+xml; charset=utf-8' ); 
    18661978    else 
    18671979      header( 'Content-Type: text/calendar; charset=utf-8' ); 
    1868     header( 'Content-Length: '.$filesize ); 
    18691980    header( 'Content-Disposition: attachment; filename="'.$filename.'"' ); 
    18701981    header( 'Cache-Control: max-age=10' ); 
    1871     echo $output; 
    1872     die(); 
     1982    die( $output ); 
    18731983  } 
    18741984/** 
     
    20502160 * 
    20512161 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2052  * @since 0.9.7 - 2006-11-23 
     2162 * @since 2.11.16 - 2012-02-04 
    20532163 * @return string 
    20542164 */ 
     
    20572167    $output       = null; 
    20582168    foreach( $this->attach as $attachPart ) { 
    2059       if(! empty( $attachPart['value'] )) { 
     2169      if( !empty( $attachPart['value'] )) { 
    20602170        $attributes = $this->_createParams( $attachPart['params'] ); 
     2171        if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) { 
     2172          $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes ); 
     2173          $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value']; 
     2174          $output     = substr( $str, 0, 75 ).$this->nl; 
     2175          $str        = substr( $str, 75 ); 
     2176          $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' ); 
     2177          if( ' ' == substr( $output, -1 )) 
     2178            $output   = rtrim( $output ); 
     2179          if( $this->nl != substr( $output, ( 0 - strlen( $this->nl )))) 
     2180            $output  .= $this->nl; 
     2181          return $output; 
     2182        } 
    20612183        $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] ); 
    20622184      } 
     
    20882210 * 
    20892211 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2090  * @since 2.9.8 - 2011-05-30 
     2212 * @since 2.11.12 - 2012-01-31 
    20912213 * @return string 
    20922214 */ 
     
    21052227          $attendee2     .= $paramvalue; 
    21062228        elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif 
     2229          $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' ); 
     2230          foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes 
     2231            if( is_array( $pValue ) || in_array( $pKey, $mParams )) 
     2232              continue; 
     2233            if(( FALSE !== strpos( $pValue, ':' )) || 
     2234               ( FALSE !== strpos( $pValue, ';' )) || 
     2235               ( FALSE !== strpos( $pValue, ',' ))) 
     2236              $paramvalue[$pKey] = '"'.$pValue.'"'; 
     2237          } 
    21072238        // set attenddee parameters in rfc2445 order 
    21082239          if( isset( $paramvalue['CUTYPE'] )) 
     
    21112242            $attendee1   .= $this->intAttrDelimiter.'MEMBER='; 
    21122243            foreach( $paramvalue['MEMBER'] as $cix => $opv ) 
    2113               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; 
     2244              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; 
    21142245          } 
    21152246          if( isset( $paramvalue['ROLE'] )) 
     
    21222253            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO='; 
    21232254            foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv ) 
    2124               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; 
     2255              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; 
    21252256          } 
    21262257          if( isset( $paramvalue['DELEGATED-FROM'] )) { 
    21272258            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM='; 
    21282259            foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv ) 
    2129               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; 
     2260              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; 
    21302261          } 
    21312262          if( isset( $paramvalue['SENT-BY'] )) 
    2132             $attendee1   .= $this->intAttrDelimiter.'SENT-BY="'.$paramvalue['SENT-BY'].'"'; 
     2263            $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY']; 
    21332264          if( isset( $paramvalue['CN'] )) 
    2134             $attendee1   .= $this->intAttrDelimiter.'CN="'.$paramvalue['CN'].'"'; 
    2135           if( isset( $paramvalue['DIR'] )) 
    2136             $attendee1   .= $this->intAttrDelimiter.'DIR="'.$paramvalue['DIR'].'"'; 
     2265            $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN']; 
     2266          if( isset( $paramvalue['DIR'] )) { 
     2267            $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : ''; 
     2268            $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim; 
     2269          } 
    21372270          if( isset( $paramvalue['LANGUAGE'] )) 
    21382271            $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE']; 
     
    21632296 * 
    21642297 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2165  * @since 2.6.34 - 2010-12-18 
     2298 * @since 2.12.18 - 2012-07-13 
    21662299 * @param string $value 
    21672300 * @param array $params, optional 
     
    21722305    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; 
    21732306          // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params 
    2174     if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) 
    2175       $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos ); 
    2176     elseif( !empty( $value )) 
    2177       $value = 'MAILTO:'.$value; 
     2307    if( !empty( $value )) { 
     2308      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) 
     2309        $value = 'MAILTO:'.$value; 
     2310      elseif( !empty( $value )) 
     2311        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos ); 
     2312      $value = str_replace( 'mailto:', 'MAILTO:', $value ); 
     2313    } 
    21782314    $params2 = array(); 
    21792315    if( is_array($params )) { 
     
    23712507        return $this->_createElement( 'COMPLETED' ); 
    23722508      else return FALSE; 
    2373     $formatted  = iCalUtilityFunctions::_format_date_time( $this->completed['value'], 7 ); 
     2509    $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 ); 
    23742510    $attributes = $this->_createParams( $this->completed['params'] ); 
    23752511    return $this->_createElement( 'COMPLETED', $attributes, $formatted ); 
     
    24532589  function createCreated() { 
    24542590    if( empty( $this->created )) return FALSE; 
    2455     $formatted  = iCalUtilityFunctions::_format_date_time( $this->created['value'], 7 ); 
     2591    $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 ); 
    24562592    $attributes = $this->_createParams( $this->created['params'] ); 
    24572593    return $this->_createElement( 'CREATED', $attributes, $formatted ); 
     
    25272663 * 
    25282664 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2529  * @since 2.9.6 - 2011-05-14 
     2665 * @since 2.14.4 - 2012-09-26 
    25302666 * @return string 
    25312667 */ 
     
    25412677        return $this->_createElement( 'DTEND' ); 
    25422678      else return FALSE; 
    2543     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtend['value'] ); 
    2544     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && 
    2545        ( !isset( $this->dtend['params']['VALUE'] )        || ( $this->dtend['params']['VALUE'] != 'DATE' )) && 
    2546          !isset( $this->dtend['params']['TZID'] )) 
    2547       $this->dtend['params']['TZID'] = $tzid; 
     2679    $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null; 
     2680    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno ); 
    25482681    $attributes = $this->_createParams( $this->dtend['params'] ); 
    25492682    return $this->_createElement( 'DTEND', $attributes, $formatted ); 
     
    25952728        !isset( $this->dtstamp['value']['sec'] )) 
    25962729      $this->_makeDtstamp(); 
    2597     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 ); 
     2730    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 ); 
    25982731    $attributes = $this->_createParams( $this->dtstamp['params'] ); 
    25992732    return $this->_createElement( 'DTSTAMP', $attributes, $formatted ); 
     
    26032736 * 
    26042737 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2605  * @since 2.6.25 - 2010-11-09 
     2738 * @since 2.14.1 - 2012-09-29 
    26062739 * @return void 
    26072740 */ 
    26082741  function _makeDtstamp() { 
    2609     $d = mktime( date('H'), date('m'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')); 
    2610     $this->dtstamp['value'] = array( 'year'  => date( 'Y', $d ) 
    2611                                    , 'month' => date( 'm', $d ) 
    2612                                    , 'day'   => date( 'd', $d ) 
    2613                                    , 'hour'  => date( 'H', $d ) 
    2614                                    , 'min'   => date( 'i', $d ) 
    2615                                    , 'sec'   => date( 's', $d )); 
     2742    $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y'))); 
     2743    $date = explode( '-', $d ); 
     2744    $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' ); 
    26162745    $this->dtstamp['params'] = null; 
    26172746  } 
     
    26452774 * 
    26462775 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2647  * @since 2.9.6 - 2011-05-15 
     2776 * @since 2.14.4 - 2012-09-26 
    26482777 * @return string 
    26492778 */ 
     
    26612790    } 
    26622791    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) 
    2663       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] ); 
    2664     elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && 
    2665        ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' ))  && 
    2666          !isset( $this->dtstart['params']['TZID'] )) 
    2667       $this->dtstart['params']['TZID'] = $tzid; 
    2668     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstart['value'] ); 
     2792       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] ); 
     2793    $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null; 
     2794    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno ); 
    26692795    $attributes = $this->_createParams( $this->dtstart['params'] ); 
    26702796    return $this->_createElement( 'DTSTART', $attributes, $formatted ); 
     
    27052831 * 
    27062832 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2707  * @since 2.4.8 - 2008-10-22 
     2833 * @since 2.14.4 - 2012-09-26 
    27082834 * @return string 
    27092835 */ 
     
    27212847       return FALSE; 
    27222848    } 
    2723     $formatted  = iCalUtilityFunctions::_format_date_time( $this->due['value'] ); 
    2724     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && 
    2725        ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' ))  && 
    2726          !isset( $this->due['params']['TZID'] )) 
    2727       $this->due['params']['TZID'] = $tzid; 
     2849    $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null; 
     2850    $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno ); 
    27282851    $attributes = $this->_createParams( $this->due['params'] ); 
    27292852    return $this->_createElement( 'DUE', $attributes, $formatted ); 
     
    27772900      else return FALSE; 
    27782901    $attributes = $this->_createParams( $this->duration['params'] ); 
    2779     return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_format_duration( $this->duration['value'] )); 
     2902    return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] )); 
    27802903  } 
    27812904/** 
     
    27952918    if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE; 
    27962919    if( is_array( $week ) && ( 1 <= count( $week ))) 
    2797       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); 
     2920      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); 
    27982921    elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) { 
    27992922      $week = trim( $week ); 
    28002923      if( in_array( substr( $week, 0, 1 ), array( '+', '-' ))) 
    28012924        $week = substr( $week, 1 ); 
    2802       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_string( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); 
     2925      $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); 
    28032926    } 
    28042927    elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec )) 
    28052928      return FALSE; 
    28062929    else 
    2807       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params )); 
     2930      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params )); 
    28082931    return TRUE; 
    28092932  } 
     
    28302953      foreach( $theExdate['value'] as $eix => $exdatePart ) { 
    28312954        $parno = count( $exdatePart ); 
    2832         $formatted = iCalUtilityFunctions::_format_date_time( $exdatePart, $parno ); 
     2955        $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno ); 
    28332956        if( isset( $theExdate['params']['TZID'] )) 
    28342957          $formatted = str_replace( 'Z', '', $formatted); 
     
    28572980 * 
    28582981 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2859  * @since 2.5.1 - 2008-11-05 
     2982 * @since 2.14.1 - 2012-10-02 
    28602983 * @param array exdates 
    28612984 * @param array $params, optional 
     
    28732996    } 
    28742997    $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); 
     2998    $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE; 
    28752999            /* ev. check 1:st date and save ev. timezone **/ 
    28763000    iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] ); 
    28773001    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter 
    28783002    foreach( $exdates as $eix => $theExdate ) { 
    2879       if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) 
     3003      iCalUtilityFunctions::_strDate2arr( $theExdate ); 
     3004      if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) { 
     3005        if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) { 
     3006          if( isset( $input['params']['TZID'] )) 
     3007            $theExdate['tz'] = $input['params']['TZID']; 
     3008          else 
     3009            $input['params']['TZID'] = $theExdate['tz']; 
     3010        } 
    28803011        $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno ); 
    2881       elseif(  is_array( $theExdate )) 
    2882         $exdatea = iCalUtilityFunctions::_date_time_array( $theExdate, $parno ); 
    2883       elseif( 8 <= strlen( trim( $theExdate ))) // ex. 2006-08-03 10:12:18 
    2884         $exdatea = iCalUtilityFunctions::_date_time_string( $theExdate, $parno ); 
     3012      } 
     3013      elseif(  is_array( $theExdate )) { 
     3014        $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno ); 
     3015        if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) { 
     3016          $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     3017          $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     3018          unset( $exdatea['unparsedtext'] ); 
     3019        } 
     3020        else 
     3021          $exdatea = $d; 
     3022      } 
     3023      elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18 
     3024        $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno ); 
     3025        unset( $exdatea['unparsedtext'] ); 
     3026      } 
    28853027      if( 3 == $parno ) 
    28863028        unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] ); 
     
    28923034         ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) 
    28933035        unset( $exdatea['tz'] ); 
     3036      if( $toZ ) // time zone Z 
     3037        $exdatea['tz'] = 'Z'; 
    28943038      $input['value'][] = $exdatea; 
    28953039    } 
     
    29003044      unset( $input['params']['TZID'] ); 
    29013045    } 
     3046    if( $toZ ) // time zone Z 
     3047      unset( $input['params']['TZID'] ); 
    29023048    iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index ); 
    29033049    return TRUE; 
     
    29413087 * 
    29423088 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2943  * @since 2.4.8 - 2008-10-22 
     3089 * @since 2.1.23 - 2012-02-16 
    29443090 * @return string 
    29453091 */ 
     
    29483094    $output = null; 
    29493095    foreach( $this->freebusy as $freebusyPart ) { 
    2950       if( empty( $freebusyPart['value'] )) { 
     3096      if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) { 
    29513097        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' ); 
    29523098        continue; 
     
    29543100      $attributes = $content = null; 
    29553101      if( isset( $freebusyPart['value']['fbtype'] )) { 
    2956         $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype']; 
     3102          $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype']; 
    29573103        unset( $freebusyPart['value']['fbtype'] ); 
    29583104        $freebusyPart['value'] = array_values( $freebusyPart['value'] ); 
     
    29643110      $cnt = count( $freebusyPart['value']); 
    29653111      foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) { 
    2966         $formatted   = iCalUtilityFunctions::_format_date_time( $freebusyPeriod[0] ); 
     3112        $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] ); 
    29673113        $content .= $formatted; 
    29683114        $content .= '/'; 
     
    29763122            isset( $freebusyPeriod[1]['month'] ) && 
    29773123            isset( $freebusyPeriod[1]['day'] )) { 
    2978           $content .= iCalUtilityFunctions::_format_date_time( $freebusyPeriod[1] ); 
     3124          $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] ); 
    29793125        } 
    29803126        else {                                  // period=  -> dur-time 
    2981           $content .= iCalUtilityFunctions::_format_duration( $freebusyPeriod[1] ); 
     3127          $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] ); 
    29823128        } 
    29833129        if( $fno < $cnt ) 
    29843130          $content .= ','; 
    2985         ++$fno; 
     3131        $fno++; 
    29863132      } 
    29873133      $output .= $this->_createElement( 'FREEBUSY', $attributes, $content ); 
     
    29933139 * 
    29943140 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    2995  * @since 2.8.10 - 2011-03-24 
     3141 * @since 2.10.30 - 2012-01-16 
    29963142 * @param string $fbType 
    29973143 * @param array $fbValues 
     
    30223168        if( is_array( $fbMember )) { 
    30233169          if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value 
    3024             $freebusyPairMember       = iCalUtilityFunctions::_date_time_array( $fbMember, 7 ); 
     3170            $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 ); 
    30253171            $freebusyPairMember['tz'] = 'Z'; 
    30263172          } 
     
    30303176          } 
    30313177          else {                                         // array format duration 
    3032             $freebusyPairMember = iCalUtilityFunctions::_duration_array( $fbMember ); 
     3178            $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember ); 
    30333179          } 
    30343180        } 
     
    30373183          if( 'P' != $fbMember{0} ) 
    30383184            $fbmember = substr( $fbMember, 1 ); 
    3039           $freebusyPairMember = iCalUtilityFunctions::_duration_string( $fbMember ); 
     3185          $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember ); 
    30403186        } 
    30413187        elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18 
    3042           $freebusyPairMember       = iCalUtilityFunctions::_date_time_string( $fbMember, 7 ); 
     3188          $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 ); 
     3189          unset( $freebusyPairMember['unparsedtext'] ); 
    30433190          $freebusyPairMember['tz'] = 'Z'; 
    30443191        } 
     
    30583205 * 
    30593206 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3060  * @since 2.4.8 - 2008-10-21 
     3207 * @since 2.12.6 - 2012-04-21 
    30613208 * @return string 
    30623209 */ 
     
    30663213      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE; 
    30673214    $attributes = $this->_createParams( $this->geo['params'] ); 
    3068     $content    = null; 
    3069     $content   .= number_format( (float) $this->geo['value']['latitude'], 6, '.', ''); 
    3070     $content   .= ';'; 
    3071     $content   .= number_format( (float) $this->geo['value']['longitude'], 6, '.', ''); 
     3215    if( 0.0 < $this->geo['value']['latitude'] ) 
     3216      $sign   = '+'; 
     3217    else 
     3218      $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : ''; 
     3219    $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#€%&/( 
     3220    $content  = rtrim( rtrim( $content, '0' ), '.' ); 
     3221    if( 0.0 < $this->geo['value']['longitude'] ) 
     3222      $sign   = '+'; 
     3223    else 
     3224      $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : ''; 
     3225    $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#€%&/( 
     3226    $content  = rtrim( rtrim( $content, '0' ), '.' ); 
    30723227    return $this->_createElement( 'GEO', $attributes, $content ); 
    30733228  } 
     
    30763231 * 
    30773232 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3078  * @since 2.4.8 - 2008-11-04 
     3233 * @since 2.12.5 - 2012-04-21 
    30793234 * @param float $latitude 
    30803235 * @param float $longitude 
     
    30833238 */ 
    30843239  function setGeo( $latitude, $longitude, $params=FALSE ) { 
    3085     if( !empty( $latitude ) && !empty( $longitude )) { 
     3240    if(( !empty( $latitude )  || ( 0 == $latitude )) && 
     3241       ( !empty( $longitude ) || ( 0 == $longitude ))) { 
    30863242      if( !is_array( $this->geo )) $this->geo = array(); 
    3087       $this->geo['value']['latitude']  = $latitude; 
    3088       $this->geo['value']['longitude'] = $longitude; 
     3243      $this->geo['value']['latitude']  = (float) $latitude; 
     3244      $this->geo['value']['longitude'] = (float) $longitude; 
    30893245      $this->geo['params'] = iCalUtilityFunctions::_setParams( $params ); 
    30903246    } 
     
    31093265    if( empty( $this->lastmodified )) return FALSE; 
    31103266    $attributes = $this->_createParams( $this->lastmodified['params'] ); 
    3111     $formatted  = iCalUtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 ); 
     3267    $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 ); 
    31123268    return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted ); 
    31133269  } 
     
    31883344 * 
    31893345 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3190  * @since 2.6.27 - 2010-11-29 
     3346 * @since 2.12.18 - 2012-07-13 
    31913347 * @param string $value 
    31923348 * @param array params optional 
     
    31953351  function setOrganizer( $value, $params=FALSE ) { 
    31963352    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; 
    3197     if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) 
    3198       $value = 'MAILTO:'.$value; 
    3199     else 
    3200       $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos ); 
    3201     $value = str_replace( 'mailto:', 'MAILTO:', $value ); 
     3353    if( !empty( $value )) { 
     3354      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) 
     3355        $value = 'MAILTO:'.$value; 
     3356      elseif( !empty( $value )) 
     3357        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos ); 
     3358      $value = str_replace( 'mailto:', 'MAILTO:', $value ); 
     3359    } 
    32023360    $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); 
    32033361    if( isset( $this->organizer['params']['SENT-BY'] )){ 
     
    32813439 * 
    32823440 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3283  * @since 2.4.16 - 2008-10-26 
     3441 * @since 2.14.1 - 2012-10-04 
    32843442 * @return string 
    32853443 */ 
     
    32903448    if( $utctime  ) 
    32913449      unset( $this->rdate['params']['TZID'] ); 
    3292     foreach( $this->rdate as $theRdate ) { 
     3450    foreach( $this->rdate as $rpix => $theRdate ) { 
    32933451      if( empty( $theRdate['value'] )) { 
    32943452        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' ); 
     
    33013459      $content = null; 
    33023460      $rno = 1; 
    3303       foreach( $theRdate['value'] as $rpix => $rdatePart ) { 
     3461      foreach( $theRdate['value'] as $rix => $rdatePart ) { 
    33043462        $contentPart = null; 
    33053463        if( is_array( $rdatePart ) && 
     
    33073465          if( $utctime ) 
    33083466            unset( $rdatePart[0]['tz'] ); 
    3309           $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1 
     3467          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1 
    33103468          if( $utctime || !empty( $theRdate['params']['TZID'] )) 
    33113469            $formatted = str_replace( 'Z', '', $formatted); 
    3312           if( 0 < $rpix ) { 
    3313             if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { 
    3314               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; 
    3315             } 
    3316             else 
    3317               $formatted = str_replace( 'Z', '', $formatted ); 
    3318           } 
    33193470          $contentPart .= $formatted; 
    33203471          $contentPart .= '/'; 
     
    33343485            if( $utctime ) 
    33353486              unset( $rdatePart[1]['tz'] ); 
    3336             $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2 
     3487            $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2 
    33373488            if( $utctime || !empty( $theRdate['params']['TZID'] )) 
    3338               $formatted = str_replace( 'Z', '', $formatted); 
    3339             if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { 
    3340               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; 
    3341             } 
    3342             else 
    33433489              $formatted = str_replace( 'Z', '', $formatted ); 
    33443490           $contentPart .= $formatted; 
    33453491          } 
    33463492          else {                                  // period=  -> dur-time 
    3347             $contentPart .= iCalUtilityFunctions::_format_duration( $rdatePart[1] ); 
     3493            $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] ); 
    33483494          } 
    33493495        } // PERIOD end 
     
    33513497          if( $utctime ) 
    33523498            unset( $rdatePart['tz'] ); 
    3353           $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart); 
     3499          $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null; 
     3500          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno ); 
    33543501          if( $utctime || !empty( $theRdate['params']['TZID'] )) 
    33553502            $formatted = str_replace( 'Z', '', $formatted); 
    3356           if( !$utctime && ( 0 < $rpix )) { 
    3357             if( !empty( $theRdate['value'][0]['tz'] ) && iCalUtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) { 
    3358               if( 'Z' != substr( $formatted, -1 )) 
    3359                 $formatted .= 'Z'; 
    3360             } 
    3361             else 
    3362               $formatted = str_replace( 'Z', '', $formatted ); 
    3363           } 
    33643503          $contentPart .= $formatted; 
    33653504        } 
     
    33673506        if( $rno < $cnt ) 
    33683507          $content .= ','; 
    3369         ++$rno; 
     3508        $rno++; 
    33703509      } 
    33713510      $output    .= $this->_createElement( 'RDATE', $attributes, $content ); 
     
    33773516 * 
    33783517 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3379  * @since 2.5.1 - 2008-11-07 
     3518 * @since 2.14.1 - 2012-10-04 
    33803519 * @param array $rdates 
    33813520 * @param array $params, optional 
     
    33973536      $input['params']['VALUE'] = 'DATE-TIME'; 
    33983537    } 
     3538    $zArr = array( 'GMT', 'UTC', 'Z' ); 
     3539    $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE; 
    33993540            /*  check if PERIOD, if not set */ 
    34003541    if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) && 
     
    34113552      $date  = reset( $date ); 
    34123553    iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] ); 
    3413     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) 
    3414       unset( $input['params']['TZID'] ); 
    34153554    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default 
    34163555    foreach( $rdates as $rpix => $theRdate ) { 
    34173556      $inputa = null; 
     3557      iCalUtilityFunctions::_strDate2arr( $theRdate ); 
    34183558      if( is_array( $theRdate )) { 
    34193559        if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD 
    34203560          foreach( $theRdate as $rix => $rPeriod ) { 
     3561            iCalUtilityFunctions::_strDate2arr( $theRdate ); 
    34213562            if( is_array( $rPeriod )) { 
    3422               if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod ))      // timestamp 
    3423                 $inputab  = ( isset( $rPeriod['tz'] )) ? iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCalUtilityFunctions::_timestamp2date( $rPeriod, 6 ); 
    3424               elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) 
    3425                 $inputab  = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCalUtilityFunctions::_date_time_array( $rPeriod, 6 ); 
    3426               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod ))))  // text-date 
    3427                 $inputab  = iCalUtilityFunctions::_date_time_string( reset( $rPeriod ), $parno ); 
     3563              if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp 
     3564                if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) { 
     3565                  if( isset( $input['params']['TZID'] )) 
     3566                    $rPeriod['tz'] = $input['params']['TZID']; 
     3567                  else 
     3568                    $input['params']['TZID'] = $rPeriod['tz']; 
     3569                } 
     3570                $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ); 
     3571              } 
     3572              elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) { 
     3573                $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 ); 
     3574                if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) { 
     3575                  $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     3576                  $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     3577                  unset( $inputab['unparsedtext'] ); 
     3578                } 
     3579                else 
     3580                  $inputab = $d; 
     3581              } 
     3582              elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date 
     3583                $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno ); 
     3584                unset( $inputab['unparsedtext'] ); 
     3585              } 
    34283586              else                                               // array format duration 
    3429                 $inputab  = iCalUtilityFunctions::_duration_array( $rPeriod ); 
     3587                $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod ); 
    34303588            } 
    34313589            elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration 
    34323590                   ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) { 
    34333591              if( 'P' != $rPeriod[0] ) 
    3434                 $rPeriod  = substr( $rPeriod, 1 ); 
    3435               $inputab    = iCalUtilityFunctions::_duration_string( $rPeriod ); 
     3592                $rPeriod   = substr( $rPeriod, 1 ); 
     3593              $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod ); 
    34363594            } 
    3437             elseif( 8 <= strlen( trim( $rPeriod )))              // text date ex. 2006-08-03 10:12:18 
    3438               $inputab    = iCalUtilityFunctions::_date_time_string( $rPeriod, $parno ); 
    3439             if(  isset( $input['params']['TZID'] ) || 
    3440                ( isset( $inputab['tz'] )   && !iCalUtilityFunctions::_isOffset( $inputab['tz'] )) || 
    3441                ( isset( $inputa[0] )       && ( !isset( $inputa[0]['tz'] )))       || 
    3442                ( isset( $inputa[0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa[0]['tz'] ))) 
    3443               unset( $inputab['tz'] ); 
    3444             $inputa[]     = $inputab; 
     3595            elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18 
     3596              $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno ); 
     3597              unset( $inputab['unparsedtext'] ); 
     3598            } 
     3599            if(( 0 == $rpix ) && ( 0 == $rix )) { 
     3600              if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) { 
     3601                $inputab['tz'] = 'Z'; 
     3602                $toZ = TRUE; 
     3603              } 
     3604            } 
     3605            else { 
     3606              if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] )) 
     3607                $inputab['tz'] = 'Z'; 
     3608              else 
     3609                unset( $inputab['tz'] ); 
     3610            } 
     3611            if( $toZ && isset( $inputab['year'] ) ) 
     3612              $inputab['tz'] = 'Z'; 
     3613            $inputa[]      = $inputab; 
    34453614          } 
    34463615        } // PERIOD end 
    3447         elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate ))      // timestamp 
     3616        elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp 
     3617          if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) { 
     3618            if( isset( $input['params']['TZID'] )) 
     3619              $theRdate['tz'] = $input['params']['TZID']; 
     3620            else 
     3621              $input['params']['TZID'] = $theRdate['tz']; 
     3622          } 
    34483623          $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno ); 
    3449         else                                                                    // date[-time] 
    3450           $inputa = iCalUtilityFunctions::_date_time_array( $theRdate, $parno ); 
    3451       } 
    3452       elseif( 8 <= strlen( trim( $theRdate )))                   // text date ex. 2006-08-03 10:12:18 
    3453         $inputa       = iCalUtilityFunctions::_date_time_string( $theRdate, $parno ); 
     3624        } 
     3625        else {                                                                  // date[-time] 
     3626          $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno ); 
     3627          if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) { 
     3628            $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] ); 
     3629            $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     3630            unset( $inputa['unparsedtext'] ); 
     3631          } 
     3632        } 
     3633      } 
     3634      elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18 
     3635        $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno ); 
     3636        unset( $inputa['unparsedtext'] ); 
     3637        if( $toZ ) 
     3638          $inputa['tz'] = 'Z'; 
     3639      } 
    34543640      if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD 
     3641        if(( 0 == $rpix ) && !$toZ ) 
     3642          $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE; 
     3643        if( $toZ ) 
     3644          $inputa['tz']    = 'Z'; 
    34553645        if( 3 == $parno ) 
    34563646          unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] ); 
    34573647        elseif( isset( $inputa['tz'] )) 
    3458           $inputa['tz'] = (string) $inputa['tz']; 
    3459         if(  isset( $input['params']['TZID'] ) || 
    3460            ( isset( $inputa['tz'] )            && !iCalUtilityFunctions::_isOffset( $inputa['tz'] ))     || 
    3461            ( isset( $input['value'][0] )       && ( !isset( $input['value'][0]['tz'] )))  || 
    3462            ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) 
    3463           unset( $inputa['tz'] ); 
    3464       } 
    3465       $input['value'][] = $inputa; 
     3648          $inputa['tz']    = (string) $inputa['tz']; 
     3649        if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] )))) 
     3650          if( !$toZ ) 
     3651            unset( $inputa['tz'] ); 
     3652      } 
     3653      $input['value'][]    = $inputa; 
    34663654    } 
    34673655    if( 3 == $parno ) { 
     
    34693657      unset( $input['params']['TZID'] ); 
    34703658    } 
     3659    if( $toZ ) 
     3660      unset( $input['params']['TZID'] ); 
    34713661    iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index ); 
    34723662    return TRUE; 
     
    34803670 * 
    34813671 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3482  * @since 2.9.6 - 2011-05-15 
     3672 * @since 2.14.4 - 2012-09-26 
    34833673 * @return string 
    34843674 */ 
     
    34873677    if( empty( $this->recurrenceid['value'] )) 
    34883678      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE; 
    3489     $formatted  = iCalUtilityFunctions::_format_date_time( $this->recurrenceid['value'] ); 
    3490     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && 
    3491        ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' ))  && 
    3492          !isset( $this->recurrenceid['params']['TZID'] )) 
    3493       $this->recurrenceid['params']['TZID'] = $tzid; 
     3679    $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null; 
     3680    $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno ); 
    34943681    $attributes = $this->_createParams( $this->recurrenceid['params'] ); 
    34953682    return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted ); 
     
    35293716 * 
    35303717 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3531  * @since 2.4.8 - 2008-10-23 
     3718 * @since 2.11.24 - 2012-02-23 
    35323719 * @return string 
    35333720 */ 
     
    35363723    $output = null; 
    35373724    foreach( $this->relatedto as $relation ) { 
    3538       if( empty( $relation['value'] )) { 
    3539         if( $this->getConfig( 'allowEmpty' )) $output.= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] )); 
    3540         continue; 
    3541       } 
    3542       $attributes = $this->_createParams( $relation['params'] ); 
    3543       $content    = ( 'xcal' != $this->format ) ? '<' : ''; 
    3544       $content   .= $this->_strrep( $relation['value'] ); 
    3545       $content   .= ( 'xcal' != $this->format ) ? '>' : ''; 
    3546       $output    .= $this->_createElement( 'RELATED-TO', $attributes, $content ); 
     3725      if( !empty( $relation['value'] )) 
     3726        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), $this->_strrep( $relation['value'] ) ); 
     3727      elseif( $this->getConfig( 'allowEmpty' )) 
     3728        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] )); 
    35473729    } 
    35483730    return $output; 
     
    35523734 * 
    35533735 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3554  * @since 2.5.1 - 2008-11-07 
     3736 * @since 2.11.24 - 2012-02-23 
    35553737 * @param float $relid 
    35563738 * @param array $params, optional 
     
    35603742  function setRelatedTo( $value, $params=FALSE, $index=FALSE ) { 
    35613743    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; 
    3562     if(( '<' == substr( $value, 0, 1 )) && ( '>' == substr( $value, -1 ))) 
    3563       $value = substr( $value, 1, ( strlen( $value ) - 2 )); 
    35643744    iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default 
    35653745    iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index ); 
     
    35853765  } 
    35863766/** 
    3587  * set calendar component property transp 
     3767 * set calendar component property repeat 
    35883768 * 
    35893769 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     
    37423922 * set calendar component property sequence 
    37433923 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3744  * @since 2.9.3 - 2011-05-14 
     3924 * @since 2.10.8 - 2011-09-19 
    37453925 * @param int $value optional 
    37463926 * @param array $params optional 
     
    37493929  function setSequence( $value=FALSE, $params=FALSE ) { 
    37503930    if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value )) 
    3751       $value = ( isset( $this->sequence['value'] ) && ( 0 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : 1; 
     3931      $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0'; 
    37523932    $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); 
    37533933    return TRUE; 
     
    38694049        isset( $this->trigger['value']['month'] )  && 
    38704050        isset( $this->trigger['value']['day'] )) 
    3871       $content      .= iCalUtilityFunctions::_format_date_time( $this->trigger['value'] ); 
     4051      $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] ); 
    38724052    else { 
    38734053      if( TRUE !== $this->trigger['value']['relatedStart'] ) 
     
    38754055      if( $this->trigger['value']['before'] ) 
    38764056        $content    .= '-'; 
    3877       $content      .= iCalUtilityFunctions::_format_duration( $this->trigger['value'] ); 
     4057      $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] ); 
    38784058    } 
    38794059    $attributes     .= $this->_createParams( $this->trigger['params'] ); 
     
    38844064 * 
    38854065 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    3886  * @since 2.9.9 - 2011-06-17 
     4066 * @since 2.14.1 - 2012-09-20 
    38874067 * @param mixed $year 
    38884068 * @param mixed $month optional 
     
    39054085      else 
    39064086        return FALSE; 
    3907     if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp 
     4087    if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC 
    39084088      $params = iCalUtilityFunctions::_setParams( $month ); 
    39094089      $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 ); 
     
    39384118        if(     'P'  != $year[0] ) 
    39394119          $year       = substr( $year, 1 ); 
    3940         $date         = iCalUtilityFunctions::_duration_string( $year); 
     4120        $date         = iCalUtilityFunctions::_durationStr2arr( $year); 
    39414121      } 
    39424122      else   // date 
    3943         $date    = iCalUtilityFunctions::_date_time_string( $year, 7 ); 
    3944       unset( $year, $month, $day ); 
     4123        $date    = iCalUtilityFunctions::_strdate2date( $year, 7 ); 
     4124      unset( $year, $month, $day, $date['unparsedtext'] ); 
    39454125      if( empty( $date )) 
    39464126        $sec = 0; 
     
    42784458 * 
    42794459 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    4280  * @since 2.9.3 - 2011-05-14 
     4460 * @since 2.11.9 - 2012-01-16 
    42814461 * @param string $label 
    42824462 * @param mixed $value 
     
    42854465 */ 
    42864466  function setXprop( $label, $value, $params=FALSE ) { 
    4287     if( empty( $label )) return; 
     4467    if( empty( $label )) 
     4468      return FALSE; 
     4469    if( 'X-' != strtoupper( substr( $label, 0, 2 ))) 
     4470      return FALSE; 
    42884471    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; 
    42894472    $xprop           = array( 'value' => $value ); 
     
    43384521 * 
    43394522 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    4340  * @since 2.6.22 - 2010-12-06 
     4523 * @since 2.10.16 - 2011-10-28 
    43414524 * @param string $label property name 
    43424525 * @param string $attributes property attributes 
     
    43574540    $attachInlineBinary = FALSE; 
    43584541    $attachfmttype      = null; 
     4542    if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) { 
     4543      $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT' 
     4544                               , 'ref'      => $label 
     4545                               , 'type2'    => '(#PCDATA)' ); 
     4546    } 
    43594547    if( !empty( $attributes ))  { 
    43604548      $attributes  = trim( $attributes ); 
    4361       if ( 'xcal' == $this->format) { 
     4549      if ( 'xcal' == $this->format ) { 
    43624550        $attributes2 = explode( $this->intAttrDelimiter, $attributes ); 
    43634551        $attributes  = null; 
    4364         foreach( $attributes2 as $attribute ) { 
     4552        foreach( $attributes2 as $aix => $attribute ) { 
    43654553          $attrKVarr = explode( '=', $attribute ); 
    43664554          if( empty( $attrKVarr[0] )) 
     
    43684556          if( !isset( $attrKVarr[1] )) { 
    43694557            $attrValue = $attrKVarr[0]; 
    4370             $attrKey   = null; 
     4558            $attrKey   = $aix; 
    43714559          } 
    43724560          elseif( 2 == count( $attrKVarr)) { 
     
    44024590      } 
    44034591    } 
    4404     if(((( 'attach' == $label ) && !$attachInlineBinary ) || 
    4405          ( in_array( $label, array( 'tzurl', 'url' ))))      && ( 'xcal' == $this->format)) { 
     4592    if(( 'xcal' == $this->format) && 
     4593       ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) { 
    44064594      $pos = strrpos($content, "/"); 
    44074595      $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content; 
     
    44174605      if( 'attach' == $label ) { 
    44184606        $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes ); 
    4419         $content = $this->_createElement( 'extref', $attributes, null ); 
     4607        $content = $this->nl.$this->_createElement( 'extref', $attributes, null ); 
    44204608        $attributes = null; 
    44214609      } 
    44224610    } 
    4423     elseif(( 'attach' == $label ) && $attachInlineBinary && ( 'xcal' == $this->format)) { 
     4611    elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) { 
    44244612      $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute 
    44254613    } 
     
    44294617        case 'xcal': 
    44304618          $output .= ' /'; 
    4431           $output .= $this->elementStart2; 
     4619          $output .= $this->elementStart2.$this->nl; 
    44324620          return $output; 
    44334621          break; 
     
    44534641 * 
    44544642 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    4455  * @since 2.6.33 - 2010-12-18 
     4643 * @since 2.10.27 - 2012-01-16 
    44564644 * @param array $params  optional 
    44574645 * @param array $ctrKeys optional 
     
    44674655    $xparams = array(); 
    44684656    foreach( $params as $paramKey => $paramValue ) { 
     4657      if(( FALSE !== strpos( $paramValue, ':' )) || 
     4658         ( FALSE !== strpos( $paramValue, ';' )) || 
     4659         ( FALSE !== strpos( $paramValue, ',' ))) 
     4660        $paramValue = '"'.$paramValue.'"'; 
    44694661      if( ctype_digit( (string) $paramKey )) { 
    44704662        $xparams[]          = $paramValue; 
     
    44974689    if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys )) 
    44984690      $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE']; 
    4499     if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) 
     4691    if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) { 
    45004692      $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID']; 
     4693    } 
    45014694    if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys )) 
    45024695      $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE']; 
     
    45044697      $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE']; 
    45054698    if( isset( $params['CN'] )       && $CNattrKey ) { 
    4506       $attr1                = $this->intAttrDelimiter.'CN="'.$params['CN'].'"'; 
     4699      $attr1                = $this->intAttrDelimiter.'CN='.$params['CN']; 
    45074700      $CNattrExist          = TRUE; 
    45084701    } 
    4509     if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) 
    4510       $attr1               .= $this->intAttrDelimiter.'DIR="'.$params['DIR'].'"'; 
     4702    if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) { 
     4703      $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"'; 
     4704      $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim; 
     4705    } 
    45114706    if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys )) 
    4512       $attr1               .= $this->intAttrDelimiter.'SENT-BY="'.$params['SENT-BY'].'"'; 
    4513     if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) 
    4514       $attr1               .= $this->intAttrDelimiter.'ALTREP="'.$params['ALTREP'].'"'; 
     4707      $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY']; 
     4708    if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) { 
     4709      $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"'; 
     4710      $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim; 
     4711    } 
    45154712    if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) { 
    45164713      $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE']; 
     
    45284725 * 
    45294726 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    4530  * @since 2.4.8 - 2008-10-22 
     4727 * @since 2.14.1 - 2012-10-06 
    45314728 * @param array $recurlabel 
    45324729 * @param array $recurdata 
     
    45494746          } 
    45504747          case 'UNTIL': { 
    4551             $content2 .= ";UNTIL="; 
    4552             $content2 .= iCalUtilityFunctions::_format_date_time( $rulevalue ); 
     4748            $parno     = ( isset( $rulevalue['hour'] )) ? 7 : 3; 
     4749            $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno ); 
    45534750            break; 
    45544751          } 
     
    45924789                } 
    45934790                $content2 .= $content21.$content22; 
    4594                 ++$bydaycnt; 
     4791                $bydaycnt++; 
    45954792              } 
    45964793              else { 
     
    46004797                else { 
    46014798                  $content22 .= $valuePart; 
    4602                   ++$bydaycnt; 
     4799                  $bydaycnt++; 
    46034800                } 
    46044801                $content2 .= $content21.$content22; 
     
    46914888        $output = array(); 
    46924889        if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) { 
    4693           if( empty( $this->uid['value'] )) $this->_makeuid(); 
     4890          if( empty( $this->uid['value'] ))   $this->_makeuid(); 
    46944891                                              $output['UID']              = 1; 
    4695         } 
    4696         if( !empty( $this->dtstamp ))         $output['DTSTAMP']          = 1; 
     4892          if( empty( $this->dtstamp ))        $this->_makeDtstamp(); 
     4893                                              $output['DTSTAMP']          = 1; 
     4894        } 
    46974895        if( !empty( $this->summary ))         $output['SUMMARY']          = 1; 
    46984896        if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description ); 
     
    47404938        return $output; 
    47414939        break; 
     4940      case 'SETPROPERTYNAMES': 
     4941        return array_keys( $this->getConfig( 'propinfo' )); 
     4942        break; 
    47424943      case 'TZID': 
    47434944        return $this->dtzid; 
     
    47544955 * 
    47554956 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    4756  * @since 2.9.6 - 2011-05-14 
     4957 * @since 2.10.18 - 2011-10-28 
    47574958 * @param mixed  $config 
    47584959 * @param string $value 
     
    47624963  function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) { 
    47634964    if( is_array( $config )) { 
     4965      $ak = array_keys( $config ); 
     4966      foreach( $ak as $k ) { 
     4967        if( 'NEWLINECHAR' == strtoupper( $k )) { 
     4968          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] )) 
     4969            return FALSE; 
     4970          unset( $config[$k] ); 
     4971          break; 
     4972        } 
     4973      } 
    47644974      foreach( $config as $cKey => $cValue ) { 
    47654975        if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate )) 
     
    47935003      case 'NEWLINECHAR': 
    47945004        $this->nl = $value; 
     5005        $this->_createFormat(); 
    47955006        $subcfg = array( 'NL' => $value ); 
    47965007        $res    = TRUE; 
     
    50765287            if( $propix != $xpropno ) 
    50775288              $reduced[$xpropkey] = $xpropvalue; 
    5078             ++$xpropno; 
     5289            $xpropno++; 
    50795290          } 
    50805291        } 
     
    51155326 * 
    51165327 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    5117  * @since 2.10.1 - 2011-07-16 
     5328 * @since 2.12.4 - 2012-04-22 
    51185329 * @param string $propName, optional 
    51195330 * @param int @propix, optional, if specific property is wanted in case of multiply occurences 
     
    51235334 */ 
    51245335  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) { 
     5336    if( 'GEOLOCATION' == strtoupper( $propName )) { 
     5337      $content = $this->getProperty( 'LOCATION' ); 
     5338      $content = ( !empty( $content )) ? $content.' ' : ''; 
     5339      if(( FALSE === ( $geo     = $this->getProperty( 'GEO' ))) || empty( $geo )) 
     5340        return FALSE; 
     5341      if( 0.0 < $geo['latitude'] ) 
     5342        $sign   = '+'; 
     5343      else 
     5344        $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : ''; 
     5345      $content .= $sign.sprintf( "%09.6f", abs( $geo['latitude'] ));   // sprintf && lpad && float && sign !"#€%&/( 
     5346      $content  = rtrim( rtrim( $content, '0' ), '.' ); 
     5347      if( 0.0 < $geo['longitude'] ) 
     5348        $sign   = '+'; 
     5349      else 
     5350       $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : ''; 
     5351      return $content.$sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';   // sprintf && lpad && float && sign !"#€%&/( 
     5352    } 
    51255353    if( $this->_notExistProp( $propName )) return FALSE; 
    51265354    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP'; 
     
    51385366        $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array(); 
    51395367        while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak ))) 
    5140           ++$propix; 
     5368          $propix++; 
     5369        $this->propix[$propName] = $propix; 
    51415370        if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51425371        return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value']; 
     
    51455374        $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array(); 
    51465375        while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak ))) 
    5147           ++$propix; 
     5376          $propix++; 
     5377        $this->propix[$propName] = $propix; 
    51485378        if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51495379        return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value']; 
     
    51525382        $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array(); 
    51535383        while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak ))) 
    5154           ++$propix; 
     5384          $propix++; 
     5385        $this->propix[$propName] = $propix; 
    51555386        if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51565387        return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value']; 
     
    51625393        $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array(); 
    51635394        while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak ))) 
    5164           ++$propix; 
     5395          $propix++; 
     5396        $this->propix[$propName] = $propix; 
    51655397        if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51665398        return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value']; 
     
    51725404        $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array(); 
    51735405        while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak ))) 
    5174           ++$propix; 
     5406          $propix++; 
     5407        $this->propix[$propName] = $propix; 
    51755408        if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51765409        return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value']; 
     
    51825415        $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array(); 
    51835416        while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak ))) 
    5184           ++$propix; 
     5417          $propix++; 
     5418        $this->propix[$propName] = $propix; 
    51855419        if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    51865420        return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value']; 
     
    52105444        $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array(); 
    52115445        while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak ))) 
    5212           ++$propix; 
     5446          $propix++; 
     5447        $this->propix[$propName] = $propix; 
    52135448        if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52145449        return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value']; 
     
    52175452        $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array(); 
    52185453        while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak ))) 
    5219           ++$propix; 
     5454          $propix++; 
     5455        $this->propix[$propName] = $propix; 
    52205456        if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52215457        return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value']; 
     
    52245460        $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array(); 
    52255461        while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak ))) 
    5226           ++$propix; 
     5462          $propix++; 
     5463        $this->propix[$propName] = $propix; 
    52275464        if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52285465        return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value']; 
     
    52505487        while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak ))) 
    52515488          $propix++; 
     5489        $this->propix[$propName] = $propix; 
    52525490        if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52535491        return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value']; 
     
    52605498        while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak ))) 
    52615499          $propix++; 
     5500        $this->propix[$propName] = $propix; 
    52625501        if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52635502        return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value']; 
     
    52695508        $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array(); 
    52705509        while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak ))) 
    5271           ++$propix; 
     5510          $propix++; 
     5511        $this->propix[$propName] = $propix; 
    52725512        if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52735513        return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value']; 
     
    52765516        $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array(); 
    52775517        while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak ))) 
    5278           ++$propix; 
     5518          $propix++; 
     5519        $this->propix[$propName] = $propix; 
    52795520        if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52805521        return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value']; 
     
    52835524        $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array(); 
    52845525        while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak ))) 
    5285           ++$propix; 
     5526          $propix++; 
     5527        $this->propix[$propName] = $propix; 
    52865528        if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    52875529        return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value']; 
     
    53085550        $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array(); 
    53095551        while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak ))) 
    5310           ++$propix; 
     5552          $propix++; 
     5553        $this->propix[$propName] = $propix; 
    53115554        if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } 
    53125555        return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value']; 
     
    53455588                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] ); 
    53465589            else 
    5347               ++$xpropno; 
     5590              $xpropno++; 
    53485591          } 
    53495592          return FALSE; // not found ?? 
     
    53535596  } 
    53545597/** 
    5355  * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence 
    5356  * 
    5357  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    5358  * @since 2.8.8 - 2011-04-13 
     5598 * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence 
     5599 * 
     5600 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     5601 * @since 2.13.4 - 2012-08-07 
    53595602 * @param string $propName 
    53605603 * @param array  $output, incremented result array 
    53615604 */ 
    53625605  function _getProperties( $propName, & $output ) { 
    5363     if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' ))) 
    5364       return output; 
     5606    if( empty( $output )) 
     5607      $output = array(); 
     5608    if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' ))) 
     5609      return $output; 
    53655610    while( FALSE !== ( $content = $this->getProperty( $propName ))) { 
     5611      if( empty( $content )) 
     5612        continue; 
    53665613      if( is_array( $content )) { 
    53675614        foreach( $content as $part ) { 
     
    53865633          } 
    53875634        } 
    5388       } 
     5635      } // end if( is_array( $content )) 
    53895636      elseif( FALSE !== strpos( $content, ',' )) { 
    53905637        $content = explode( ',', $content ); 
     
    53985645          } 
    53995646        } 
    5400       } 
     5647      } // end elseif( FALSE !== strpos( $content, ',' )) 
    54015648      else { 
    54025649        $content = trim( $content ); 
     
    54105657    } 
    54115658    ksort( $output ); 
    5412     return $output; 
    54135659  } 
    54145660/** 
     
    54305676      return FALSE; 
    54315677    $arglist[0] = strtoupper( $arglist[0] ); 
    5432     for( $argix=$numargs; $argix < 12; ++$argix ) { 
     5678    for( $argix=$numargs; $argix < 12; $argix++ ) { 
    54335679      if( !isset( $arglist[$argix] )) 
    54345680        $arglist[$argix] = null; 
     
    55315777 * 
    55325778 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    5533  * @since 2.10.2 - 2011-07-17 
     5779 * @since 2.15.10 - 2012-10-28 
    55345780 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings 
    55355781 * @return bool FALSE if error occurs during parsing 
     
    55375783 */ 
    55385784  function parse( $unparsedtext=null ) { 
     5785    $nl = $this->getConfig( 'nl' ); 
    55395786    if( !empty( $unparsedtext )) { 
    5540       $nl = $this->getConfig( 'nl' ); 
    55415787      if( is_array( $unparsedtext )) 
    55425788        $unparsedtext = implode( '\n'.$nl, $unparsedtext ); 
    5543             /* fix line folding */ 
    5544       $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings 
    5545       $EOLmark = FALSE; 
    5546       foreach( $eolchars as $eolchar ) { 
    5547         if( !$EOLmark  && ( FALSE !== strpos( $unparsedtext, $eolchar ))) { 
    5548           $unparsedtext = str_replace( $eolchar." ",  '',  $unparsedtext ); 
    5549           $unparsedtext = str_replace( $eolchar."\t", '',  $unparsedtext ); 
    5550           if( $eolchar != $nl ) 
    5551             $unparsedtext = str_replace( $eolchar,    $nl, $unparsedtext ); 
    5552           $EOLmark = TRUE; 
    5553         } 
    5554       } 
    5555       $tmp = explode( $nl, $unparsedtext ); 
    5556       $unparsedtext = array(); 
    5557       foreach( $tmp as $tmpr ) 
    5558         if( !empty( $tmpr )) 
    5559           $unparsedtext[] = $tmpr; 
     5789      $unparsedtext = explode( $nl, iCalUtilityFunctions::convEolChar( $unparsedtext, $nl )); 
    55605790    } 
    55615791    elseif( !isset( $this->unparsed )) 
     
    55635793    else 
    55645794      $unparsedtext = $this->unparsed; 
     5795            /* skip leading (empty/invalid) lines */ 
     5796    foreach( $unparsedtext as $lix => $line ) { 
     5797      $tst = trim( $line ); 
     5798      if(( '\n' == $tst ) || empty( $tst )) 
     5799        unset( $unparsedtext[$lix] ); 
     5800      else 
     5801        break; 
     5802    } 
    55655803    $this->unparsed = array(); 
    5566     $comp = & $this; 
    5567     $config = $this->getConfig(); 
    5568     foreach ( $unparsedtext as $line ) { 
    5569  // echo $comp->objName.": $line<br />"; // test ### 
    5570       if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' ))) 
    5571         $this->components[] = $comp->copy(); 
    5572       elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 ))) 
     5804    $comp           = & $this; 
     5805    $config         = $this->getConfig(); 
     5806    $compsync = $subsync = 0; 
     5807    foreach ( $unparsedtext as $lix => $line ) { 
     5808      if( 'END:VALARM'         == strtoupper( substr( $line, 0, 10 ))) { 
     5809        if( 1 != $subsync ) return FALSE; 
     5810        $this->components[]     = $comp->copy(); 
     5811        $subsync--; 
     5812      } 
     5813      elseif( 'END:DAYLIGHT'   == strtoupper( substr( $line, 0, 12 ))) { 
     5814        if( 1 != $subsync ) return FALSE; 
     5815        $this->components[]     = $comp->copy(); 
     5816        $subsync--; 
     5817      } 
     5818      elseif( 'END:STANDARD'   == strtoupper( substr( $line, 0, 12 ))) { 
     5819        if( 1 != $subsync ) return FALSE; 
    55735820        array_unshift( $this->components, $comp->copy()); 
    5574       elseif( 'END:' == strtoupper( substr( $line, 0, 4 ))) 
    5575         break; 
    5576       elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) 
     5821        $subsync--; 
     5822      } 
     5823      elseif( 'END:'           == strtoupper( substr( $line, 0, 4 ))) { // end:<component> 
     5824        if( 1 != $compsync ) return FALSE; 
     5825        if( 0 < $subsync ) 
     5826          $this->components[]   = $comp->copy(); 
     5827        $compsync--; 
     5828        break;                       /* skip trailing empty lines */ 
     5829      } 
     5830      elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) { 
    55775831        $comp = new valarm( $config); 
    5578       elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) 
     5832        $subsync++; 
     5833      } 
     5834      elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) { 
    55795835        $comp = new vtimezone( 'standard', $config ); 
    5580       elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) 
     5836        $subsync++; 
     5837      } 
     5838      elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) { 
    55815839        $comp = new vtimezone( 'daylight', $config ); 
    5582       elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 ))) 
    5583         continue; 
    5584       else { 
    5585         $comp->unparsed[] = $line; 
    5586 // echo $comp->objName.": $line<br />\n"; // test ### 
    5587       } 
    5588     } 
     5840        $subsync++; 
     5841      } 
     5842      elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))  // begin:<component> 
     5843        $compsync++; 
     5844      else 
     5845        $comp->unparsed[]       = $line; 
     5846    } 
     5847    if( 0 < $subsync ) 
     5848      $this->components[]   = $comp->copy(); 
    55895849    unset( $config ); 
    5590 // echo $this->objName.'<br />'.var_export( $this->unparsed, TRUE )."<br />\n"; // test ### 
    55915850            /* concatenate property values spread over several lines */ 
    55925851    $lastix    = -1; 
     
    56005859                      , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' ); 
    56015860    $proprows  = array(); 
    5602     foreach( $this->unparsed as $line ) { 
    5603       $newProp = FALSE; 
    5604       foreach ( $propnames as $propname ) { 
    5605         if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) { 
    5606           $newProp = TRUE; 
    5607           break; 
    5608         } 
    5609       } 
    5610       if( $newProp ) { 
    5611         if( -1 < $lastix ) 
    5612           $proprows[$lastix] = $proprows[$lastix]; 
    5613         $newProp = FALSE; 
    5614         ++$lastix; 
    5615         $proprows[$lastix]  = $line; 
    5616       } 
    5617       else 
    5618         $proprows[$lastix] .= '!"#€%&/()=?'.$line; 
     5861    for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines 
     5862      $line = rtrim( $this->unparsed[$i], $nl ); 
     5863      while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} )) 
     5864        $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl ); 
     5865      $proprows[] = $line; 
    56195866    } 
    56205867            /* parse each property 'line' */ 
     5868    $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' ); 
     5869    $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ); 
     5870    $paramProto4 = array( 'crid:', 'news:', 'pres:' ); 
    56215871    foreach( $proprows as $line ) { 
    5622       $line = str_replace( '!"#€%&/()=? ', '', $line ); 
    5623       $line = str_replace( '!"#€%&/()=?', '', $line ); 
    56245872      if( '\n' == substr( $line, -2 )) 
    5625         $line = substr( $line, 0, strlen( $line ) - 2 ); 
    5626             /* get propname, (problem with x-properties, otherwise in previous loop) */ 
    5627       $cix = $propname = null; 
    5628       for( $cix=0, $clen = strlen( $line ); $cix < $clen; ++$cix ) { 
     5873        $line = substr( $line, 0, -2 ); 
     5874            /* get propname */ 
     5875      $propname = null; 
     5876      $cix = 0; 
     5877      while( isset( $line[$cix] )) { 
    56295878        if( in_array( $line[$cix], array( ':', ';' ))) 
    56305879          break; 
    5631         else { 
     5880        else 
    56325881          $propname .= $line[$cix]; 
    5633         } 
     5882        $cix++; 
    56345883      } 
    56355884      if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) { 
     
    56375886        $propname  = 'X-'; 
    56385887      } 
     5888      if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names 
     5889        continue; 
    56395890            /* rest of the line is opt.params and value */ 
    56405891      $line = substr( $line, $cix ); 
    56415892            /* separate attributes from value */ 
    5642       $attr   = array(); 
    5643       $attrix = -1; 
    5644       $clen = strlen( $line ); 
    5645       for( $cix=0; $cix < $clen; ++$cix ) { 
    5646         if((       ':'   == $line[$cix] )             && 
    5647                  ( '://' != substr( $line, $cix, 3 )) && 
    5648              ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) && 
    5649              ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) && 
    5650            ( 'mailto:'   != strtolower( substr( $line, $cix - 6, 7 )))) { 
     5893      $attr         = array(); 
     5894      $attrix       = -1; 
     5895      $clen         = strlen( $line ); 
     5896      $WithinQuotes = FALSE; 
     5897      $cix          = 0; 
     5898      while( FALSE !== substr( $line, $cix, 1 )) { 
     5899        if(                       (  ':' == $line[$cix] )                         && 
     5900                                  ( substr( $line,$cix,     3 )  != '://' )       && 
     5901           ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   && 
     5902           ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) && 
     5903           ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) && 
     5904                      ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   && 
     5905             !$WithinQuotes ) { 
    56515906          $attrEnd = TRUE; 
    56525907          if(( $cix < ( $clen - 4 )) && 
     
    56605915          } 
    56615916          if( $attrEnd) { 
    5662             $line = substr( $line, $cix + 1 ); 
     5917            $line = substr( $line, ( $cix + 1 )); 
    56635918            break; 
    56645919          } 
    5665         } 
     5920          $cix++; 
     5921        } 
     5922        if( '"' == $line[$cix] ) 
     5923          $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE; 
    56665924        if( ';' == $line[$cix] ) 
    56675925          $attr[++$attrix] = null; 
    56685926        else 
    56695927          $attr[$attrix] .= $line[$cix]; 
     5928        $cix++; 
    56705929      } 
    56715930            /* make attributes in array format */ 
     
    56825941        case 'ATTENDEE': 
    56835942          foreach( $propattr as $pix => $attr ) { 
     5943            if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' ))) 
     5944              continue; 
    56845945            $attr2 = explode( ',', $attr ); 
    56855946              if( 1 < count( $attr2 )) 
     
    56885949          $this->setProperty( $propname, $line, $propattr ); 
    56895950          break; 
     5951        case 'X-': 
     5952          $propname = ( isset( $propname2 )) ? $propname2 : $propname; 
     5953          unset( $propname2 ); 
    56905954        case 'CATEGORIES': 
    56915955        case 'RESOURCES': 
    56925956          if( FALSE !== strpos( $line, ',' )) { 
    5693             $content  = explode( ',', $line ); 
    5694             $clen     = count( $content ); 
    5695             for( $cix = 0; $cix < $clen; ++$cix ) { 
    5696               if( "\\" == substr($content[$cix], -1)) { 
    5697                 $content[$cix] .= ','.$content[$cix + 1]; 
    5698                 unset($content[$cix + 1]); 
    5699                 ++$cix; 
     5957            $content  = array( 0 => '' ); 
     5958            $cix = $lix = 0; 
     5959            while( FALSE !== substr( $line, $lix, 1 )) { 
     5960              if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) { 
     5961                $cix++; 
     5962                $content[$cix] = ''; 
    57005963              } 
     5964              else 
     5965                $content[$cix] .= $line[$lix]; 
     5966              $lix++; 
    57015967            } 
    57025968            if( 1 < count( $content )) { 
     
    57105976              $line = reset( $content ); 
    57115977          } 
    5712         case 'X-': 
    5713           $propname = ( isset( $propname2 )) ? $propname2 : $propname; 
    57145978        case 'COMMENT': 
    57155979        case 'CONTACT': 
     
    57205984            $propattr = null; 
    57215985          $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr ); 
    5722           unset( $propname2 ); 
    57235986          break; 
    57245987        case 'REQUEST-STATUS': 
     
    58566119 * 
    58576120 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    5858  * @since 2.8.8 - 2011-03-15 
     6121 * @since 2.15.4 - 2012-10-18 
    58596122 * @return object 
    58606123 */ 
    58616124  function copy() { 
    5862     $serialized_contents = serialize( $this ); 
    5863     $copy = unserialize( $serialized_contents ); 
    5864     return $copy; 
     6125    return unserialize( serialize( $this )); 
    58656126 } 
    58666127/*********************************************************************************/ 
     
    58986159          return TRUE; 
    58996160        } 
    5900         ++$cix2dC; 
     6161        $cix2dC++; 
    59016162      } 
    59026163      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { 
     
    59496210         if( $index == $cix2gC ) 
    59506211           return $component->copy(); 
    5951          ++$cix2gC; 
     6212         $cix2gC++; 
    59526213      } 
    59536214      elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' ))) 
     
    60426303          return TRUE; 
    60436304        } 
    6044         ++$cix2sC; 
     6305        $cix2sC++; 
    60456306      } 
    60466307      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace 
     
    60626323 * 
    60636324 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    6064  * @since 2.6.27 - 2010-12-12 
     6325 * @since 2.11.20 - 2012-02-06 
     6326 * @param array $xcaldecl 
    60656327 * @return string 
    60666328 */ 
    60676329  function createSubComponent() { 
    60686330    $output = null; 
     6331    if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order 
     6332      $stdarr = $dlarr = array(); 
     6333      foreach( $this->components as $component ) { 
     6334        if( empty( $component )) 
     6335          continue; 
     6336        $dt  = $component->getProperty( 'dtstart' ); 
     6337        $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] ); 
     6338        if( 'standard' == $component->objName ) { 
     6339          while( isset( $stdarr[$key] )) 
     6340            $key += 1; 
     6341          $stdarr[$key] = $component->copy(); 
     6342        } 
     6343        elseif( 'daylight' == $component->objName ) { 
     6344          while( isset( $dlarr[$key] )) 
     6345            $key += 1; 
     6346          $dlarr[$key] = $component->copy(); 
     6347        } 
     6348      } // end foreach( $this->components as $component ) 
     6349      $this->components = array(); 
     6350      ksort( $stdarr, SORT_NUMERIC ); 
     6351      foreach( $stdarr as $std ) 
     6352        $this->components[] = $std->copy(); 
     6353      unset( $stdarr ); 
     6354      ksort( $dlarr,  SORT_NUMERIC ); 
     6355      foreach( $dlarr as $dl ) 
     6356        $this->components[] = $dl->copy(); 
     6357      unset( $dlarr ); 
     6358    } // end if( 'vtimezone' == $this->objName ) 
    60696359    foreach( $this->components as $component ) { 
    6070       if( empty( $component )) continue; 
    60716360      $component->setConfig( $this->getConfig(), FALSE, TRUE ); 
    60726361      $output .= $component->createComponent( $this->xcaldecl ); 
     
    60906379 * the reserved expression "\n" in the arg $string could be broken up by the 
    60916380 * folding of lines, causing ambiguity in the return string. 
    6092  * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be. 
    6093  * 
    6094  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    6095  * @since 2.6.13 - 2010-12-06 
     6381 * 
     6382 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     6383 * @since 2.12.17 - 2012-07-15 
    60966384 * @param string $value 
    60976385 * @return string 
    60986386 */ 
    60996387  function _size75( $string ) { 
    6100     $tmp    = $string; 
    6101     $string = null; 
    6102             /* if PHP is config with  mb_string.. . */ 
    6103     if( defined( MB_OVERLOAD_STRING )) { 
    6104       $strlen  = mb_strlen( $tmp ); 
    6105       while( $strlen > 75 ) { 
    6106         $breakAtChar = 75; 
    6107         if( substr( $tmp, ( $breakAtChar - 1 ), strlen( '\n' )) == '\n' ) 
    6108           $breakAtChar = $breakAtChar - 1; 
    6109         $string .= mb_substr( $tmp, 0, $breakAtChar ); 
    6110         if( '\n' == substr( $string, ( 0 - strlen( '\n' )))) 
    6111           $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n' ))); 
    6112         if( $this->nl != mb_substr( $string, ( 0 - strlen( $this->nl )))) 
    6113           $string .= $this->nl; 
    6114         $tmp     = ' '.mb_substr( $tmp, $breakAtChar ); 
    6115         $strlen  = mb_strlen( $tmp ); 
    6116       } // end while 
    6117       if( 0 < $strlen ) { 
    6118         $string .= $tmp; // the rest 
    6119         if( '\n' == substr( $string, ( 0 - strlen( '\n' )))) 
    6120           $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n' ))); 
    6121         if( $this->nl != mb_substr( $string, ( 0 - strlen( $this->nl )))) 
    6122           $string .= $this->nl; 
    6123       } 
    6124       return $string; 
    6125     } 
    6126             /* if PHP is not config with  mb_string.. . */ 
    6127     $eolcharlen = strlen( '\n' ); 
     6388    $tmp             = $string; 
     6389    $string          = ''; 
     6390    $cCnt = $x       = 0; 
    61286391    while( TRUE ) { 
    6129       $bytecnt = strlen( $tmp ); 
    6130       $charCnt = $ix = 0; 
    6131       for( $ix = 0; $ix < $bytecnt; ++$ix ) { 
    6132         if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen ))) { 
    6133           $ix += $eolcharlen; 
    6134           break;                                    // break when '\n' and eol 
    6135         } 
    6136         elseif( 74 < $charCnt ) 
    6137           break;                                    // always break for-loop here 
    6138         else { 
    6139           $byte = ord( $tmp[$ix] ); 
    6140           if ($byte <= 127) {                       // add a one byte character 
    6141             $string .= substr( $tmp, $ix, 1 ); 
    6142             $charCnt += 1; 
     6392      if( !isset( $tmp[$x] )) { 
     6393        $string     .= $this->nl;                     // loop breakes here 
     6394        break; 
     6395      } 
     6396      elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) { 
     6397        $string     .= $this->nl.' \n';               // don't break lines inside '\n' 
     6398        $x          += 2; 
     6399        if( !isset( $tmp[$x] )) { 
     6400          $string   .= $this->nl; 
     6401          break; 
     6402        } 
     6403        $cCnt        = 3; 
     6404      } 
     6405      elseif( 75    <= $cCnt ) { 
     6406        $string     .= $this->nl.' '; 
     6407        $cCnt        = 1; 
     6408      } 
     6409      $byte          = ord( $tmp[$x] ); 
     6410      $string       .= $tmp[$x]; 
     6411      switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 
     6412        case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII) 
     6413          $cCnt     += 1; 
     6414          break;                                      // add a one byte character 
     6415        case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX 
     6416          if( isset( $tmp[$x+1] )) { 
     6417            $cCnt   += 1; 
     6418            $string  .= $tmp[$x+1]; 
     6419            $x       += 1;                            // add a two bytes character 
    61436420          } 
    6144           else if ($byte >= 194 && $byte <= 223) {  // start byte in two byte character 
    6145             $string .= substr( $tmp, $ix, 2 );      // add a two bytes character 
    6146             $charCnt += 1; 
     6421          break; 
     6422        case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX 
     6423          if( isset( $tmp[$x+2] )) { 
     6424            $cCnt   += 1; 
     6425            $string .= $tmp[$x+1].$tmp[$x+2]; 
     6426            $x      += 2;                             // add a three bytes character 
    61476427          } 
    6148           else if ($byte >= 224 && $byte <= 239) {  // start byte in three bytes character 
    6149             $string .= substr( $tmp, $ix, 3 );      // add a three bytes character 
    6150             $charCnt += 1; 
     6428          break; 
     6429        case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX 
     6430          if( isset( $tmp[$x+3] )) { 
     6431            $cCnt   += 1; 
     6432            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3]; 
     6433            $x      += 3;                             // add a four bytes character 
    61516434          } 
    6152           else if ($byte >= 240 && $byte <= 244) {  // start byte in four bytes character 
    6153             $string .= substr( $tmp, $ix, 4 );      // add a four bytes character 
    6154             $charCnt += 1; 
     6435          break; 
     6436        case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX 
     6437          if( isset( $tmp[$x+4] )) { 
     6438            $cCnt   += 1; 
     6439            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4]; 
     6440            $x      += 4;                             // add a five bytes character 
    61556441          } 
    6156         } 
    6157       } // end for 
    6158       if( '\n' == substr( $string, ( 0 - strlen( '\n' )))) 
    6159         $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n' ))); 
    6160       if( $this->nl != substr( $string, ( 0 - strlen( $this->nl )))) 
    6161         $string .= $this->nl; 
    6162       $tmp     = substr( $tmp, $ix ); 
    6163       if( empty( $tmp )) 
    6164         break; // while-loop breakes here 
    6165       else 
    6166         $tmp  = ' '.$tmp; 
    6167     } // end while 
    6168     if( !empty( $tmp )) { 
    6169       if( '\n' == substr( $string, ( 0 - strlen( '\n' )))) 
    6170         $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n' ))).$this->nl; 
    6171       if( $this->nl != substr( $string, ( 0 - strlen( $this->nl )))) 
    6172         $string .= $this->nl; 
    6173     } 
     6442          break; 
     6443        case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X 
     6444          if( isset( $tmp[$x+5] )) { 
     6445            $cCnt   += 1; 
     6446            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5]; 
     6447            $x      += 5;                             // add a six bytes character 
     6448          } 
     6449        default:                                      // add any other byte without counting up $cCnt 
     6450          break; 
     6451      } // end switch( TRUE ) 
     6452      $x         += 1;                                // next 'byte' to test 
     6453    } // end while( TRUE ) { 
    61746454    return $string; 
    61756455  } 
     
    61786458 * 
    61796459 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    6180  * @since 2.6.15 - 2010-09-24 
     6460 * @since 2.12.16 - 2012-07-16 
    61816461 * @param string $string 
    61826462 * @return string 
     
    61916471        $pos = 0; 
    61926472        $specChars = array( 'n', 'N', 'r', ',', ';' ); 
    6193         while( $pos <= strlen( $string )) { 
    6194           $pos = strpos( $string, "\\", $pos ); 
    6195           if( FALSE === $pos ) 
     6473        while( isset( $string[$pos] )) { 
     6474          if( FALSE === ( $pos = strpos( $string, "\\", $pos ))) 
    61966475            break; 
    61976476          if( !in_array( substr( $string, $pos, 1 ), $specChars )) { 
     
    62076486        if( FALSE !== strpos( $string, ';' )) 
    62086487          $string = str_replace(';',   '\;',      $string); 
    6209  
    62106488        if( FALSE !== strpos( $string, "\r\n" )) 
    62116489          $string = str_replace( "\r\n", '\n',    $string); 
    62126490        elseif( FALSE !== strpos( $string, "\r" )) 
    62136491          $string = str_replace( "\r", '\n',      $string); 
    6214  
    62156492        elseif( FALSE !== strpos( $string, "\n" )) 
    62166493          $string = str_replace( "\n", '\n',      $string); 
    6217  
    62186494        if( FALSE !== strpos( $string, '\N' )) 
    62196495          $string = str_replace( '\N', '\n',      $string); 
     
    63386614 * 
    63396615 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
    6340  * @since 2.5.1 - 2008-11-07 
     6616 * @since 2.10.16 - 2011-10-28 
    63416617 * @param array $xcaldecl 
    63426618 * @return string 
     
    68107086    $component    .= $this->createXprop(); 
    68117087    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2; 
     7088    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { 
     7089      foreach( $this->xcaldecl as $localxcaldecl ) 
     7090        $xcaldecl[] = $localxcaldecl; 
     7091    } 
    68127092    return $component; 
    68137093  } 
     
    69117191  } 
    69127192} 
     7193/*********************************************************************************/ 
     7194/*********************************************************************************/ 
     7195/** 
     7196 * moving all utility (static) functions to a utility class 
     7197 * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file 
     7198 * 
     7199 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7200 * @since 2.10.1 - 2011-07-16 
     7201 * 
     7202 */ 
     7203class iCalUtilityFunctions { 
     7204  // Store the single instance of iCalUtilityFunctions 
     7205  private static $m_pInstance; 
     7206 
     7207  // Private constructor to limit object instantiation to within the class 
     7208  private function __construct() { 
     7209    $m_pInstance = FALSE; 
     7210  } 
     7211 
     7212  // Getter method for creating/returning the single instance of this class 
     7213  public static function getInstance() { 
     7214    if (!self::$m_pInstance) 
     7215      self::$m_pInstance = new iCalUtilityFunctions(); 
     7216 
     7217    return self::$m_pInstance; 
     7218  } 
     7219/** 
     7220 * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed) 
     7221 * 
     7222 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7223 * @since 2.14.1 - 2012-09-27 
     7224 * @param array $datetime 
     7225 * @param int $parno optional, default FALSE 
     7226 * @return array 
     7227 */ 
     7228  public static function _date_time_array( $datetime, $parno=FALSE ) { 
     7229    return iCalUtilityFunctions::_chkDateArr( $datetime, $parno ); 
     7230  } 
     7231  public static function _chkDateArr( $datetime, $parno=FALSE ) { 
     7232    $output = array(); 
     7233    foreach( $datetime as $dateKey => $datePart ) { 
     7234      switch ( $dateKey ) { 
     7235        case '0': case 'year':   $output['year']  = $datePart; break; 
     7236        case '1': case 'month':  $output['month'] = $datePart; break; 
     7237        case '2': case 'day':    $output['day']   = $datePart; break; 
     7238      } 
     7239      if( 3 != $parno ) { 
     7240        switch ( $dateKey ) { 
     7241          case '0': 
     7242          case '1': 
     7243          case '2': break; 
     7244          case '3': case 'hour': $output['hour']  = $datePart; break; 
     7245          case '4': case 'min' : $output['min']   = $datePart; break; 
     7246          case '5': case 'sec' : $output['sec']   = $datePart; break; 
     7247          case '6': case 'tz'  : $output['tz']    = $datePart; break; 
     7248        } 
     7249      } 
     7250    } 
     7251    if( 3 != $parno ) { 
     7252      if( !isset( $output['hour'] ))         $output['hour'] = 0; 
     7253      if( !isset( $output['min']  ))         $output['min']  = 0; 
     7254      if( !isset( $output['sec']  ))         $output['sec']  = 0; 
     7255      if( isset( $output['tz'] ) && 
     7256        (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] ))) 
     7257                                             $output['tz']   = 'Z'; 
     7258    } 
     7259    return $output; 
     7260  } 
     7261/** 
     7262 * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params) 
     7263 * 
     7264 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7265 * @since 2.10.30 - 2012-01-16 
     7266 * @param array $date, date to check 
     7267 * @param int $parno, no of date parts (i.e. year, month.. .) 
     7268 * @param array $params, property parameters 
     7269 * @return void 
     7270 */ 
     7271  public static function _chkdatecfg( $theDate, & $parno, & $params ) { 
     7272    if( isset( $params['TZID'] )) 
     7273      $parno = 6; 
     7274    elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )) 
     7275      $parno = 3; 
     7276    else { 
     7277      if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] )) 
     7278        $parno = 7; 
     7279      if( is_array( $theDate )) { 
     7280        if( isset( $theDate['timestamp'] )) 
     7281          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null; 
     7282        else 
     7283          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null; 
     7284        if( !empty( $tzid )) { 
     7285          $parno = 7; 
     7286          if( !iCalUtilityFunctions::_isOffset( $tzid )) 
     7287            $params['TZID'] = $tzid; // save only timezone 
     7288        } 
     7289        elseif( !$parno && ( 3 == count( $theDate )) && 
     7290          ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))) 
     7291          $parno = 3; 
     7292        else 
     7293          $parno = 6; 
     7294      } 
     7295      else { // string 
     7296        $date = trim( $theDate ); 
     7297        if( 'Z' == substr( $date, -1 )) 
     7298          $parno = 7; // UTC DATE-TIME 
     7299        elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) && 
     7300          ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' )))) 
     7301          $parno = 3; // DATE 
     7302        $date = iCalUtilityFunctions::_strdate2date( $date, $parno ); 
     7303        unset( $date['unparsedtext'] ); 
     7304        if( !empty( $date['tz'] )) { 
     7305          $parno = 7; 
     7306          if( !iCalUtilityFunctions::_isOffset( $date['tz'] )) 
     7307            $params['TZID'] = $date['tz']; // save only timezone 
     7308        } 
     7309        elseif( empty( $parno )) 
     7310          $parno = 6; 
     7311      } 
     7312      if( isset( $params['TZID'] )) 
     7313        $parno = 6; 
     7314    } 
     7315  } 
     7316/** 
     7317 * byte oriented line folding fix 
     7318 * 
     7319 * remove any line-endings that may include spaces or tabs 
     7320 * and convert all line endings (iCal default '\r\n'), 
     7321 * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n' 
     7322 * 
     7323 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7324 * @since 2.12.17 - 2012-07-12 
     7325 * @param string $text 
     7326 * @param string $nl 
     7327 * @return string 
     7328 */ 
     7329  public static function convEolChar( & $text, $nl ) { 
     7330    $outp = ''; 
     7331    $cix  = 0; 
     7332    while(    isset(   $text[$cix] )) { 
     7333      if(     isset(   $text[$cix + 2] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] ) && 
     7334        ((    " " ==   $text[$cix + 2] ) ||  ( "\t" == $text[$cix + 2] )))                    // 2 pos eolchar + ' ' or '\t' 
     7335        $cix  += 2;                                                                           // skip 3 
     7336      elseif( isset(   $text[$cix + 1] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] )) { 
     7337        $outp .= $nl;                                                                         // 2 pos eolchar 
     7338        $cix  += 1;                                                                           // replace with $nl 
     7339      } 
     7340      elseif( isset(   $text[$cix + 1] ) && (( "\r" == $text[$cix] ) || ( "\n" == $text[$cix] )) && 
     7341           (( " " ==   $text[$cix + 1] ) ||  ( "\t" == $text[$cix + 1] )))                     // 1 pos eolchar + ' ' or '\t' 
     7342        $cix  += 1;                                                                            // skip 2 
     7343      elseif(( "\r" == $text[$cix] )     ||  ( "\n" == $text[$cix] ))                          // 1 pos eolchar 
     7344        $outp .= $nl;                                                                          // replace with $nl 
     7345      else 
     7346        $outp .= $text[$cix];                                                                  // add any other byte 
     7347      $cix    += 1; 
     7348    } 
     7349    return $outp; 
     7350  } 
     7351/** 
     7352 * create a calendar timezone and standard/daylight components 
     7353 * 
     7354 * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone: 
     7355 * 
     7356 * BEGIN:VTIMEZONE 
     7357 * TZID:Europe/Stockholm 
     7358 * BEGIN:STANDARD 
     7359 * DTSTART:20101031T020000 
     7360 * TZOFFSETFROM:+0200 
     7361 * TZOFFSETTO:+0100 
     7362 * TZNAME:CET 
     7363 * END:STANDARD 
     7364 * BEGIN:DAYLIGHT 
     7365 * DTSTART:20100328T030000 
     7366 * TZOFFSETFROM:+0100 
     7367 * TZOFFSETTO:+0200 
     7368 * TZNAME:CEST 
     7369 * END:DAYLIGHT 
     7370 * END:VTIMEZONE 
     7371 * 
     7372 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7373 * @since 2.16.1 - 2012-11-26 
     7374 * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com> 
     7375 * Additional changes jpirkey 
     7376 * @param object $calendar, reference to an iCalcreator calendar instance 
     7377 * @param string $timezone, a PHP5 (DateTimeZone) valid timezone 
     7378 * @param array  $xProp,    *[x-propName => x-propValue], optional 
     7379 * @param int    $from      a unix timestamp 
     7380 * @param int    $to        a unix timestamp 
     7381 * @return bool 
     7382 */ 
     7383  public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) { 
     7384    if( empty( $timezone )) 
     7385      return FALSE; 
     7386    if( !empty( $from ) && !is_int( $from )) 
     7387      return FALSE; 
     7388    if( !empty( $to )   && !is_int( $to )) 
     7389      return FALSE; 
     7390    try { 
     7391      $dtz               = new DateTimeZone( $timezone ); 
     7392      $transitions       = $dtz->getTransitions(); 
     7393      $utcTz             = new DateTimeZone( 'UTC' ); 
     7394    } 
     7395    catch( Exception $e ) { return FALSE; } 
     7396    if( empty( $to )) { 
     7397      $dates             = array_keys( $calendar->getProperty( 'dtstart' )); 
     7398      if( empty( $dates )) 
     7399        $dates           = array( date( 'Ymd' )); 
     7400    } 
     7401    if( !empty( $from )) 
     7402      $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC) 
     7403    else { 
     7404      $from              = reset( $dates );                      // set lowest date to the lowest dtstart date 
     7405      $dateFrom          = new DateTime( $from.'T000000', $dtz ); 
     7406      $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date 
     7407      $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC 
     7408    } 
     7409    $dateFromYmd         = $dateFrom->format('Y-m-d' ); 
     7410    if( !empty( $to )) 
     7411      $dateTo            = new DateTime( "@$to" );               // set end date (UTC) 
     7412    else { 
     7413      $to                = end( $dates );                        // set highest date to the highest dtstart date 
     7414      $dateTo            = new DateTime( $to.'T235959', $dtz ); 
     7415      $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date 
     7416      $dateTo->setTimezone( $utcTz );                            // convert local date to UTC 
     7417    } 
     7418    $dateToYmd           = $dateTo->format('Y-m-d' ); 
     7419    unset( $dtz ); 
     7420    $transTemp           = array(); 
     7421    $prevOffsetfrom      = 0; 
     7422    $stdIx  = $dlghtIx   = null; 
     7423    $prevTrans           = FALSE; 
     7424    foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!! 
     7425      $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC) 
     7426      $transDateYmd      = $date->format('Y-m-d' ); 
     7427      if ( $transDateYmd < $dateFromYmd ) { 
     7428        $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom 
     7429        $prevTrans       = $trans;                               // save it in case we don't find any that match 
     7430        $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0; 
     7431        continue; 
     7432      } 
     7433      if( $transDateYmd > $dateToYmd ) 
     7434        break;                                                   // loop always (?) breaks here 
     7435      if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) { 
     7436        $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom 
     7437        $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date 
     7438        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart and (opt) rdate setting 
     7439        $d = explode( '-', $d ); 
     7440        $trans['time']   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] ); 
     7441      } 
     7442      $prevOffsetfrom    = $trans['offset']; 
     7443      if( TRUE !== $trans['isdst'] ) {                           // standard timezone 
     7444        if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order) 
     7445           ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        && 
     7446           ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  && 
     7447           ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) { 
     7448          $transTemp[$stdIx]['rdate'][]        =   $trans['time']; 
     7449          continue; 
     7450        } 
     7451        $stdIx           = $tix; 
     7452      } // end standard timezone 
     7453      else {                                                     // daylight timezone 
     7454        if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order) 
     7455           ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         && 
     7456           ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   && 
     7457           ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) { 
     7458          $transTemp[$dlghtIx]['rdate'][]        =   $trans['time']; 
     7459          continue; 
     7460        } 
     7461        $dlghtIx         = $tix; 
     7462      } // end daylight timezone 
     7463      $transTemp[$tix]   = $trans; 
     7464    } // end foreach( $transitions as $tix => $trans ) 
     7465    $tz  = & $calendar->newComponent( 'vtimezone' ); 
     7466    $tz->setproperty( 'tzid', $timezone ); 
     7467    if( !empty( $xProp )) { 
     7468      foreach( $xProp as $xPropName => $xPropValue ) 
     7469        if( 'x-' == strtolower( substr( $xPropName, 0, 2 ))) 
     7470          $tz->setproperty( $xPropName, $xPropValue ); 
     7471    } 
     7472    if( empty( $transTemp )) {      // if no match found 
     7473      if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info 
     7474        $date = new DateTime( "@{$prevTrans['ts']}" );           // set transition date (UTC) 
     7475        $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date 
     7476        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart setting 
     7477        $d = explode( '-', $d ); 
     7478        $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] ); 
     7479        $transTemp[0] = $prevTrans; 
     7480      } 
     7481      else {                        // or we use the timezone identifier to BUILD the standard tz info (?) 
     7482        $date = new DateTime( 'now', new DateTimeZone( $timezone )); 
     7483        $transTemp[0] = array( 'time'       => $date->format( 'Y-m-d\TH:i:s O' ) 
     7484                             , 'offset'     => $date->format( 'Z' ) 
     7485                             , 'offsetfrom' => $date->format( 'Z' ) 
     7486                             , 'isdst'      => FALSE ); 
     7487      } 
     7488    } 
     7489    unset( $transitions, $date, $prevTrans ); 
     7490    foreach( $transTemp as $tix => $trans ) { 
     7491      $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight'; 
     7492      $scomp = & $tz->newComponent( $type ); 
     7493      $scomp->setProperty( 'dtstart',         $trans['time'] ); 
     7494//      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ### 
     7495      if( !empty( $trans['abbr'] )) 
     7496        $scomp->setProperty( 'tzname',        $trans['abbr'] ); 
     7497      if( isset( $trans['offsetfrom'] )) 
     7498        $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] )); 
     7499      $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] )); 
     7500      if( isset( $trans['rdate'] )) 
     7501        $scomp->setProperty( 'RDATE',         $trans['rdate'] ); 
     7502    } 
     7503    return TRUE; 
     7504  } 
     7505/** 
     7506 * creates formatted output for calendar component property data value type date/date-time 
     7507 * 
     7508 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7509 * @since 2.14.1 - 2012-09-17 
     7510 * @param array   $datetime 
     7511 * @param int     $parno, optional, default 6 
     7512 * @return string 
     7513 */ 
     7514  public static function _format_date_time( $datetime, $parno=6 ) { 
     7515    return iCalUtilityFunctions::_date2strdate( $datetime, $parno ); 
     7516  } 
     7517  public static function _date2strdate( $datetime, $parno=6 ) { 
     7518    if( !isset( $datetime['year'] )  && 
     7519        !isset( $datetime['month'] ) && 
     7520        !isset( $datetime['day'] )   && 
     7521        !isset( $datetime['hour'] )  && 
     7522        !isset( $datetime['min'] )   && 
     7523        !isset( $datetime['sec'] )) 
     7524      return; 
     7525    $output     = null; 
     7526    foreach( $datetime as $dkey => & $dvalue ) 
     7527      if( 'tz' != $dkey ) $dvalue = (integer) $dvalue; 
     7528    $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] ); 
     7529    if( 3 == $parno ) 
     7530      return $output; 
     7531    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0; 
     7532    if( !isset( $datetime['min'] ))  $datetime['min']  = 0; 
     7533    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0; 
     7534    $output    .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] ); 
     7535    if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) { 
     7536      $datetime['tz'] = trim( $datetime['tz'] ); 
     7537      if( 'Z'  == $datetime['tz'] ) 
     7538        $parno  = 7; 
     7539      elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) { 
     7540        $parno  = 7; 
     7541        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ); 
     7542        try { 
     7543          $d    = new DateTime( $output, new DateTimeZone( 'UTC' )); 
     7544          if( 0 != $offset ) // adjust för offset 
     7545            $d->modify( "$offset seconds" ); 
     7546          $output = $d->format( 'Ymd\THis' ); 
     7547        } 
     7548        catch( Exception $e ) { 
     7549          $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] )); 
     7550        } 
     7551      } 
     7552      if( 7 == $parno ) 
     7553        $output .= 'Z'; 
     7554    } 
     7555    return $output; 
     7556  } 
     7557/** 
     7558 * convert a date/datetime (array) to timestamp 
     7559 * 
     7560 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7561 * @since 2.14.1 - 2012-09-29 
     7562 * @param array  $datetime  datetime(/date) 
     7563 * @param string $wtz       timezone 
     7564 * @return int 
     7565 */ 
     7566  public static function _date2timestamp( $datetime, $wtz=null ) { 
     7567    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0; 
     7568    if( !isset( $datetime['min'] ))  $datetime['min']  = 0; 
     7569    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0; 
     7570    if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty(  $datetime['tz'] ))) 
     7571      return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] ); 
     7572    $output = $offset = 0; 
     7573    if( empty( $wtz )) { 
     7574      if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) { 
     7575        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1; 
     7576        $wtz    = 'UTC'; 
     7577      } 
     7578      else 
     7579        $wtz    = $datetime['tz']; 
     7580    } 
     7581    if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz ))) 
     7582      $wtz      = 'UTC'; 
     7583    try { 
     7584      $strdate  = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] ); 
     7585      $d        = new DateTime( $strdate, new DateTimeZone( $wtz )); 
     7586      if( 0    != $offset )  // adjust for offset 
     7587        $d->modify( $offset.' seconds' ); 
     7588      $output   = $d->format( 'U' ); 
     7589      unset( $d ); 
     7590    } 
     7591    catch( Exception $e ) { 
     7592      $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] ); 
     7593    } 
     7594    return $output; 
     7595  } 
     7596/** 
     7597 * ensures internal duration format for input in array format 
     7598 * 
     7599 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7600 * @since 2.14.1 - 2012-09-25 
     7601 * @param array $duration 
     7602 * @return array 
     7603 */ 
     7604  public static function _duration_array( $duration ) { 
     7605    return iCalUtilityFunctions::_duration2arr( $duration ); 
     7606  } 
     7607  public static function _duration2arr( $duration ) { 
     7608    $output = array(); 
     7609    if(    is_array( $duration )        && 
     7610       ( 1 == count( $duration ))       && 
     7611              isset( $duration['sec'] ) && 
     7612              ( 60 < $duration['sec'] )) { 
     7613      $durseconds  = $duration['sec']; 
     7614      $output['week'] = (int) floor( $durseconds / ( 60 * 60 * 24 * 7 )); 
     7615      $durseconds     =              $durseconds % ( 60 * 60 * 24 * 7 ); 
     7616      $output['day']  = (int) floor( $durseconds / ( 60 * 60 * 24 )); 
     7617      $durseconds     =              $durseconds % ( 60 * 60 * 24 ); 
     7618      $output['hour'] = (int) floor( $durseconds / ( 60 * 60 )); 
     7619      $durseconds     =              $durseconds % ( 60 * 60 ); 
     7620      $output['min']  = (int) floor( $durseconds / ( 60 )); 
     7621      $output['sec']  =            ( $durseconds % ( 60 )); 
     7622    } 
     7623    else { 
     7624      foreach( $duration as $durKey => $durValue ) { 
     7625        if( empty( $durValue )) continue; 
     7626        switch ( $durKey ) { 
     7627          case '0': case 'week': $output['week']  = $durValue; break; 
     7628          case '1': case 'day':  $output['day']   = $durValue; break; 
     7629          case '2': case 'hour': $output['hour']  = $durValue; break; 
     7630          case '3': case 'min':  $output['min']   = $durValue; break; 
     7631          case '4': case 'sec':  $output['sec']   = $durValue; break; 
     7632        } 
     7633      } 
     7634    } 
     7635    if( isset( $output['week'] ) && ( 0 < $output['week'] )) { 
     7636      unset( $output['day'], $output['hour'], $output['min'], $output['sec'] ); 
     7637      return $output; 
     7638    } 
     7639    unset( $output['week'] ); 
     7640    if( empty( $output['day'] )) 
     7641      unset( $output['day'] ); 
     7642    if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) { 
     7643      if( !isset( $output['hour'] )) $output['hour'] = 0; 
     7644      if( !isset( $output['min']  )) $output['min']  = 0; 
     7645      if( !isset( $output['sec']  )) $output['sec']  = 0; 
     7646      if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] )) 
     7647        unset( $output['hour'], $output['min'], $output['sec'] ); 
     7648    } 
     7649    return $output; 
     7650  } 
     7651/** 
     7652 * convert startdate+duration to a array format datetime 
     7653 * 
     7654 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7655 * @since 2.15.12 - 2012-10-31 
     7656 * @param array   $startdate 
     7657 * @param array   $duration 
     7658 * @return array, date format 
     7659 */ 
     7660  public static function _duration2date( $startdate, $duration ) { 
     7661    $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE; 
     7662    $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0; 
     7663    $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0; 
     7664    $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0; 
     7665    $dtend = 0; 
     7666    if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 ); 
     7667    if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 ); 
     7668    if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 ); 
     7669    if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 ); 
     7670    if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec']; 
     7671    $date     = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] )); 
     7672    $d        = explode( '-', $date ); 
     7673    $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] ); 
     7674    if( isset( $startdate['tz'] )) 
     7675      $dtend2['tz']   = $startdate['tz']; 
     7676    if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] ))) 
     7677      unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] ); 
     7678    return $dtend2; 
     7679  } 
     7680/** 
     7681 * ensures internal duration format for an input string (iCal) formatted duration 
     7682 * 
     7683 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7684 * @since 2.14.1 - 2012-09-25 
     7685 * @param string $duration 
     7686 * @return array 
     7687 */ 
     7688  public static function _duration_string( $duration ) { 
     7689    return iCalUtilityFunctions::_durationStr2arr( $duration ); 
     7690  } 
     7691  public static function _durationStr2arr( $duration ) { 
     7692    $duration = (string) trim( $duration ); 
     7693    while( 'P' != strtoupper( substr( $duration, 0, 1 ))) { 
     7694      if( 0 < strlen( $duration )) 
     7695        $duration = substr( $duration, 1 ); 
     7696      else 
     7697        return false; // no leading P !?!? 
     7698    } 
     7699    $duration = substr( $duration, 1 ); // skip P 
     7700    $duration = str_replace ( 't', 'T', $duration ); 
     7701    $duration = str_replace ( 'T', '', $duration ); 
     7702    $output = array(); 
     7703    $val    = null; 
     7704    for( $ix=0; $ix < strlen( $duration ); $ix++ ) { 
     7705      switch( strtoupper( substr( $duration, $ix, 1 ))) { 
     7706       case 'W': 
     7707         $output['week'] = $val; 
     7708         $val            = null; 
     7709         break; 
     7710       case 'D': 
     7711         $output['day']  = $val; 
     7712         $val            = null; 
     7713         break; 
     7714       case 'H': 
     7715         $output['hour'] = $val; 
     7716         $val            = null; 
     7717         break; 
     7718       case 'M': 
     7719         $output['min']  = $val; 
     7720         $val            = null; 
     7721         break; 
     7722       case 'S': 
     7723         $output['sec']  = $val; 
     7724         $val            = null; 
     7725         break; 
     7726       default: 
     7727         if( !ctype_digit( substr( $duration, $ix, 1 ))) 
     7728           return false; // unknown duration control character  !?!? 
     7729         else 
     7730           $val .= substr( $duration, $ix, 1 ); 
     7731      } 
     7732    } 
     7733    return iCalUtilityFunctions::_duration2arr( $output ); 
     7734  } 
     7735/** 
     7736 * creates formatted output for calendar component property data value type duration 
     7737 * 
     7738 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7739 * @since 2.15.8 - 2012-10-30 
     7740 * @param array $duration, array( week, day, hour, min, sec ) 
     7741 * @return string 
     7742 */ 
     7743  public static function _format_duration( $duration ) { 
     7744    return iCalUtilityFunctions::_duration2str( $duration ); 
     7745  } 
     7746  public static function _duration2str( $duration ) { 
     7747    if( isset( $duration['week'] ) || 
     7748        isset( $duration['day'] )  || 
     7749        isset( $duration['hour'] ) || 
     7750        isset( $duration['min'] )  || 
     7751        isset( $duration['sec'] )) 
     7752       $ok = TRUE; 
     7753    else 
     7754      return; 
     7755    if( isset( $duration['week'] ) && ( 0 < $duration['week'] )) 
     7756      return 'P'.$duration['week'].'W'; 
     7757    $output = 'P'; 
     7758    if( isset($duration['day'] ) && ( 0 < $duration['day'] )) 
     7759      $output .= $duration['day'].'D'; 
     7760    if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) || 
     7761       ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  || 
     7762       ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) { 
     7763      $output .= 'T'; 
     7764      $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H'; 
     7765      $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M'; 
     7766      $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S'; 
     7767    } 
     7768    if( 'P' == $output ) 
     7769      $output = 'PT0H0M0S'; 
     7770    return $output; 
     7771  } 
     7772/** 
     7773 * removes expkey+expvalue from array and returns hitval (if found) else returns elseval 
     7774 * 
     7775 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7776 * @since 2.4.16 - 2008-11-08 
     7777 * @param array $array 
     7778 * @param string $expkey, expected key 
     7779 * @param string $expval, expected value 
     7780 * @param int $hitVal optional, return value if found 
     7781 * @param int $elseVal optional, return value if not found 
     7782 * @param int $preSet optional, return value if already preset 
     7783 * @return int 
     7784 */ 
     7785  public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) { 
     7786    if( $preSet ) 
     7787      return $preSet; 
     7788    if( !is_array( $array ) || ( 0 == count( $array ))) 
     7789      return $elseVal; 
     7790    foreach( $array as $key => $value ) { 
     7791      if( strtoupper( $expkey ) == strtoupper( $key )) { 
     7792        if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) { 
     7793          unset( $array[$key] ); 
     7794          return $hitVal; 
     7795        } 
     7796      } 
     7797    } 
     7798    return $elseVal; 
     7799  } 
     7800/** 
     7801 * checks if input contains a (array formatted) date/time 
     7802 * 
     7803 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7804 * @since 2.11.8 - 2012-01-20 
     7805 * @param array $input 
     7806 * @return bool 
     7807 */ 
     7808  public static function _isArrayDate( $input ) { 
     7809    if( !is_array( $input )) 
     7810      return FALSE; 
     7811    if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 )))) 
     7812      return FALSE; 
     7813    if( 7 == count( $input )) 
     7814      return TRUE; 
     7815    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) 
     7816      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); 
     7817    if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] )) 
     7818      return FALSE; 
     7819    if( in_array( 0, $input )) 
     7820      return FALSE; 
     7821    if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] )) 
     7822      return FALSE; 
     7823    if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) && 
     7824         checkdate( (int) $input[1], (int) $input[2], (int) $input[0] )) 
     7825      return TRUE; 
     7826    $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y 
     7827    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) 
     7828      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); 
     7829    return FALSE; 
     7830  } 
     7831/** 
     7832 * checks if input array contains a timestamp date 
     7833 * 
     7834 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7835 * @since 2.4.16 - 2008-10-18 
     7836 * @param array $input 
     7837 * @return bool 
     7838 */ 
     7839  public static function _isArrayTimestampDate( $input ) { 
     7840    return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ; 
     7841  } 
     7842/** 
     7843 * controls if input string contains (trailing) UTC/iCal offset 
     7844 * 
     7845 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7846 * @since 2.14.1 - 2012-09-21 
     7847 * @param string $input 
     7848 * @return bool 
     7849 */ 
     7850  public static function _isOffset( $input ) { 
     7851    $input         = trim( (string) $input ); 
     7852    if( 'Z' == substr( $input, -1 )) 
     7853      return TRUE; 
     7854    elseif((   5 <= strlen( $input )) && 
     7855       ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) && 
     7856       (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 ))) 
     7857      return TRUE; 
     7858    elseif((    7 <= strlen( $input )) && 
     7859       ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && 
     7860       ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) 
     7861      return TRUE; 
     7862    return FALSE; 
     7863  } 
     7864/** 
     7865 * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone 
     7866 * matching (MS) UCT offset and time zone descriptors 
     7867 * 
     7868 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7869 * @since 2.14.1 - 2012-09-16 
     7870 * @param string $timezone, input/output variable reference 
     7871 * @return bool 
     7872 */ 
     7873  public static function ms2phpTZ( & $timezone ) { 
     7874    if( empty( $timezone )) 
     7875      return FALSE; 
     7876    $search = str_replace( '"', '', $timezone ); 
     7877    $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search ); 
     7878    if( '(UTC' != substr( $search, 0, 4 )) 
     7879      return FALSE; 
     7880    if( FALSE === ( $pos = strpos( $search, ')' ))) 
     7881      return FALSE; 
     7882    $pos    = strpos( $search, ')' ); 
     7883    $searchOffset = substr( $search, 4, ( $pos - 4 )); 
     7884    $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset )); 
     7885    while( ' ' ==substr( $search, ( $pos + 1 ))) 
     7886      $pos += 1; 
     7887    $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) )); 
     7888    $searchWords  = explode( ' ', $searchText ); 
     7889    $timezone_abbreviations = DateTimeZone::listAbbreviations(); 
     7890    $hits = array(); 
     7891    foreach( $timezone_abbreviations as $name => $transitions ) { 
     7892      foreach( $transitions as $cnt => $transition ) { 
     7893        if( empty( $transition['offset'] )      || 
     7894            empty( $transition['timezone_id'] ) || 
     7895          ( $transition['offset'] != $searchOffset )) 
     7896        continue; 
     7897        $cWords = explode( '/', $transition['timezone_id'] ); 
     7898        $cPrio   = $hitCnt = $rank = 0; 
     7899        foreach( $cWords as $cWord ) { 
     7900          if( empty( $cWord )) 
     7901            continue; 
     7902          $cPrio += 1; 
     7903          $sPrio  = 0; 
     7904          foreach( $searchWords as $sWord ) { 
     7905            if( empty( $sWord ) || ( 'time' == strtolower( $sWord ))) 
     7906              continue; 
     7907            $sPrio += 1; 
     7908            if( strtolower( $cWord ) == strtolower( $sWord )) { 
     7909              $hitCnt += 1; 
     7910              $rank   += ( $cPrio + $sPrio ); 
     7911            } 
     7912            else 
     7913              $rank += 10; 
     7914          } 
     7915        } 
     7916        if( 0 < $hitCnt ) { 
     7917          $hits[$rank][] = $transition['timezone_id']; 
     7918        } 
     7919      } 
     7920    } 
     7921    unset( $timezone_abbreviations ); 
     7922    if( empty( $hits )) 
     7923      return FALSE; 
     7924    ksort( $hits ); 
     7925    foreach( $hits as $rank => $tzs ) { 
     7926      if( !empty( $tzs )) { 
     7927        $timezone = reset( $tzs ); 
     7928        return TRUE; 
     7929      } 
     7930    } 
     7931    return FALSE; 
     7932  } 
     7933/** 
     7934 * transforms offset in seconds to [-/+]hhmm[ss] 
     7935 * 
     7936 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7937 * @since 2011-05-02 
     7938 * @param string $seconds 
     7939 * @return string 
     7940 */ 
     7941  public static function offsetSec2His( $seconds ) { 
     7942    if( '-' == substr( $seconds, 0, 1 )) { 
     7943      $prefix  = '-'; 
     7944      $seconds = substr( $seconds, 1 ); 
     7945    } 
     7946    elseif( '+' == substr( $seconds, 0, 1 )) { 
     7947      $prefix  = '+'; 
     7948      $seconds = substr( $seconds, 1 ); 
     7949    } 
     7950    else 
     7951      $prefix  = '+'; 
     7952    $output  = ''; 
     7953    $hour    = (int) floor( $seconds / 3600 ); 
     7954    if( 10 > $hour ) 
     7955      $hour  = '0'.$hour; 
     7956    $seconds = $seconds % 3600; 
     7957    $min     = (int) floor( $seconds / 60 ); 
     7958    if( 10 > $min ) 
     7959      $min   = '0'.$min; 
     7960    $output  = $hour.$min; 
     7961    $seconds = $seconds % 60; 
     7962    if( 0 < $seconds) { 
     7963      if( 9 < $seconds) 
     7964        $output .= $seconds; 
     7965      else 
     7966        $output .= '0'.$seconds; 
     7967    } 
     7968    return $prefix.$output; 
     7969  } 
     7970/** 
     7971 * updates an array with dates based on a recur pattern 
     7972 * 
     7973 * if missing, UNTIL is set 1 year from startdate (emergency break) 
     7974 * 
     7975 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     7976 * @since 2.10.19 - 2011-10-31 
     7977 * @param array $result, array to update, array([timestamp] => timestamp) 
     7978 * @param array $recur, pattern for recurrency (only value part, params ignored) 
     7979 * @param array $wdate, component start date 
     7980 * @param array $startdate, start date 
     7981 * @param array $enddate, optional 
     7982 * @return void 
     7983 * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start 
     7984 */ 
     7985  public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) { 
     7986    foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v; 
     7987    $wdateStart  = $wdate; 
     7988    $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate ); 
     7989    $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate ); 
     7990    if( !$enddate ) { 
     7991      $enddate = $startdate; 
     7992      $enddate['year'] += 1; 
     7993    } 
     7994// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test### 
     7995    $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break 
     7996    if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] )) 
     7997      $recur['UNTIL'] = $enddate; // create break 
     7998    if( isset( $recur['UNTIL'] )) { 
     7999      $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] ); 
     8000      if( $endDatets > $tdatets ) { 
     8001        $endDatets = $tdatets; // emergency break 
     8002        $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); 
     8003      } 
     8004      else 
     8005        $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); 
     8006    } 
     8007    if( $wdatets > $endDatets ) { 
     8008// echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test 
     8009      return array(); // nothing to do.. . 
     8010    } 
     8011    if( !isset( $recur['FREQ'] )) // "MUST be specified.. ." 
     8012      $recur['FREQ'] = 'DAILY'; // ?? 
     8013    $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ?? 
     8014    $weekStart = (int) date( 'W', ( $wdatets + $wkst )); 
     8015    if( !isset( $recur['INTERVAL'] )) 
     8016      $recur['INTERVAL'] = 1; 
     8017    $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence 
     8018            /* find out how to step up dates and set index for interval count */ 
     8019    $step = array(); 
     8020    if( 'YEARLY' == $recur['FREQ'] ) 
     8021      $step['year']  = 1; 
     8022    elseif( 'MONTHLY' == $recur['FREQ'] ) 
     8023      $step['month'] = 1; 
     8024    elseif( 'WEEKLY' == $recur['FREQ'] ) 
     8025      $step['day']   = 7; 
     8026    else 
     8027      $step['day']   = 1; 
     8028    if( isset( $step['year'] ) && isset( $recur['BYMONTH'] )) 
     8029      $step = array( 'month' => 1 ); 
     8030    if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ?? 
     8031      $step = array( 'day' => 7 ); 
     8032    if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] )) 
     8033      $step = array( 'day' => 1 ); 
     8034    $intervalarr = array(); 
     8035    if( 1 < $recur['INTERVAL'] ) { 
     8036      $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); 
     8037      $intervalarr = array( $intervalix => 0 ); 
     8038    } 
     8039    if( isset( $recur['BYSETPOS'] )) { // save start date + weekno 
     8040      $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array(); 
     8041// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ### 
     8042      if( is_array( $recur['BYSETPOS'] )) { 
     8043        foreach( $recur['BYSETPOS'] as $bix => $bval ) 
     8044          $recur['BYSETPOS'][$bix] = (int) $bval; 
     8045      } 
     8046      else 
     8047        $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] ); 
     8048      if( 'YEARLY' == $recur['FREQ'] ) { 
     8049        $wdate['month'] = $wdate['day'] = 1; // start from beginning of year 
     8050        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate ); 
     8051        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year 
     8052      } 
     8053      elseif( 'MONTHLY' == $recur['FREQ'] ) { 
     8054        $wdate['day']   = 1; // start from beginning of month 
     8055        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate ); 
     8056        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month 
     8057      } 
     8058      else 
     8059        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period 
     8060// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test### 
     8061      $bysetposWold = (int) date( 'W', ( $wdatets + $wkst )); 
     8062      $bysetposYold = $wdate['year']; 
     8063      $bysetposMold = $wdate['month']; 
     8064      $bysetposDold = $wdate['day']; 
     8065    } 
     8066    else 
     8067      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); 
     8068    $year_old     = null; 
     8069    $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); 
     8070             /* MAIN LOOP */ 
     8071// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test 
     8072    while( TRUE ) { 
     8073      if( isset( $endDatets ) && ( $wdatets > $endDatets )) 
     8074        break; 
     8075      if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) 
     8076        break; 
     8077      if( $year_old != $wdate['year'] ) { 
     8078        $year_old   = $wdate['year']; 
     8079        $daycnts    = array(); 
     8080        $yeardays   = $weekno = 0; 
     8081        $yeardaycnt = array(); 
     8082        foreach( $daynames as $dn ) 
     8083          $yeardaycnt[$dn] = 0; 
     8084        for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters 
     8085          $daycnts[$m] = array(); 
     8086          $weekdaycnt = array(); 
     8087          foreach( $daynames as $dn ) 
     8088            $weekdaycnt[$dn] = 0; 
     8089          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); 
     8090          for( $d   = 1; $d <= $mcnt; $d++ ) { 
     8091            $daycnts[$m][$d] = array(); 
     8092            if( isset( $recur['BYYEARDAY'] )) { 
     8093              $yeardays++; 
     8094              $daycnts[$m][$d]['yearcnt_up'] = $yeardays; 
     8095            } 
     8096            if( isset( $recur['BYDAY'] )) { 
     8097              $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] )); 
     8098              $day    = $daynames[$day]; 
     8099              $daycnts[$m][$d]['DAY'] = $day; 
     8100              $weekdaycnt[$day]++; 
     8101              $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day]; 
     8102              $yeardaycnt[$day]++; 
     8103              $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day]; 
     8104            } 
     8105            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) 
     8106              $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year'])); 
     8107          } 
     8108        } 
     8109        $daycnt = 0; 
     8110        $yeardaycnt = array(); 
     8111        if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) { 
     8112          $weekno = null; 
     8113          for( $d=31; $d > 25; $d-- ) { // get last weekno for year 
     8114            if( !$weekno ) 
     8115              $weekno = $daycnts[12][$d]['weekno_up']; 
     8116            elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) { 
     8117              $weekno = $daycnts[12][$d]['weekno_up']; 
     8118              break; 
     8119            } 
     8120          } 
     8121        } 
     8122        for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters 
     8123          $weekdaycnt = array(); 
     8124          foreach( $daynames as $dn ) 
     8125            $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; 
     8126          $monthcnt = 0; 
     8127          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); 
     8128          for( $d   = $mcnt; $d > 0; $d-- ) { 
     8129            if( isset( $recur['BYYEARDAY'] )) { 
     8130              $daycnt -= 1; 
     8131              $daycnts[$m][$d]['yearcnt_down'] = $daycnt; 
     8132            } 
     8133            if( isset( $recur['BYMONTHDAY'] )) { 
     8134              $monthcnt -= 1; 
     8135              $daycnts[$m][$d]['monthcnt_down'] = $monthcnt; 
     8136            } 
     8137            if( isset( $recur['BYDAY'] )) { 
     8138              $day  = $daycnts[$m][$d]['DAY']; 
     8139              $weekdaycnt[$day] -= 1; 
     8140              $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day]; 
     8141              $yeardaycnt[$day] -= 1; 
     8142              $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day]; 
     8143            } 
     8144            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) 
     8145              $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1); 
     8146          } 
     8147        } 
     8148      } 
     8149            /* check interval */ 
     8150      if( 1 < $recur['INTERVAL'] ) { 
     8151            /* create interval index */ 
     8152        $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); 
     8153            /* check interval */ 
     8154        $currentKey = array_keys( $intervalarr ); 
     8155        $currentKey = end( $currentKey ); // get last index 
     8156        if( $currentKey != $intervalix ) 
     8157          $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 )); 
     8158        if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) && 
     8159           ( 0 != $intervalarr[$intervalix] )) { 
     8160            /* step up date */ 
     8161// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test 
     8162          iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); 
     8163          continue; 
     8164        } 
     8165        else // continue within the selected interval 
     8166          $intervalarr[$intervalix] = 0; 
     8167// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test 
     8168      } 
     8169      $updateOK = TRUE; 
     8170      if( $updateOK && isset( $recur['BYMONTH'] )) 
     8171        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH'] 
     8172                                           , $wdate['month'] 
     8173                                           ,($wdate['month'] - 13)); 
     8174      if( $updateOK && isset( $recur['BYWEEKNO'] )) 
     8175        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO'] 
     8176                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] 
     8177                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] ); 
     8178      if( $updateOK && isset( $recur['BYYEARDAY'] )) 
     8179        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY'] 
     8180                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up'] 
     8181                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] ); 
     8182      if( $updateOK && isset( $recur['BYMONTHDAY'] )) 
     8183        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY'] 
     8184                                           , $wdate['day'] 
     8185                                           , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] ); 
     8186// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test### 
     8187      if( $updateOK && isset( $recur['BYDAY'] )) { 
     8188        $updateOK = FALSE; 
     8189        $m = $wdate['month']; 
     8190        $d = $wdate['day']; 
     8191        if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no 
     8192          $daynoexists = $daynosw = $daynamesw =  FALSE; 
     8193          if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] ) 
     8194            $daynamesw = TRUE; 
     8195          if( isset( $recur['BYDAY'][0] )) { 
     8196            $daynoexists = TRUE; 
     8197            if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] )) 
     8198              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] 
     8199                                                , $daycnts[$m][$d]['monthdayno_up'] 
     8200                                                , $daycnts[$m][$d]['monthdayno_down'] ); 
     8201            elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) 
     8202              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] 
     8203                                                , $daycnts[$m][$d]['yeardayno_up'] 
     8204                                                , $daycnts[$m][$d]['yeardayno_down'] ); 
     8205          } 
     8206          if((  $daynoexists &&  $daynosw && $daynamesw ) || 
     8207             ( !$daynoexists && !$daynosw && $daynamesw )) { 
     8208            $updateOK = TRUE; 
     8209// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ### 
     8210          } 
     8211//echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ### 
     8212        } 
     8213        else { 
     8214          foreach( $recur['BYDAY'] as $bydayvalue ) { 
     8215            $daynoexists = $daynosw = $daynamesw = FALSE; 
     8216            if( isset( $bydayvalue['DAY'] ) && 
     8217                     ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] )) 
     8218              $daynamesw = TRUE; 
     8219            if( isset( $bydayvalue[0] )) { 
     8220              $daynoexists = TRUE; 
     8221              if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || 
     8222                   isset( $recur['BYMONTH'] )) 
     8223                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] 
     8224                                                  , $daycnts[$m][$d]['monthdayno_up'] 
     8225                                                  , $daycnts[$m][$d]['monthdayno_down'] ); 
     8226              elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) 
     8227                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] 
     8228                                                  , $daycnts[$m][$d]['yeardayno_up'] 
     8229                                                  , $daycnts[$m][$d]['yeardayno_down'] ); 
     8230            } 
     8231// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ### 
     8232            if((  $daynoexists &&  $daynosw && $daynamesw ) || 
     8233               ( !$daynoexists && !$daynosw && $daynamesw )) { 
     8234              $updateOK = TRUE; 
     8235              break; 
     8236            } 
     8237          } 
     8238        } 
     8239      } 
     8240// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ### 
     8241            /* check BYSETPOS */ 
     8242      if( $updateOK ) { 
     8243        if( isset( $recur['BYSETPOS'] ) && 
     8244          ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) { 
     8245          if( isset( $recur['WEEKLY'] )) { 
     8246            if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] ) 
     8247              $bysetposw1[] = $wdatets; 
     8248            else 
     8249              $bysetposw2[] = $wdatets; 
     8250          } 
     8251          else { 
     8252            if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  && 
     8253                                            ( $bysetposYold == $wdate['year'] ))   || 
     8254               ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  && 
     8255                                           (( $bysetposYold == $wdate['year'] )  && 
     8256                                            ( $bysetposMold == $wdate['month'] ))) || 
     8257               ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  && 
     8258                                           (( $bysetposYold == $wdate['year'] )  && 
     8259                                            ( $bysetposMold == $wdate['month'])  && 
     8260                                            ( $bysetposDold == $wdate['day'] )))) { 
     8261// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test 
     8262              $bysetposymd1[] = $wdatets; 
     8263            } 
     8264            else { 
     8265// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test 
     8266              $bysetposymd2[] = $wdatets; 
     8267            } 
     8268          } 
     8269        } 
     8270        else { 
     8271            /* update result array if BYSETPOS is set */ 
     8272          $countcnt++; 
     8273          if( $startdatets <= $wdatets ) { // only output within period 
     8274            $result[$wdatets] = TRUE; 
     8275// echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test 
     8276          } 
     8277// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test 
     8278          $updateOK = FALSE; 
     8279        } 
     8280      } 
     8281            /* step up date */ 
     8282      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); 
     8283            /* check if BYSETPOS is set for updating result array */ 
     8284      if( $updateOK && isset( $recur['BYSETPOS'] )) { 
     8285        $bysetpos       = FALSE; 
     8286        if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) && 
     8287          ( $bysetposYold != $wdate['year'] )) { 
     8288          $bysetpos     = TRUE; 
     8289          $bysetposYold = $wdate['year']; 
     8290        } 
     8291        elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] && 
     8292         (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) { 
     8293          $bysetpos     = TRUE; 
     8294          $bysetposYold = $wdate['year']; 
     8295          $bysetposMold = $wdate['month']; 
     8296        } 
     8297        elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) { 
     8298          $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year'])); 
     8299          if( $bysetposWold != $weekno ) { 
     8300            $bysetposWold = $weekno; 
     8301            $bysetpos     = TRUE; 
     8302          } 
     8303        } 
     8304        elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) && 
     8305         (( $bysetposYold != $wdate['year'] )  || 
     8306          ( $bysetposMold != $wdate['month'] ) || 
     8307          ( $bysetposDold != $wdate['day'] ))) { 
     8308          $bysetpos     = TRUE; 
     8309          $bysetposYold = $wdate['year']; 
     8310          $bysetposMold = $wdate['month']; 
     8311          $bysetposDold = $wdate['day']; 
     8312        } 
     8313        if( $bysetpos ) { 
     8314          if( isset( $recur['BYWEEKNO'] )) { 
     8315            $bysetposarr1 = & $bysetposw1; 
     8316            $bysetposarr2 = & $bysetposw2; 
     8317          } 
     8318          else { 
     8319            $bysetposarr1 = & $bysetposymd1; 
     8320            $bysetposarr2 = & $bysetposymd2; 
     8321          } 
     8322// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ### 
     8323          foreach( $recur['BYSETPOS'] as $ix ) { 
     8324            if( 0 > $ix ) // both positive and negative BYSETPOS allowed 
     8325              $ix = ( count( $bysetposarr1 ) + $ix + 1); 
     8326            $ix--; 
     8327            if( isset( $bysetposarr1[$ix] )) { 
     8328              if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period 
     8329//                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ### 
     8330//                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ### 
     8331// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ### 
     8332                $result[$bysetposarr1[$ix]] = TRUE; 
     8333// echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ### 
     8334              } 
     8335              $countcnt++; 
     8336            } 
     8337            if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) 
     8338              break; 
     8339          } 
     8340// echo "<br />\n"; // test ### 
     8341          $bysetposarr1 = $bysetposarr2; 
     8342          $bysetposarr2 = array(); 
     8343        } 
     8344      } 
     8345    } 
     8346  } 
     8347  public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) { 
     8348    if( is_array( $BYvalue ) && 
     8349      ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue ))) 
     8350      return TRUE; 
     8351    elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue )) 
     8352      return TRUE; 
     8353    else 
     8354      return FALSE; 
     8355  } 
     8356  public static function _recurIntervalIx( $freq, $date, $wkst ) { 
     8357            /* create interval index */ 
     8358    switch( $freq ) { 
     8359      case 'YEARLY': 
     8360        $intervalix = $date['year']; 
     8361        break; 
     8362      case 'MONTHLY': 
     8363        $intervalix = $date['year'].'-'.$date['month']; 
     8364        break; 
     8365      case 'WEEKLY': 
     8366        $wdatets    = iCalUtilityFunctions::_date2timestamp( $date ); 
     8367        $intervalix = (int) date( 'W', ( $wdatets + $wkst )); 
     8368       break; 
     8369      case 'DAILY': 
     8370           default: 
     8371        $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day']; 
     8372        break; 
     8373    } 
     8374    return $intervalix; 
     8375  } 
     8376/** 
     8377 * convert input format for exrule and rrule to internal format 
     8378 * 
     8379 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8380 * @since 2.14.1 - 2012-09-24 
     8381 * @param array $rexrule 
     8382 * @return array 
     8383 */ 
     8384  public static function _setRexrule( $rexrule ) { 
     8385    $input          = array(); 
     8386    if( empty( $rexrule )) 
     8387      return $input; 
     8388    foreach( $rexrule as $rexrulelabel => $rexrulevalue ) { 
     8389      $rexrulelabel = strtoupper( $rexrulelabel ); 
     8390      if( 'UNTIL'  != $rexrulelabel ) 
     8391        $input[$rexrulelabel]   = $rexrulevalue; 
     8392      else { 
     8393        iCalUtilityFunctions::_strDate2arr( $rexrulevalue ); 
     8394        if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC 
     8395          $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' ); 
     8396        elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time 
     8397          $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3; 
     8398          $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno ); 
     8399          if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) { 
     8400            $strdate              = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     8401            $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8402            unset( $input[$rexrulelabel]['unparsedtext'] ); 
     8403          } 
     8404          else 
     8405           $input[$rexrulelabel] = $d; 
     8406        } 
     8407        elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC 
     8408          $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue ); 
     8409          unset( $input['$rexrulelabel']['unparsedtext'] ); 
     8410        } 
     8411        if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] )) 
     8412          $input[$rexrulelabel]['tz'] = 'Z'; 
     8413      } 
     8414    } 
     8415            /* set recurrence rule specification in rfc2445 order */ 
     8416    $input2 = array(); 
     8417    if( isset( $input['FREQ'] )) 
     8418      $input2['FREQ']       = $input['FREQ']; 
     8419    if( isset( $input['UNTIL'] )) 
     8420      $input2['UNTIL']      = $input['UNTIL']; 
     8421    elseif( isset( $input['COUNT'] )) 
     8422      $input2['COUNT']      = $input['COUNT']; 
     8423    if( isset( $input['INTERVAL'] )) 
     8424      $input2['INTERVAL']   = $input['INTERVAL']; 
     8425    if( isset( $input['BYSECOND'] )) 
     8426      $input2['BYSECOND']   = $input['BYSECOND']; 
     8427    if( isset( $input['BYMINUTE'] )) 
     8428      $input2['BYMINUTE']   = $input['BYMINUTE']; 
     8429    if( isset( $input['BYHOUR'] )) 
     8430      $input2['BYHOUR']     = $input['BYHOUR']; 
     8431    if( isset( $input['BYDAY'] )) { 
     8432      if( !is_array( $input['BYDAY'] )) // ensure upper case.. . 
     8433        $input2['BYDAY']    = strtoupper( $input['BYDAY'] ); 
     8434      else { 
     8435        foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) { 
     8436          if( 'DAY'        == strtoupper( $BYDAYx )) 
     8437             $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv ); 
     8438          elseif( !is_array( $BYDAYv )) { 
     8439             $input2['BYDAY'][$BYDAYx]  = $BYDAYv; 
     8440          } 
     8441          else { 
     8442            foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) { 
     8443              if( 'DAY'    == strtoupper( $BYDAYx2 )) 
     8444                 $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 ); 
     8445              else 
     8446                 $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2; 
     8447            } 
     8448          } 
     8449        } 
     8450      } 
     8451    } 
     8452    if( isset( $input['BYMONTHDAY'] )) 
     8453      $input2['BYMONTHDAY'] = $input['BYMONTHDAY']; 
     8454    if( isset( $input['BYYEARDAY'] )) 
     8455      $input2['BYYEARDAY']  = $input['BYYEARDAY']; 
     8456    if( isset( $input['BYWEEKNO'] )) 
     8457      $input2['BYWEEKNO']   = $input['BYWEEKNO']; 
     8458    if( isset( $input['BYMONTH'] )) 
     8459      $input2['BYMONTH']    = $input['BYMONTH']; 
     8460    if( isset( $input['BYSETPOS'] )) 
     8461      $input2['BYSETPOS']   = $input['BYSETPOS']; 
     8462    if( isset( $input['WKST'] )) 
     8463      $input2['WKST']       = $input['WKST']; 
     8464    return $input2; 
     8465  } 
     8466/** 
     8467 * convert format for input date to internal date with parameters 
     8468 * 
     8469 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8470 * @since 2.14.1 - 2012-10-15 
     8471 * @param mixed  $year 
     8472 * @param mixed  $month   optional 
     8473 * @param int    $day     optional 
     8474 * @param int    $hour    optional 
     8475 * @param int    $min     optional 
     8476 * @param int    $sec     optional 
     8477 * @param string $tz      optional 
     8478 * @param array  $params  optional 
     8479 * @param string $caller  optional 
     8480 * @param string $objName optional 
     8481 * @param string $tzid    optional 
     8482 * @return array 
     8483 */ 
     8484  public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) { 
     8485    $input = $parno = null; 
     8486    $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE; 
     8487    iCalUtilityFunctions::_strDate2arr( $year ); 
     8488    if( iCalUtilityFunctions::_isArrayDate( $year )) { 
     8489      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, $parno ); 
     8490      if( 100 > $input['value']['year'] ) 
     8491        $input['value']['year'] += 2000; 
     8492      if( $localtime ) 
     8493        unset( $month['VALUE'], $month['TZID'] ); 
     8494      elseif( !isset( $month['TZID'] ) && isset( $tzid )) 
     8495        $month['TZID'] = $tzid; 
     8496      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) 
     8497        unset( $month['TZID'] ); 
     8498      elseif( isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) { 
     8499        $input['value']['tz'] = $month['TZID']; 
     8500        unset( $month['TZID'] ); 
     8501      } 
     8502      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8503      $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6; 
     8504      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval ); 
     8505      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno ); 
     8506      if(( 3 != $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) { 
     8507        $d             = $input['value']; 
     8508        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     8509        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno ); 
     8510        unset( $input['value']['unparsedtext'], $input['params']['TZID'] ); 
     8511      } 
     8512      if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) { 
     8513        $input['params']['TZID'] = $input['value']['tz']; 
     8514        unset( $input['value']['tz'] ); 
     8515      } 
     8516    } // end if( iCalUtilityFunctions::_isArrayDate( $year )) 
     8517    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { 
     8518      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); 
     8519      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8520      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); 
     8521      $hitval          = 7; 
     8522      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno ); 
     8523      if( !isset( $input['params']['TZID'] ) && !empty( $tzid )) 
     8524        $input['params']['TZID'] = $tzid; 
     8525      if( isset( $year['tz'] )) { 
     8526        $parno         = 6; 
     8527        if( !iCalUtilityFunctions::_isOffset( $year['tz'] )) 
     8528          $input['params']['TZID'] = $year['tz']; 
     8529      } 
     8530      elseif( isset( $input['params']['TZID'] )) { 
     8531        $year['tz']    = $input['params']['TZID']; 
     8532        $parno         = 6; 
     8533        if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { 
     8534          unset( $input['params']['TZID'] ); 
     8535          $parno       = 7; 
     8536        } 
     8537      } 
     8538      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno ); 
     8539    } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) 
     8540    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone] 
     8541      if( $localtime ) 
     8542        unset( $month['VALUE'], $month['TZID'] ); 
     8543      elseif( !isset( $month['TZID'] ) && !empty( $tzid )) 
     8544        $month['TZID'] = $tzid; 
     8545      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8546      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno ); 
     8547      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno ); 
     8548      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno ); 
     8549      unset( $input['value']['unparsedtext'] ); 
     8550      if( isset( $input['value']['tz'] )) { 
     8551        if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) { 
     8552          $d           = $input['value']; 
     8553          $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     8554          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8555          unset( $input['value']['unparsedtext'], $input['params']['TZID'] ); 
     8556        } 
     8557        else { 
     8558          $input['params']['TZID'] = $input['value']['tz']; 
     8559          unset( $input['value']['tz'] ); 
     8560        } 
     8561      } 
     8562      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { 
     8563        $d             = $input['value']; 
     8564        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] ); 
     8565        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8566        unset( $input['value']['unparsedtext'], $input['params']['TZID'] ); 
     8567      } 
     8568    } // end elseif( 8 <= strlen( trim( $year ))) 
     8569    else { 
     8570      if( is_array( $params )) 
     8571        $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); 
     8572      elseif( is_array( $tz )) { 
     8573        $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' )); 
     8574        $tz = FALSE; 
     8575      } 
     8576      elseif( is_array( $hour )) { 
     8577        $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' )); 
     8578        $hour = $min = $sec = $tz = FALSE; 
     8579      } 
     8580      if( $localtime ) 
     8581        unset ( $input['params']['VALUE'], $input['params']['TZID'] ); 
     8582      elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid )) 
     8583        $input['params']['TZID'] = $tzid; 
     8584      elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) 
     8585        unset( $input['params']['TZID'] ); 
     8586      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { 
     8587        $tz            = $input['params']['TZID']; 
     8588        unset( $input['params']['TZID'] ); 
     8589      } 
     8590      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); 
     8591      $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6; 
     8592      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno ); 
     8593      $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day ); 
     8594      if( 3 != $parno ) { 
     8595        $input['value']['hour'] = ( $hour ) ? $hour : '0'; 
     8596        $input['value']['min']  = ( $min )  ? $min  : '0'; 
     8597        $input['value']['sec']  = ( $sec )  ? $sec  : '0'; 
     8598        if( !empty( $tz )) 
     8599          $input['value']['tz'] = $tz; 
     8600        $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno ); 
     8601        if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz )) 
     8602          $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz; 
     8603        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno ); 
     8604        unset( $input['value']['unparsedtext'] ); 
     8605        if( isset( $input['value']['tz'] )) { 
     8606          if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) { 
     8607            $d           = $input['value']; 
     8608            $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     8609            $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8610            unset( $input['value']['unparsedtext'], $input['params']['TZID'] ); 
     8611          } 
     8612          else { 
     8613            $input['params']['TZID'] = $input['value']['tz']; 
     8614            unset( $input['value']['tz'] ); 
     8615          } 
     8616        } 
     8617        elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { 
     8618          $d             = $input['value']; 
     8619          $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] ); 
     8620          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8621          unset( $input['value']['unparsedtext'], $input['params']['TZID'] ); 
     8622        } 
     8623      } 
     8624    } // end else (i.e. using all arguments) 
     8625    if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) { 
     8626      $input['params']['VALUE'] = 'DATE'; 
     8627      unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] ); 
     8628    } 
     8629    elseif( isset( $input['params']['TZID'] )) { 
     8630      if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) { 
     8631        $input['value']['tz'] = 'Z'; 
     8632        unset( $input['params']['TZID'] ); 
     8633      } 
     8634      else 
     8635        unset( $input['value']['tz'] ); 
     8636    } 
     8637    elseif( isset( $input['value']['tz'] )) { 
     8638      if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] ))) 
     8639        $input['value']['tz'] = 'Z'; 
     8640      if( 'Z' != $input['value']['tz'] ) { 
     8641        $input['params']['TZID'] = $input['value']['tz']; 
     8642        unset( $input['value']['tz'] ); 
     8643      } 
     8644      else 
     8645        unset( $input['params']['TZID'] ); 
     8646    } 
     8647    if( $localtime ) 
     8648      unset( $input['value']['tz'], $input['params']['TZID'] ); 
     8649    return $input; 
     8650  } 
     8651/** 
     8652 * convert format for input date (UTC) to internal date with parameters 
     8653 * 
     8654 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8655 * @since 2.14.4 - 2012-10-06 
     8656 * @param mixed $year 
     8657 * @param mixed $month  optional 
     8658 * @param int   $day    optional 
     8659 * @param int   $hour   optional 
     8660 * @param int   $min    optional 
     8661 * @param int   $sec    optional 
     8662 * @param array $params optional 
     8663 * @return array 
     8664 */ 
     8665  public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { 
     8666    $input = null; 
     8667    iCalUtilityFunctions::_strDate2arr( $year ); 
     8668    if( iCalUtilityFunctions::_isArrayDate( $year )) { 
     8669      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 ); 
     8670      if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] )) 
     8671        $input['value']['year'] += 2000; 
     8672      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8673      if( isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) { 
     8674        $d             = $input['value']; 
     8675        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] ); 
     8676        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8677        unset( $input['value']['unparsedtext'] ); 
     8678      } 
     8679    } 
     8680    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { 
     8681      $year['tz']      = 'UTC'; 
     8682      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 ); 
     8683      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8684    } 
     8685    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 
     8686      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 ); 
     8687      unset( $input['value']['unparsedtext'] ); 
     8688      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); 
     8689    } 
     8690    else { 
     8691      $input['value']  = array( 'year'  => $year 
     8692                              , 'month' => $month 
     8693                              , 'day'   => $day 
     8694                              , 'hour'  => $hour 
     8695                              , 'min'   => $min 
     8696                              , 'sec'   => $sec ); 
     8697      if(  isset( $tz )) $input['value']['tz'] = $tz; 
     8698      if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) || 
     8699         ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) { 
     8700          if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) 
     8701            $input['value']['tz'] = $input['params']['TZID']; 
     8702          unset( $input['params']['TZID'] ); 
     8703        $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 ); 
     8704        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 ); 
     8705        unset( $input['value']['unparsedtext'] ); 
     8706      } 
     8707      $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); 
     8708    } 
     8709    $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default 
     8710    if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0; 
     8711    if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0; 
     8712    if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0; 
     8713    $input['value']['tz'] = 'Z'; 
     8714    return $input; 
     8715  } 
     8716/** 
     8717 * check index and set (an indexed) content in multiple value array 
     8718 * 
     8719 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8720 * @since 2.6.12 - 2011-01-03 
     8721 * @param array $valArr 
     8722 * @param mixed $value 
     8723 * @param array $params 
     8724 * @param array $defaults 
     8725 * @param int $index 
     8726 * @return void 
     8727 */ 
     8728  public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) { 
     8729    if( !is_array( $valArr )) $valArr = array(); 
     8730    if( $index ) 
     8731      $index = $index - 1; 
     8732    elseif( 0 < count( $valArr )) { 
     8733      $keys  = array_keys( $valArr ); 
     8734      $index = end( $keys ) + 1; 
     8735    } 
     8736    else 
     8737      $index = 0; 
     8738    $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults )); 
     8739    ksort( $valArr ); 
     8740  } 
     8741/** 
     8742 * set input (formatted) parameters- component property attributes 
     8743 * 
     8744 * default parameters can be set, if missing 
     8745 * 
     8746 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8747 * @since 1.x.x - 2007-05-01 
     8748 * @param array $params 
     8749 * @param array $defaults 
     8750 * @return array 
     8751 */ 
     8752  public static function _setParams( $params, $defaults=FALSE ) { 
     8753    if( !is_array( $params)) 
     8754      $params = array(); 
     8755    $input = array(); 
     8756    foreach( $params as $paramKey => $paramValue ) { 
     8757      if( is_array( $paramValue )) { 
     8758        foreach( $paramValue as $pkey => $pValue ) { 
     8759          if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 ))) 
     8760            $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 )); 
     8761        } 
     8762      } 
     8763      elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 ))) 
     8764        $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 )); 
     8765      if( 'VALUE' == strtoupper( $paramKey )) 
     8766        $input['VALUE']                 = strtoupper( $paramValue ); 
     8767      else 
     8768        $input[strtoupper( $paramKey )] = $paramValue; 
     8769    } 
     8770    if( is_array( $defaults )) { 
     8771      foreach( $defaults as $paramKey => $paramValue ) { 
     8772        if( !isset( $input[$paramKey] )) 
     8773          $input[$paramKey] = $paramValue; 
     8774      } 
     8775    } 
     8776    return (0 < count( $input )) ? $input : null; 
     8777  } 
     8778/** 
     8779 * step date, return updated date, array and timpstamp 
     8780 * 
     8781 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8782 * @since 2.14.1 - 2012-09-24 
     8783 * @param array $date, date to step 
     8784 * @param int   $timestamp 
     8785 * @param array $step, default array( 'day' => 1 ) 
     8786 * @return void 
     8787 */ 
     8788  public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) { 
     8789    if( !isset( $date['hour'] )) $date['hour'] = 0; 
     8790    if( !isset( $date['min'] ))  $date['min']  = 0; 
     8791    if( !isset( $date['sec'] ))  $date['sec']  = 0; 
     8792    foreach( $step as $stepix => $stepvalue ) 
     8793      $date[$stepix] += $stepvalue; 
     8794    $timestamp  = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] ); 
     8795    $d          = date( 'Y-m-d-H-i-s', $timestamp); 
     8796    $d          = explode( '-', $d ); 
     8797    $date       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] ); 
     8798    foreach( $date as $k => $v ) 
     8799      $date[$k] = (int) $v; 
     8800  } 
     8801/** 
     8802 * convert a date from specific string to array format 
     8803 * 
     8804 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8805 * @since 2.11.8 - 2012-01-27 
     8806 * @param mixed $input 
     8807 * @return bool, TRUE on success 
     8808 */ 
     8809  public static function _strDate2arr( & $input ) { 
     8810    if( is_array( $input )) 
     8811      return FALSE; 
     8812    if( 5 > strlen( (string) $input )) 
     8813      return FALSE; 
     8814    $work = $input; 
     8815    if( 2 == substr_count( $work, '-' )) 
     8816      $work = str_replace( '-', '', $work ); 
     8817    if( 2 == substr_count( $work, '/' )) 
     8818      $work = str_replace( '/', '', $work ); 
     8819    if( !ctype_digit( substr( $work, 0, 8 ))) 
     8820      return FALSE; 
     8821    $temp = array( 'year'  => (int) substr( $work,  0, 4 ) 
     8822                 , 'month' => (int) substr( $work,  4, 2 ) 
     8823                 , 'day'   => (int) substr( $work,  6, 2 )); 
     8824    if( !checkdate( $temp['month'], $temp['day'], $temp['year'] )) 
     8825      return FALSE; 
     8826    if( 8 == strlen( $work )) { 
     8827      $input = $temp; 
     8828      return TRUE; 
     8829    } 
     8830    if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 ))) 
     8831      $work =  substr( $work, 9 ); 
     8832    elseif( ctype_digit( substr( $work, 8, 1 ))) 
     8833      $work = substr( $work, 8 ); 
     8834    else 
     8835     return FALSE; 
     8836    if( 2 == substr_count( $work, ':' )) 
     8837      $work = str_replace( ':', '', $work ); 
     8838    if( !ctype_digit( substr( $work, 0, 4 ))) 
     8839      return FALSE; 
     8840    $temp['hour']  = substr( $work, 0, 2 ); 
     8841    $temp['min']   = substr( $work, 2, 2 ); 
     8842    if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) || 
     8843       (( 0 > $temp['min'] )  || ( $temp['min']  > 59 ))) 
     8844      return FALSE; 
     8845    if( ctype_digit( substr( $work, 4, 2 ))) { 
     8846      $temp['sec'] = substr( $work, 4, 2 ); 
     8847      if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 )) 
     8848        return FALSE; 
     8849      $len = 6; 
     8850    } 
     8851    else { 
     8852      $temp['sec'] = 0; 
     8853      $len = 4; 
     8854    } 
     8855    if( $len < strlen( $work)) 
     8856      $temp['tz'] = trim( substr( $work, 6 )); 
     8857    $input = $temp; 
     8858    return TRUE; 
     8859  } 
     8860/** 
     8861 * ensures internal date-time/date format for input date-time/date in string fromat 
     8862 * 
     8863 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8864 * @since 2.14.1 - 2012-10-07 
     8865 * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com> 
     8866 * @param array $datetime 
     8867 * @param int   $parno optional, default FALSE 
     8868 * @param moxed $wtz optional, default null 
     8869 * @return array 
     8870 */ 
     8871  public static function _date_time_string( $datetime, $parno=FALSE ) { 
     8872    return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null ); 
     8873  } 
     8874  public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) { 
     8875    // save original input string to return it later 
     8876    $unparseddatetime = $datetime; 
     8877    $datetime   = (string) trim( $datetime ); 
     8878    $tz         = null; 
     8879    $offset     = 0; 
     8880    $tzSts      = FALSE; 
     8881    $len        = strlen( $datetime ); 
     8882    if( 'Z' == substr( $datetime, -1 )) { 
     8883      $tz       = 'Z'; 
     8884      $datetime = trim( substr( $datetime, 0, ( $len - 1 ))); 
     8885      $tzSts    = TRUE; 
     8886      $len      = 88; 
     8887    } 
     8888    if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset 
     8889      $tz       = substr( $datetime, -5, 5 ); 
     8890      $datetime = trim( substr( $datetime, 0, ($len - 5))); 
     8891      $len      = strlen( $datetime ); 
     8892    } 
     8893    elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset 
     8894      $tz       = substr( $datetime, -7, 7 ); 
     8895      $datetime = trim( substr( $datetime, 0, ($len - 7))); 
     8896      $len      = strlen( $datetime ); 
     8897    } 
     8898    elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) { 
     8899      $output = $datetime; 
     8900      if( !empty( $tz )) 
     8901        $output['tz'] = 'Z'; 
     8902      $output['unparsedtext'] = $unparseddatetime; 
     8903      return $output; 
     8904    } 
     8905    else { 
     8906      $cx  = $tx = 0;    //  find any trailing timezone or offset 
     8907      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) { 
     8908        $char = substr( $datetime, $cx, 1 ); 
     8909        if(( ' ' == $char) || ctype_digit( $char )) 
     8910          break; // if exists, tz ends here.. . ? 
     8911        else 
     8912           $tx--; // tz length counter 
     8913      } 
     8914      if( 0 > $tx ) { // if any 
     8915        $tz     = substr( $datetime, $tx ); 
     8916        $datetime = trim( substr( $datetime, 0, $len + $tx )); 
     8917        $len    = strlen( $datetime ); 
     8918      } 
     8919      if(( 17 <= $len ) ||  // long textual datetime 
     8920         ( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) || 
     8921         ( ctype_digit( substr( $datetime, 0, 14 )))) { 
     8922        $len    = 88; 
     8923        $tzSts  = TRUE; 
     8924      } 
     8925      else 
     8926        $tz     = null; // no tz for Y-m-d dates 
     8927    } 
     8928    if( empty( $tz ) && !empty( $wtz )) 
     8929      $tz       = $wtz; 
     8930    if( 17 >= $len ) // any Y-m-d textual date 
     8931      $tz       = null; 
     8932    if( !empty( $tz ) && ( 17 < $len )) { // tz set AND long textual datetime 
     8933      if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) { 
     8934        $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1; 
     8935        $tz     = 'UTC'; 
     8936        $tzSts  = TRUE; 
     8937      } 
     8938      elseif( !empty( $wtz )) 
     8939        $tzSts  = TRUE; 
     8940      $tz       = trim( $tz ); 
     8941      if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz ))) 
     8942        $tz     = 'UTC'; 
     8943      if( 0 < substr_count( $datetime, '-' )) 
     8944        $datetime = str_replace( '-', '/', $datetime ); 
     8945      try { 
     8946        $d        = new DateTime( $datetime, new DateTimeZone( $tz )); 
     8947        if( 0  != $offset )  // adjust for offset 
     8948          $d->modify( $offset.' seconds' ); 
     8949        $datestring = $d->format( 'Y-m-d-H-i-s' ); 
     8950        unset( $d ); 
     8951      } 
     8952      catch( Exception $e ) { 
     8953        $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime )); 
     8954      } 
     8955    } // end if( !empty( $tz ) && ( 17 < $len )) 
     8956    else 
     8957      $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime )); 
     8958// echo "<tr><td>&nbsp;<td colspan='3'>_strdate2date input=$datetime, tz=$tz, offset=$offset, wtz=$wtz, len=$len, prepDate=$datestring\n"; 
     8959    if( 'UTC' == $tz ) 
     8960      $tz         = 'Z'; 
     8961    $d            = explode( '-', $datestring ); 
     8962    $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] ); 
     8963    if((( FALSE !== $parno ) && ( 3 != $parno )) || // parno is set to 6 or 7 
     8964       (( FALSE === $parno ) && ( 'Z' == $tz ))  || // parno is not set and UTC 
     8965       (( FALSE === $parno ) && ( 'Z' != $tz ) && ( 0 != $d[3] + $d[4] + $d[5] ) && ( 17 < $len ))) { // !parno and !UTC and 0 != hour+min+sec and long input text 
     8966      $output['hour'] = $d[3]; 
     8967      $output['min']  = $d[4]; 
     8968      $output['sec']  = $d[5]; 
     8969      if(( $tzSts || ( 7 == $parno )) && !empty( $tz )) 
     8970        $output['tz'] = $tz; 
     8971    } 
     8972    // return original string in the array in case strtotime failed to make sense of it 
     8973    $output['unparsedtext'] = $unparseddatetime; 
     8974    return $output; 
     8975  } 
     8976/** 
     8977 * convert timestamp to date array, default UTC or adjusted for offset/timezone 
     8978 * 
     8979 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     8980 * @since 2.15.1 - 2012-10-17 
     8981 * @param mixed   $timestamp 
     8982 * @param int     $parno 
     8983 * @param string  $wtz 
     8984 * @return array 
     8985 */ 
     8986  public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) { 
     8987    if( is_array( $timestamp )) { 
     8988      $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz; 
     8989      $timestamp = $timestamp['timestamp']; 
     8990    } 
     8991    $tz          = ( isset( $tz )) ? $tz : $wtz; 
     8992    if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz ))) 
     8993      $tz        = 'UTC'; 
     8994    elseif( iCalUtilityFunctions::_isOffset( $tz )) { 
     8995      $offset    = iCalUtilityFunctions::_tz2offset( $tz ); 
     8996      $tz        = 'UTC'; 
     8997    } 
     8998    try { 
     8999      $d         = new DateTime( "@$timestamp" );  // set UTC date 
     9000      if( isset( $offset ) && ( 0 != $offset ))    // adjust for offset 
     9001        $d->modify( $offset.' seconds' ); 
     9002      elseif( 'UTC' != $tz ) 
     9003        $d->setTimezone( new DateTimeZone( $tz )); // convert to local date 
     9004      $date      = $d->format( 'Y-m-d-H-i-s' ); 
     9005      unset( $d ); 
     9006    } 
     9007    catch( Exception $e ) { 
     9008      $date      = date( 'Y-m-d-H-i-s', $timestamp ); 
     9009    } 
     9010    $date        = explode( '-', $date ); 
     9011    $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] ); 
     9012    if( 3 != $parno ) { 
     9013      $output['hour'] = $date[3]; 
     9014      $output['min']  = $date[4]; 
     9015      $output['sec']  = $date[5]; 
     9016      if( 'UTC' == $tz && ( !isset( $offset ) || ( 0 == $offset ))) 
     9017        $output['tz'] = 'Z'; 
     9018    } 
     9019    return $output; 
     9020  } 
     9021/** 
     9022 * convert timestamp (seconds) to duration in array format 
     9023 * 
     9024 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9025 * @since 2.6.23 - 2010-10-23 
     9026 * @param int $timestamp 
     9027 * @return array, duration format 
     9028 */ 
     9029  public static function _timestamp2duration( $timestamp ) { 
     9030    $dur         = array(); 
     9031    $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 )); 
     9032    $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 ); 
     9033    $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 )); 
     9034    $timestamp   =              $timestamp % ( 24 * 60 * 60 ); 
     9035    $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 )); 
     9036    $timestamp   =              $timestamp % ( 60 * 60 ); 
     9037    $dur['min']  = (int) floor( $timestamp / ( 60 )); 
     9038    $dur['sec']  = (int)        $timestamp % ( 60 ); 
     9039    return $dur; 
     9040  } 
     9041/** 
     9042 * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0) 
     9043 * 
     9044 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9045 * @since 2.15.1 - 2012-10-17 
     9046 * @param mixed  $date,   date to alter 
     9047 * @param string $tzFrom, PHP valid 'from' timezone 
     9048 * @param string $tzTo,   PHP valid 'to' timezone, default 'UTC' 
     9049 * @param string $format, date output format, default 'Ymd\THis' 
     9050 * @return bool 
     9051 */ 
     9052  public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) { 
     9053    if( is_array( $date ) && isset( $date['timestamp'] )) { 
     9054      try { 
     9055        $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date 
     9056        $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date 
     9057      } 
     9058      catch( Exception $e ) { return FALSE; } 
     9059    } 
     9060    else { 
     9061      if( iCalUtilityFunctions::_isArrayDate( $date )) { 
     9062        if( isset( $date['tz'] )) 
     9063          unset( $date['tz'] ); 
     9064        $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date )); 
     9065      } 
     9066      if( 'Z' == substr( $date, -1 )) 
     9067        $date = substr( $date, 0, ( strlen( $date ) - 2 )); 
     9068      try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); } 
     9069      catch( Exception $e ) { return FALSE; } 
     9070    } 
     9071    try { $d->setTimezone( new DateTimeZone( $tzTo )); } 
     9072    catch( Exception $e ) { return FALSE; } 
     9073    $date = $d->format( $format ); 
     9074    return TRUE; 
     9075  } 
     9076/** 
     9077 * convert offset, [+/-]HHmm[ss], to seconds used when correcting UTC to localtime or v.v. 
     9078 * 
     9079 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9080 * @since 2.11.4 - 2012-01-11 
     9081 * @param string $offset 
     9082 * @return integer 
     9083 */ 
     9084  public static function _tz2offset( $tz ) { 
     9085    $tz           = trim( (string) $tz ); 
     9086    $offset       = 0; 
     9087    if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) || 
     9088       ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) || 
     9089       (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) || 
     9090           (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 )))) 
     9091      return $offset; 
     9092    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600; 
     9093    $min2sec      = (int) substr( $tz, 3, 2 ) *   60; 
     9094    $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00'; 
     9095    $offset       = $hours2sec + $min2sec + $sec; 
     9096    $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset; 
     9097    return $offset; 
     9098  } 
     9099} 
     9100/*********************************************************************************/ 
     9101/*          iCalcreator vCard helper functions                                   */ 
     9102/*********************************************************************************/ 
     9103/** 
     9104 * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard 
     9105 * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE 
     9106 * 
     9107 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9108 * @since 2.12.2 - 2012-07-11 
     9109 * @param object $email 
     9110 * $param string $version, vCard version (default 2.1) 
     9111 * $param string $directory, where to save vCards (default FALSE) 
     9112 * $param string $ext, vCard file extension (default 'vcf') 
     9113 * @return mixed 
     9114 */ 
     9115function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) { 
     9116  if( FALSE === ( $pos = strpos( $email, '@' ))) 
     9117    return FALSE; 
     9118  if( $directory ) { 
     9119    if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR )))) 
     9120      $directory .= DIRECTORY_SEPARATOR; 
     9121    if( !is_dir( $directory ) || !is_writable( $directory )) 
     9122      return FALSE; 
     9123  } 
     9124            /* prepare vCard */ 
     9125  $email  = str_replace( 'MAILTO:', '', $email ); 
     9126  $name   = $person = substr( $email, 0, $pos ); 
     9127  if( ctype_upper( $name ) || ctype_lower( $name )) 
     9128    $name = array( $name ); 
     9129  else { 
     9130    if( FALSE !== ( $pos = strpos( $name, '.' ))) { 
     9131      $name = explode( '.', $name ); 
     9132      foreach( $name as $k => $part ) 
     9133        $name[$k] = ucfirst( $part ); 
     9134    } 
     9135    else { // split camelCase 
     9136      $chars = $name; 
     9137      $name  = array( $chars[0] ); 
     9138      $k     = 0; 
     9139      $x     = 1; 
     9140      while( FALSE !== ( $char = substr( $chars, $x, 1 ))) { 
     9141        if( ctype_upper( $char )) { 
     9142          $k += 1; 
     9143          $name[$k] = ''; 
     9144        } 
     9145        $name[$k]  .= $char; 
     9146        $x++; 
     9147      } 
     9148    } 
     9149  } 
     9150  $nl     = "\r\n"; 
     9151  $FN     = 'FN:'.implode( ' ', $name ).$nl; 
     9152  $name   = array_reverse( $name ); 
     9153  $N      = 'N:'.array_shift( $name ); 
     9154  $scCnt  = 0; 
     9155  while( NULL != ( $part = array_shift( $name ))) { 
     9156    if(( '4.0' != $version ) || ( 4 > $scCnt )) 
     9157      $scCnt += 1; 
     9158    $N   .= ';'.$part; 
     9159  } 
     9160  while(( '4.0' == $version ) && ( 4 > $scCnt )) { 
     9161    $N   .= ';'; 
     9162    $scCnt += 1; 
     9163  } 
     9164  $N     .= $nl; 
     9165  $EMAIL  = 'EMAIL:'.$email.$nl; 
     9166           /* create vCard */ 
     9167  $vCard  = 'BEGIN:VCARD'.$nl; 
     9168  $vCard .= "VERSION:$version$nl"; 
     9169  $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl"; 
     9170  $vCard .= $N; 
     9171  $vCard .= $FN; 
     9172  $vCard .= $EMAIL; 
     9173  $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl; 
     9174  $vCard .= 'END:VCARD'.$nl; 
     9175            /* save each vCard as (unique) single file */ 
     9176  if( $directory ) { 
     9177    $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email ); 
     9178    $cnt   = 1; 
     9179    $dbl   = ''; 
     9180    while( is_file ( $fname.$dbl.'.'.$ext )) { 
     9181      $cnt += 1; 
     9182      $dbl = "_$cnt"; 
     9183    } 
     9184    if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext )) 
     9185      return FALSE; 
     9186    return TRUE; 
     9187  } 
     9188            /* return vCard */ 
     9189  else 
     9190    return $vCard; 
     9191} 
     9192/** 
     9193 * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards 
     9194 * 
     9195 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9196 * @since 2.12.2 - 2012-05-07 
     9197 * @param object $calendar, iCalcreator vcalendar instance reference 
     9198 * $param string $version, vCard version (default 2.1) 
     9199 * $param string $directory, where to save vCards (default FALSE) 
     9200 * $param string $ext, vCard file extension (default 'vcf') 
     9201 * @return mixed 
     9202 */ 
     9203function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) { 
     9204  $hits   = array(); 
     9205  $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' ); 
     9206  foreach( $vCardP as $prop ) { 
     9207    $hits2 = $calendar->getProperty( $prop ); 
     9208    foreach( $hits2 as $propValue => $occCnt ) { 
     9209      if( FALSE === ( $pos = strpos( $propValue, '@' ))) 
     9210        continue; 
     9211      $propValue = str_replace( 'MAILTO:', '', $propValue ); 
     9212      if( isset( $hits[$propValue] )) 
     9213        $hits[$propValue] += $occCnt; 
     9214      else 
     9215        $hits[$propValue]  = $occCnt; 
     9216    } 
     9217  } 
     9218  if( empty( $hits )) 
     9219    return FALSE; 
     9220  ksort( $hits ); 
     9221  $output   = ''; 
     9222  foreach( $hits as $email => $skip ) { 
     9223    $res = iCal2vCard( $email, $version, $directory, $ext ); 
     9224    if( $directory && !$res ) 
     9225      return FALSE; 
     9226    elseif( !$res ) 
     9227      return $res; 
     9228    else 
     9229      $output .= $res; 
     9230  } 
     9231  if( $directory ) 
     9232    return TRUE; 
     9233  if( !empty( $output )) 
     9234    return $output; 
     9235  return FALSE; 
     9236} 
     9237/*********************************************************************************/ 
     9238/*          iCalcreator XML (rfc6321) helper functions                           */ 
     9239/*********************************************************************************/ 
     9240/** 
     9241 * format iCal XML output, rfc6321, using PHP SimpleXMLElement 
     9242 * 
     9243 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9244 * @since 2.15.6 - 2012-10-19 
     9245 * @param object $calendar, iCalcreator vcalendar instance reference 
     9246 * @return string 
     9247 */ 
     9248function iCal2XML( & $calendar ) { 
     9249            /** fix an SimpleXMLElement instance and create root element */ 
     9250  $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">'; 
     9251  $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->'; 
     9252  $xmlstr    .= '</icalendar>'; 
     9253  $xml        = new SimpleXMLElement( $xmlstr ); 
     9254  $vcalendar  = $xml->addChild( 'vcalendar' ); 
     9255            /** fix calendar properties */ 
     9256  $properties = $vcalendar->addChild( 'properties' ); 
     9257  $calProps = array( 'prodid', 'version', 'calscale', 'method' ); 
     9258  foreach( $calProps as $calProp ) { 
     9259    if( FALSE !== ( $content = $calendar->getProperty( $calProp ))) 
     9260      _addXMLchild( $properties, $calProp, 'text', $content ); 
     9261  } 
     9262  while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE ))) 
     9263    _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); 
     9264  $langCal = $calendar->getConfig( 'language' ); 
     9265            /** prepare to fix components with properties */ 
     9266  $components    = $vcalendar->addChild( 'components' ); 
     9267  $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' ); 
     9268  foreach( $comps as $compName ) { 
     9269    switch( $compName ) { 
     9270      case 'vevent': 
     9271      case 'vtodo': 
     9272        $subComps     = array( 'valarm' ); 
     9273        break; 
     9274      case 'vjournal': 
     9275      case 'vfreebusy': 
     9276        $subComps     = array(); 
     9277        break; 
     9278      case 'vtimezone': 
     9279        $subComps     = array( 'standard', 'daylight' ); 
     9280        break; 
     9281    } // end switch( $compName ) 
     9282            /** fix component properties */ 
     9283    while( FALSE !== ( $component = $calendar->getComponent( $compName ))) { 
     9284      $child      = $components->addChild( $compName ); 
     9285      $properties = $child->addChild( 'properties' ); 
     9286      $langComp   = $component->getConfig( 'language' ); 
     9287      $props      = $component->getConfig( 'setPropertyNames' ); 
     9288      foreach( $props as $prop ) { 
     9289        switch( strtolower( $prop )) { 
     9290          case 'attach':          // may occur multiple times, below 
     9291            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9292              $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri'; 
     9293              unset( $content['params']['VALUE'] ); 
     9294              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9295            } 
     9296            break; 
     9297          case 'attendee': 
     9298            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9299              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { 
     9300                if( $langComp ) 
     9301                  $content['params']['LANGUAGE'] = $langComp; 
     9302                elseif( $langCal ) 
     9303                  $content['params']['LANGUAGE'] = $langCal; 
     9304              } 
     9305              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); 
     9306            } 
     9307            break; 
     9308          case 'exdate': 
     9309            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9310              $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time'; 
     9311              unset( $content['params']['VALUE'] ); 
     9312              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9313            } 
     9314            break; 
     9315          case 'freebusy': 
     9316            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9317              if( is_array( $content ) && isset( $content['value']['fbtype'] )) { 
     9318                $content['params']['FBTYPE'] = $content['value']['fbtype']; 
     9319                unset( $content['value']['fbtype'] ); 
     9320              } 
     9321              _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] ); 
     9322            } 
     9323            break; 
     9324          case 'request-status': 
     9325            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9326              if( !isset( $content['params']['LANGUAGE'] )) { 
     9327                if( $langComp ) 
     9328                  $content['params']['LANGUAGE'] = $langComp; 
     9329                elseif( $langCal ) 
     9330                  $content['params']['LANGUAGE'] = $langCal; 
     9331              } 
     9332              _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] ); 
     9333            } 
     9334            break; 
     9335          case 'rdate': 
     9336            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9337              $type = 'date-time'; 
     9338              if( isset( $content['params']['VALUE'] )) { 
     9339                if( 'DATE' == $content['params']['VALUE'] ) 
     9340                  $type = 'date'; 
     9341                elseif( 'PERIOD' == $content['params']['VALUE'] ) 
     9342                  $type = 'period'; 
     9343              } 
     9344              unset( $content['params']['VALUE'] ); 
     9345              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9346            } 
     9347            break; 
     9348          case 'categories': 
     9349          case 'comment': 
     9350          case 'contact': 
     9351          case 'description': 
     9352          case 'related-to': 
     9353          case 'resources': 
     9354            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9355              if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) { 
     9356                if( $langComp ) 
     9357                  $content['params']['LANGUAGE'] = $langComp; 
     9358                elseif( $langCal ) 
     9359                  $content['params']['LANGUAGE'] = $langCal; 
     9360              } 
     9361              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); 
     9362            } 
     9363            break; 
     9364          case 'x-prop': 
     9365            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) 
     9366              _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); 
     9367            break; 
     9368          case 'created':         // single occurence below, if set 
     9369          case 'completed': 
     9370          case 'dtstamp': 
     9371          case 'last-modified': 
     9372            $utcDate = TRUE; 
     9373          case 'dtstart': 
     9374          case 'dtend': 
     9375          case 'due': 
     9376          case 'recurrence-id': 
     9377            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9378              $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time'; 
     9379              unset( $content['params']['VALUE'] ); 
     9380              if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] )) 
     9381                unset( $content['params']['TZID'] ); 
     9382              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9383            } 
     9384            unset( $utcDate ); 
     9385            break; 
     9386          case 'duration': 
     9387            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9388              if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] )) 
     9389                $content['params']['RELATED'] = 'END'; 
     9390              _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] ); 
     9391            } 
     9392            break; 
     9393          case 'rrule': 
     9394            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) 
     9395              _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] ); 
     9396            break; 
     9397          case 'class': 
     9398          case 'location': 
     9399          case 'status': 
     9400          case 'summary': 
     9401          case 'transp': 
     9402          case 'tzid': 
     9403          case 'uid': 
     9404            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9405              if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) { 
     9406                if( $langComp ) 
     9407                  $content['params']['LANGUAGE'] = $langComp; 
     9408                elseif( $langCal ) 
     9409                  $content['params']['LANGUAGE'] = $langCal; 
     9410              } 
     9411              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); 
     9412            } 
     9413            break; 
     9414          case 'geo': 
     9415            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) 
     9416              _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] ); 
     9417            break; 
     9418          case 'organizer': 
     9419            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { 
     9420              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { 
     9421                if( $langComp ) 
     9422                  $content['params']['LANGUAGE'] = $langComp; 
     9423                elseif( $langCal ) 
     9424                  $content['params']['LANGUAGE'] = $langCal; 
     9425              } 
     9426              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); 
     9427            } 
     9428            break; 
     9429          case 'percent-complete': 
     9430          case 'priority': 
     9431          case 'sequence': 
     9432            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) 
     9433              _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] ); 
     9434            break; 
     9435          case 'tzurl': 
     9436          case 'url': 
     9437            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) 
     9438              _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] ); 
     9439            break; 
     9440        } // end switch( $prop ) 
     9441      } // end foreach( $props as $prop ) 
     9442            /** fix subComponent properties, if any */ 
     9443      foreach( $subComps as $subCompName ) { 
     9444        while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) { 
     9445          $child2       = $child->addChild( $subCompName ); 
     9446          $properties   = $child2->addChild( 'properties' ); 
     9447          $langComp     = $subcomp->getConfig( 'language' ); 
     9448          $subCompProps = $subcomp->getConfig( 'setPropertyNames' ); 
     9449          foreach( $subCompProps as $prop ) { 
     9450            switch( strtolower( $prop )) { 
     9451              case 'attach':          // may occur multiple times, below 
     9452                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9453                  $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri'; 
     9454                  unset( $content['params']['VALUE'] ); 
     9455                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9456                } 
     9457                break; 
     9458              case 'attendee': 
     9459                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9460                  if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { 
     9461                    if( $langComp ) 
     9462                      $content['params']['LANGUAGE'] = $langComp; 
     9463                    elseif( $langCal ) 
     9464                      $content['params']['LANGUAGE'] = $langCal; 
     9465                  } 
     9466                  _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); 
     9467                } 
     9468                break; 
     9469              case 'comment': 
     9470              case 'tzname': 
     9471                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9472                  if( !isset( $content['params']['LANGUAGE'] )) { 
     9473                    if( $langComp ) 
     9474                      $content['params']['LANGUAGE'] = $langComp; 
     9475                    elseif( $langCal ) 
     9476                      $content['params']['LANGUAGE'] = $langCal; 
     9477                  } 
     9478                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); 
     9479                } 
     9480                break; 
     9481              case 'rdate': 
     9482                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9483                  $type = 'date-time'; 
     9484                  if( isset( $content['params']['VALUE'] )) { 
     9485                    if( 'DATE' == $content['params']['VALUE'] ) 
     9486                      $type = 'date'; 
     9487                    elseif( 'PERIOD' == $content['params']['VALUE'] ) 
     9488                      $type = 'period'; 
     9489                  } 
     9490                  unset( $content['params']['VALUE'] ); 
     9491                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9492                } 
     9493                break; 
     9494              case 'x-prop': 
     9495                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) 
     9496                  _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); 
     9497                break; 
     9498              case 'action':      // single occurence below, if set 
     9499              case 'description': 
     9500              case 'summary': 
     9501                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9502                  if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) { 
     9503                    if( $langComp ) 
     9504                      $content['params']['LANGUAGE'] = $langComp; 
     9505                    elseif( $langCal ) 
     9506                      $content['params']['LANGUAGE'] = $langCal; 
     9507                  } 
     9508                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); 
     9509                } 
     9510                break; 
     9511              case 'dtstart': 
     9512                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9513                  unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time 
     9514                  _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] ); 
     9515                } 
     9516                break; 
     9517              case 'duration': 
     9518                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) 
     9519                  _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] ); 
     9520                break; 
     9521              case 'repeat': 
     9522                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) 
     9523                  _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] ); 
     9524                break; 
     9525              case 'trigger': 
     9526                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { 
     9527                  if( isset( $content['value']['year'] )   && 
     9528                      isset( $content['value']['month'] )  && 
     9529                      isset( $content['value']['day'] )) 
     9530                    $type = 'date-time'; 
     9531                  else { 
     9532                    $type = 'duration'; 
     9533                    if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] )) 
     9534                      $content['params']['RELATED'] = 'END'; 
     9535                  } 
     9536                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); 
     9537                } 
     9538                break; 
     9539              case 'tzoffsetto': 
     9540              case 'tzoffsetfrom': 
     9541                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) 
     9542                  _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] ); 
     9543                break; 
     9544              case 'rrule': 
     9545                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) 
     9546                  _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] ); 
     9547                break; 
     9548            } // switch( $prop ) 
     9549          } // end foreach( $subCompProps as $prop ) 
     9550        } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName ))) 
     9551      } // end foreach( $subCombs as $subCompName ) 
     9552    } // end while( FALSE !== ( $component = $calendar->getComponent( $compName ))) 
     9553  } // end foreach( $comps as $compName) 
     9554  return $xml->asXML(); 
     9555} 
     9556/** 
     9557 * Add children to a SimpleXMLelement 
     9558 * 
     9559 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9560 * @since 2.15.5 - 2012-10-19 
     9561 * @param object $parent,  reference to a SimpleXMLelement node 
     9562 * @param string $name,    new element node name 
     9563 * @param string $type,    content type, subelement(-s) name 
     9564 * @param string $content, new subelement content 
     9565 * @param array  $params,  new element 'attributes' 
     9566 * @return void 
     9567 */ 
     9568function _addXMLchild( & $parent, $name, $type, $content, $params=array()) { 
     9569            /** create new child node */ 
     9570  $name  = strtolower( $name ); 
     9571  $child = $parent->addChild( $name ); 
     9572  if( isset( $params['VALUE'] )) 
     9573    unset( $params['VALUE'] ); 
     9574  if( !empty( $params )) { 
     9575    $parameters = $child->addChild( 'parameters' ); 
     9576    foreach( $params as $param => $parVal ) { 
     9577      $param = strtolower( $param ); 
     9578      if( 'x-' == substr( $param, 0, 2  )) { 
     9579        $p1 = $parameters->addChild( $param ); 
     9580        $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal )); 
     9581      } 
     9582      else { 
     9583        $p1 = $parameters->addChild( $param ); 
     9584        switch( $param ) { 
     9585          case 'altrep': 
     9586          case 'dir':            $ptype = 'uri';            break; 
     9587          case 'delegated-from': 
     9588          case 'delegated-to': 
     9589          case 'member': 
     9590          case 'sent-by':        $ptype = 'cal-address';    break; 
     9591          case 'rsvp':           $ptype = 'boolean';        break ; 
     9592          default:               $ptype = 'text';           break; 
     9593        } 
     9594        if( is_array( $parVal )) { 
     9595          foreach( $parVal as $pV ) 
     9596            $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV )); 
     9597        } 
     9598        else 
     9599          $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal )); 
     9600      } 
     9601    } 
     9602  } 
     9603  if( empty( $content ) && ( '0' != $content )) 
     9604    return; 
     9605            /** store content */ 
     9606  switch( $type ) { 
     9607    case 'binary': 
     9608      $v = $child->addChild( $type, $content ); 
     9609      break; 
     9610    case 'boolean': 
     9611      break; 
     9612    case 'cal-address': 
     9613      $v = $child->addChild( $type, $content ); 
     9614      break; 
     9615    case 'date': 
     9616      if( array_key_exists( 'year', $content )) 
     9617        $content = array( $content ); 
     9618      foreach( $content as $date ) { 
     9619        $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] ); 
     9620        $v = $child->addChild( $type, $str ); 
     9621      } 
     9622      break; 
     9623    case 'date-time': 
     9624      if( array_key_exists( 'year', $content )) 
     9625        $content = array( $content ); 
     9626      foreach( $content as $dt ) { 
     9627        if( !isset( $dt['hour'] )) $dt['hour'] = 0; 
     9628        if( !isset( $dt['min'] ))  $dt['min']  = 0; 
     9629        if( !isset( $dt['sec'] ))  $dt['sec']  = 0; 
     9630        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] ); 
     9631        if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] )) 
     9632          $str .= 'Z'; 
     9633        $v = $child->addChild( $type, $str ); 
     9634      } 
     9635      break; 
     9636    case 'duration': 
     9637      $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : ''; 
     9638      $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) ); 
     9639      break; 
     9640    case 'geo': 
     9641      $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' )); 
     9642      $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' )); 
     9643      break; 
     9644    case 'integer': 
     9645      $v = $child->addChild( $type, $content ); 
     9646      break; 
     9647    case 'period': 
     9648      if( !is_array( $content )) 
     9649        break; 
     9650      foreach( $content as $period ) { 
     9651        $v1 = $child->addChild( $type ); 
     9652        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] ); 
     9653        if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] )) 
     9654          $str .= 'Z'; 
     9655        $v2 = $v1->addChild( 'start', $str ); 
     9656        if( array_key_exists( 'year', $period[1] )) { 
     9657          $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] ); 
     9658          if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] )) 
     9659            $str .= 'Z'; 
     9660          $v2 = $v1->addChild( 'end', $str ); 
     9661        } 
     9662        else 
     9663          $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] )); 
     9664      } 
     9665      break; 
     9666    case 'recur': 
     9667      foreach( $content as $rulelabel => $rulevalue ) { 
     9668        $rulelabel = strtolower( $rulelabel ); 
     9669        switch( $rulelabel ) { 
     9670          case 'until': 
     9671            if( isset( $rulevalue['hour'] )) 
     9672              $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] ); 
     9673            else 
     9674              $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] ); 
     9675            $v = $child->addChild( $rulelabel, $str ); 
     9676            break; 
     9677          case 'bysecond': 
     9678          case 'byminute': 
     9679          case 'byhour': 
     9680          case 'bymonthday': 
     9681          case 'byyearday': 
     9682          case 'byweekno': 
     9683          case 'bymonth': 
     9684          case 'bysetpos': { 
     9685            if( is_array( $rulevalue )) { 
     9686              foreach( $rulevalue as $vix => $valuePart ) 
     9687                $v = $child->addChild( $rulelabel, $valuePart ); 
     9688            } 
     9689            else 
     9690              $v = $child->addChild( $rulelabel, $rulevalue ); 
     9691            break; 
     9692          } 
     9693          case 'byday': { 
     9694            if( isset( $rulevalue['DAY'] )) { 
     9695              $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : ''; 
     9696              $str .= $rulevalue['DAY']; 
     9697              $p    = $child->addChild( $rulelabel, $str ); 
     9698            } 
     9699            else { 
     9700              foreach( $rulevalue as $valuePart ) { 
     9701                if( isset( $valuePart['DAY'] )) { 
     9702                  $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : ''; 
     9703                  $str .= $valuePart['DAY']; 
     9704                  $p    = $child->addChild( $rulelabel, $str ); 
     9705                } 
     9706                else 
     9707                  $p    = $child->addChild( $rulelabel, $valuePart ); 
     9708              } 
     9709            } 
     9710            break; 
     9711          } 
     9712          case 'freq': 
     9713          case 'count': 
     9714          case 'interval': 
     9715          case 'wkst': 
     9716          default: 
     9717            $p = $child->addChild( $rulelabel, $rulevalue ); 
     9718            break; 
     9719        } // end switch( $rulelabel ) 
     9720      } // end foreach( $content as $rulelabel => $rulevalue ) 
     9721      break; 
     9722    case 'rstatus': 
     9723      $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', '')); 
     9724      $v = $child->addChild( 'description', htmlspecialchars( $content['text'] )); 
     9725      if( isset( $content['extdata'] )) 
     9726        $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] )); 
     9727      break; 
     9728    case 'text': 
     9729      if( !is_array( $content )) 
     9730        $content = array( $content ); 
     9731      foreach( $content as $part ) 
     9732        $v = $child->addChild( $type, htmlspecialchars( $part )); 
     9733      break; 
     9734    case 'time': 
     9735      break; 
     9736    case 'uri': 
     9737      $v = $child->addChild( $type, $content ); 
     9738      break; 
     9739    case 'utc-offset': 
     9740      if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) { 
     9741        $str     = substr( $content, 0, 1 ); 
     9742        $content = substr( $content, 1 ); 
     9743      } 
     9744      else 
     9745        $str     = '+'; 
     9746      $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 ); 
     9747      if( 4 < strlen( $content )) 
     9748        $str .= ':'.substr( $content, 4 ); 
     9749      $v = $child->addChild( $type, $str ); 
     9750      break; 
     9751    case 'unknown': 
     9752    default: 
     9753      if( is_array( $content )) 
     9754        $content = implode( '', $content ); 
     9755      $v = $child->addChild( 'unknown', htmlspecialchars( $content )); 
     9756      break; 
     9757  } 
     9758} 
     9759/** 
     9760 * parse xml string into iCalcreator instance 
     9761 * 
     9762 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9763 * @since 2.11.2 - 2012-01-31 
     9764 * @param  string $xmlstr 
     9765 * @param  array  $iCalcfg iCalcreator config array (opt) 
     9766 * @return mixed  iCalcreator instance or FALSE on error 
     9767 */ 
     9768function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) { 
     9769  libxml_use_internal_errors( TRUE ); 
     9770  $xml = simplexml_load_string( $xmlstr ); 
     9771  if( !$xml ) { 
     9772    $str    = ''; 
     9773    $return = FALSE; 
     9774    foreach( libxml_get_errors() as $error ) { 
     9775      switch ( $error->level ) { 
     9776        case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break; 
     9777        case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break; 
     9778        case LIBXML_ERR_WARNING: 
     9779        default:                 $str .= ' WARNING '; break; 
     9780      } 
     9781      $str .= PHP_EOL.'Error when loading XML'; 
     9782      if( !empty( $error->file )) 
     9783        $str .= ',  file:'.$error->file.', '; 
     9784      $str .= ', line:'.$error->line; 
     9785      $str .= ', ('.$error->code.') '.$error->message; 
     9786    } 
     9787    error_log( $str ); 
     9788    if( LIBXML_ERR_WARNING != $error->level ) 
     9789      return $return; 
     9790    libxml_clear_errors(); 
     9791  } 
     9792  return xml2iCal( $xml, $iCalcfg ); 
     9793} 
     9794/** 
     9795 * parse xml file into iCalcreator instance 
     9796 * 
     9797 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9798 * @since  2.11.2 - 2012-01-20 
     9799 * @param  string $xmlfile 
     9800 * @param  array$iCalcfg iCalcreator config array (opt) 
     9801 * @return mixediCalcreator instance or FALSE on error 
     9802 */ 
     9803function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) { 
     9804  libxml_use_internal_errors( TRUE ); 
     9805  $xml = simplexml_load_file( $xmlfile ); 
     9806  if( !$xml ) { 
     9807    $str = ''; 
     9808    foreach( libxml_get_errors() as $error ) { 
     9809      switch ( $error->level ) { 
     9810        case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break; 
     9811        case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break; 
     9812        case LIBXML_ERR_WARNING: 
     9813        default:                 $str .= 'WARNING '; break; 
     9814      } 
     9815      $str .= 'Failed loading XML'.PHP_EOL; 
     9816      if( !empty( $error->file )) 
     9817        $str .= ' file:'.$error->file.', '; 
     9818      $str .= 'line:'.$error->line.PHP_EOL; 
     9819      $str .= '('.$error->code.') '.$error->message.PHP_EOL; 
     9820    } 
     9821    error_log( $str ); 
     9822    if( LIBXML_ERR_WARNING != $error->level ) 
     9823      return FALSE; 
     9824    libxml_clear_errors(); 
     9825  } 
     9826  return xml2iCal( $xml, $iCalcfg ); 
     9827} 
     9828/** 
     9829 * parse SimpleXMLElement instance into iCalcreator instance 
     9830 * 
     9831 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9832 * @since  2.11.2 - 2012-01-27 
     9833 * @param  object $xmlobj  SimpleXMLElement 
     9834 * @param  array  $iCalcfg iCalcreator config array (opt) 
     9835 * @return mixed  iCalcreator instance or FALSE on error 
     9836 */ 
     9837function & XML2iCal( $xmlobj, $iCalcfg=array()) { 
     9838  $iCal = new vcalendar( $iCalcfg ); 
     9839  foreach( $xmlobj->children() as $icalendar ) { // vcalendar 
     9840    foreach( $icalendar->children() as $calPart ) { // calendar properties and components 
     9841      if( 'components' == $calPart->getName()) { 
     9842        foreach( $calPart->children() as $component ) { // single components 
     9843          if( 0 < $component->count()) 
     9844            _getXMLComponents( $iCal, $component ); 
     9845        } 
     9846      } 
     9847      elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) { 
     9848        foreach( $calPart->children() as $calProp ) { // calendar properties 
     9849         $propName = $calProp->getName(); 
     9850          if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 ))) 
     9851            continue; 
     9852          $params = array(); 
     9853          foreach( $calProp->children() as $calPropElem ) { // single calendar property 
     9854            if( 'parameters' == $calPropElem->getName()) 
     9855              $params = _getXMLParams( $calPropElem ); 
     9856            else 
     9857              $iCal->setProperty( $propName, reset( $calPropElem ), $params ); 
     9858          } // end foreach( $calProp->children() as $calPropElem ) 
     9859        } // end foreach( $calPart->properties->children() as $calProp ) 
     9860      } // end if( 0 < $calPart->properties->count()) 
     9861    } // end foreach( $icalendar->children() as $calPart ) 
     9862  } // end foreach( $xmlobj->children() as $icalendar ) 
     9863  return $iCal; 
     9864} 
     9865/** 
     9866 * parse SimpleXMLElement instance property parameters and return iCalcreator property parameter array 
     9867 * 
     9868 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9869 * @since  2.11.2 - 2012-01-15 
     9870 * @param  object $parameters SimpleXMLElement 
     9871 * @return array  iCalcreator property parameter array 
     9872 */ 
     9873function _getXMLParams( & $parameters ) { 
     9874  if( 1 > $parameters->count()) 
     9875    return array(); 
     9876  $params = array(); 
     9877  foreach( $parameters->children() as $parameter ) { // single parameter key 
     9878    $key   = strtoupper( $parameter->getName()); 
     9879    $value = array(); 
     9880    foreach( $parameter->children() as $paramValue ) // skip parameter value type 
     9881      $value[] = reset( $paramValue ); 
     9882    if( 2 > count( $value )) 
     9883      $params[$key] = html_entity_decode( reset( $value )); 
     9884    else 
     9885      $params[$key] = $value; 
     9886  } 
     9887  return $params; 
     9888} 
     9889/** 
     9890 * parse SimpleXMLElement instance components, create iCalcreator component and update 
     9891 * 
     9892 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9893 * @since  2.11.2 - 2012-01-15 
     9894 * @param  array  $iCal iCalcreator calendar instance 
     9895 * @param  object $component SimpleXMLElement 
     9896 * @return void 
     9897 */ 
     9898function _getXMLComponents( & $iCal, & $component ) { 
     9899  $compName = $component->getName(); 
     9900  $comp     = & $iCal->newComponent( $compName ); 
     9901  $subComponents = array( 'valarm', 'standard', 'daylight' ); 
     9902  foreach( $component->children() as $compPart ) { // properties and (opt) subComponents 
     9903    if( 1 > $compPart->count()) 
     9904      continue; 
     9905    if( in_array( $compPart->getName(), $subComponents )) 
     9906      _getXMLComponents( $comp, $compPart ); 
     9907    elseif( 'properties' == $compPart->getName()) { 
     9908      foreach( $compPart->children() as $property ) // properties as single property 
     9909        _getXMLProperties( $comp, $property ); 
     9910    } 
     9911  } // end foreach( $component->children() as $compPart ) 
     9912} 
     9913/** 
     9914 * parse SimpleXMLElement instance property, create iCalcreator component property 
     9915 * 
     9916 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     9917 * @since  2.11.2 - 2012-01-27 
     9918 * @param  array  $iCal iCalcreator calendar instance 
     9919 * @param  object $component SimpleXMLElement 
     9920 * @return void 
     9921 */ 
     9922function _getXMLProperties( & $iCal, & $property ) { 
     9923  $propName  = $property->getName(); 
     9924  $value     = $params = array(); 
     9925  $valueType = ''; 
     9926  foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s) 
     9927    $valueType = $propPart->getName(); 
     9928    if( 'parameters' == $valueType) { 
     9929      $params = _getXMLParams( $propPart ); 
     9930      continue; 
     9931    } 
     9932    switch( $valueType ) { 
     9933      case 'binary': 
     9934        $value = reset( $propPart ); 
     9935        break; 
     9936      case 'boolean': 
     9937        break; 
     9938      case 'cal-address': 
     9939        $value = reset( $propPart ); 
     9940        break; 
     9941      case 'date': 
     9942        $params['VALUE'] = 'DATE'; 
     9943      case 'date-time': 
     9944        if(( 'exdate' == $propName ) || ( 'rdate' == $propName )) 
     9945          $value[] = reset( $propPart ); 
     9946        else 
     9947          $value = reset( $propPart ); 
     9948        break; 
     9949      case 'duration': 
     9950        $value = reset( $propPart ); 
     9951        break; 
     9952//        case 'geo': 
     9953      case 'latitude': 
     9954      case 'longitude': 
     9955        $value[$valueType] = reset( $propPart ); 
     9956        break; 
     9957      case 'integer': 
     9958        $value = reset( $propPart ); 
     9959        break; 
     9960      case 'period': 
     9961        if( 'rdate' == $propName ) 
     9962          $params['VALUE'] = 'PERIOD'; 
     9963        $pData = array(); 
     9964        foreach( $propPart->children() as $periodPart ) 
     9965          $pData[] = reset( $periodPart ); 
     9966        if( !empty( $pData )) 
     9967          $value[] = $pData; 
     9968        break; 
     9969//        case 'rrule': 
     9970      case 'freq': 
     9971      case 'count': 
     9972      case 'until': 
     9973      case 'interval': 
     9974      case 'wkst': 
     9975        $value[$valueType] = reset( $propPart ); 
     9976        break; 
     9977      case 'bysecond': 
     9978      case 'byminute': 
     9979      case 'byhour': 
     9980      case 'bymonthday': 
     9981      case 'byyearday': 
     9982      case 'byweekno': 
     9983      case 'bymonth': 
     9984      case 'bysetpos': 
     9985        $value[$valueType][] = reset( $propPart ); 
     9986        break; 
     9987      case 'byday': 
     9988        $byday = reset( $propPart ); 
     9989        if( 2 == strlen( $byday )) 
     9990          $value[$valueType][] = array( 'DAY' => $byday ); 
     9991        else { 
     9992          $day = substr( $byday, -2 ); 
     9993          $key = substr( $byday, 0, ( strlen( $byday ) - 2 )); 
     9994          $value[$valueType][] = array( $key, 'DAY' => $day ); 
     9995        } 
     9996        break; 
     9997//      case 'rstatus': 
     9998      case 'code': 
     9999        $value[0] = reset( $propPart ); 
     10000        break; 
     10001      case 'description': 
     10002        $value[1] = reset( $propPart ); 
     10003        break; 
     10004      case 'data': 
     10005        $value[2] = reset( $propPart ); 
     10006        break; 
     10007      case 'text': 
     10008        $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart )); 
     10009        $value['text'][] = html_entity_decode( $text ); 
     10010        break; 
     10011      case 'time': 
     10012        break; 
     10013      case 'uri': 
     10014        $value = reset( $propPart ); 
     10015        break; 
     10016      case 'utc-offset': 
     10017        $value = str_replace( ':', '', reset( $propPart )); 
     10018        break; 
     10019      case 'unknown': 
     10020      default: 
     10021        $value = html_entity_decode( reset( $propPart )); 
     10022        break; 
     10023    } // end switch( $valueType ) 
     10024  } // end  foreach( $property->children() as $propPart ) 
     10025  if( 'freebusy' == $propName ) { 
     10026    $fbtype = $params['FBTYPE']; 
     10027    unset( $params['FBTYPE'] ); 
     10028    $iCal->setProperty( $propName, $fbtype, $value, $params ); 
     10029  } 
     10030  elseif( 'geo' == $propName ) 
     10031    $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params ); 
     10032  elseif( 'request-status' == $propName ) { 
     10033    if( !isset( $value[2] )) 
     10034      $value[2] = FALSE; 
     10035    $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params ); 
     10036  } 
     10037  else { 
     10038    if( isset( $value['text'] ) && is_array( $value['text'] )) { 
     10039      if(( 'categories' == $propName ) || ( 'resources' == $propName )) 
     10040        $value = $value['text']; 
     10041      else 
     10042        $value = reset( $value['text'] ); 
     10043    } 
     10044    $iCal->setProperty( $propName, $value, $params ); 
     10045  } 
     10046} 
     10047/*********************************************************************************/ 
     10048/*          Additional functions to use with vtimezone components                */ 
     10049/*********************************************************************************/ 
     10050/** 
     10051 * For use with 
     10052 * iCalcreator (kigkonsult.se/iCalcreator/index.php) 
     10053 * copyright (c) 2011 Yitzchok Lavi 
     10054 * icalcreator@onebigsystem.com 
     10055 * 
     10056 * This library is free software; you can redistribute it and/or 
     10057 * modify it under the terms of the GNU Lesser General Public 
     10058 * License as published by the Free Software Foundation; either 
     10059 * version 2.1 of the License, or (at your option) any later version. 
     10060 * 
     10061 * This library is distributed in the hope that it will be useful, 
     10062 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
     10063 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
     10064 * Lesser General Public License for more details. 
     10065 * 
     10066 * You should have received a copy of the GNU Lesser General Public 
     10067 * License along with this library; if not, write to the Free Software 
     10068 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
     10069 */ 
     10070/** 
     10071 * Additional functions to use with vtimezone components 
     10072 * 
     10073 * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')! 
     10074 * 
     10075 * @author Yitzchok Lavi <icalcreator@onebigsystem.com> 
     10076 *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> 
     10077 * @version 1.0.2 - 2011-02-24 
     10078 * 
     10079 */ 
     10080/** 
     10081 * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the 
     10082 * timezone, according to the VTIMEZONE information in the input array. 
     10083 * 
     10084 * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below) 
     10085 * $param string $tzid,           time zone identifier 
     10086 * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format) 
     10087 * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname' 
     10088 * 
     10089 */ 
     10090function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) { 
     10091    if( is_array( $timestamp )) { 
     10092//$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ### 
     10093      $timestamp = gmmktime( 
     10094            $timestamp['hour'], 
     10095            $timestamp['min'], 
     10096            $timestamp['sec'], 
     10097            $timestamp['month'], 
     10098            $timestamp['day'], 
     10099            $timestamp['year'] 
     10100            ) ; 
     10101//echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ### 
     10102    } 
     10103    $tzoffset = array(); 
     10104    // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates) 
     10105    $tzoffset['offsetHis'] = '+0000'; 
     10106    $tzoffset['offsetSec'] = 0; 
     10107    $tzoffset['tzname']    = '?'; 
     10108    if( !isset( $timezonesarray[$tzid] )) 
     10109      return $tzoffset; 
     10110    $tzdatearray = $timezonesarray[$tzid]; 
     10111    if ( is_array($tzdatearray) ) { 
     10112        sort($tzdatearray); // just in case 
     10113        if ( $timestamp < $tzdatearray[0]['timestamp'] ) { 
     10114            // our date is before the first change 
     10115            $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ; 
     10116            $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ; 
     10117            $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case 
     10118        } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) { 
     10119            // our date is after the last change (we do this so our scan can stop at the last record but one) 
     10120            $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ; 
     10121            $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ; 
     10122            $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ; 
     10123        } else { 
     10124            // our date somewhere in between 
     10125            // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it 
     10126            // we don't include the last date in our loop as there isn't one after it to check 
     10127            for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) { 
     10128                if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) { 
     10129                    $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ; 
     10130                    $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ; 
     10131                    $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ; 
     10132                    break; 
     10133                } 
     10134            } 
     10135        } 
     10136    } 
     10137    return $tzoffset; 
     10138} 
     10139/** 
     10140 * Returns an array containing all the timezone data in the vcalendar object 
     10141 * 
     10142 * @param object $vcalendar, iCalcreator calendar instance 
     10143 * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname) 
     10144 *                based on the timezone data in the vcalendar object 
     10145 * 
     10146 */ 
     10147function getTimezonesAsDateArrays($vcalendar) { 
     10148    $timezonedata = array(); 
     10149    while( $vtz = $vcalendar->getComponent( 'vtimezone' )) { 
     10150        $tzid       = $vtz->getProperty('tzid'); 
     10151        $alltzdates = array(); 
     10152        while ( $vtzc = $vtz->getComponent( 'standard' )) { 
     10153            $newtzdates = expandTimezoneDates($vtzc); 
     10154            $alltzdates = array_merge($alltzdates, $newtzdates); 
     10155        } 
     10156        while ( $vtzc = $vtz->getComponent( 'daylight' )) { 
     10157            $newtzdates = expandTimezoneDates($vtzc); 
     10158            $alltzdates = array_merge($alltzdates, $newtzdates); 
     10159        } 
     10160        sort($alltzdates); 
     10161        $timezonedata[$tzid] = $alltzdates; 
     10162    } 
     10163    return $timezonedata; 
     10164} 
     10165/** 
     10166 * Returns an array containing time zone data from vtimezone standard/daylight instances 
     10167 * 
     10168 * @param object $vtzc, an iCalcreator calendar standard/daylight instance 
     10169 * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname) 
     10170 * 
     10171 */ 
     10172function expandTimezoneDates($vtzc) { 
     10173    $tzdates = array(); 
     10174    // prepare time zone "description" to attach to each change 
     10175    $tzbefore = array(); 
     10176    $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ; 
     10177    $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']); 
     10178    if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 ))) 
     10179      $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec']; 
     10180    $tzafter = array(); 
     10181    $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ; 
     10182    $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']); 
     10183    if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 ))) 
     10184      $tzafter['offsetSec'] = '+'.$tzafter['offsetSec']; 
     10185    if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname'))) 
     10186      $tzafter['tzname'] = $tzafter['offsetHis']; 
     10187    // find out where to start from 
     10188    $dtstart = $vtzc->getProperty('dtstart'); 
     10189    $dtstarttimestamp = mktime( 
     10190            $dtstart['hour'], 
     10191            $dtstart['min'], 
     10192            $dtstart['sec'], 
     10193            $dtstart['month'], 
     10194            $dtstart['day'], 
     10195            $dtstart['year'] 
     10196            ) ; 
     10197    if( !isset( $dtstart['unparsedtext'] )) // ?? 
     10198      $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] ); 
     10199    if ( $dtstarttimestamp == 0 ) { 
     10200        // it seems that the dtstart string may not have parsed correctly 
     10201        // let's set a timestamp starting from 1902, using the time part of the original string 
     10202        // so that the time will change at the right time of day 
     10203        // at worst we'll get midnight again 
     10204        $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ; 
     10205        $dtstarttimestamp = strtotime("19020101",0); 
     10206        $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp); 
     10207    } 
     10208    // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp 
     10209    $diff  = -1 * $tzbefore['offsetSec']; 
     10210    $dtstarttimestamp += $diff; 
     10211                // add this (start) change to the array of changes 
     10212    $tzdates[] = array( 
     10213        'timestamp' => $dtstarttimestamp, 
     10214        'tzbefore'  => $tzbefore, 
     10215        'tzafter'   => $tzafter 
     10216        ); 
     10217    $datearray = getdate($dtstarttimestamp); 
     10218    // save original array to use time parts, because strtotime (used below) apparently loses the time 
     10219    $changetime = $datearray ; 
     10220    // generate dates according to an RRULE line 
     10221    $rrule = $vtzc->getProperty('rrule') ; 
     10222    if ( is_array($rrule) ) { 
     10223        if ( $rrule['FREQ'] == 'YEARLY' ) { 
     10224            // calculate transition dates starting from DTSTART 
     10225            $offsetchangetimestamp = $dtstarttimestamp; 
     10226            // calculate transition dates until 10 years in the future 
     10227            $stoptimestamp = strtotime("+10 year",time()); 
     10228            // if UNTIL is set, calculate until then (however far ahead) 
     10229            if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) { 
     10230                $stoptimestamp = mktime( 
     10231                    $rrule['UNTIL']['hour'], 
     10232                    $rrule['UNTIL']['min'], 
     10233                    $rrule['UNTIL']['sec'], 
     10234                    $rrule['UNTIL']['month'], 
     10235                    $rrule['UNTIL']['day'], 
     10236                    $rrule['UNTIL']['year'] 
     10237                    ) ; 
     10238            } 
     10239            $count = 0 ; 
     10240            $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ; 
     10241            $daynames = array( 
     10242                        'SU' => 'Sunday', 
     10243                        'MO' => 'Monday', 
     10244                        'TU' => 'Tuesday', 
     10245                        'WE' => 'Wednesday', 
     10246                        'TH' => 'Thursday', 
     10247                        'FR' => 'Friday', 
     10248                        'SA' => 'Saturday' 
     10249                        ); 
     10250            // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates 
     10251            while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) { 
     10252                // break up the timestamp into its parts 
     10253                $datearray = getdate($offsetchangetimestamp); 
     10254                if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) { 
     10255                    // set the month 
     10256                    $datearray['mon'] = $rrule['BYMONTH'] ; 
     10257                } 
     10258                if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) { 
     10259                    // set specific day of month 
     10260                    $datearray['mday']  = $rrule['BYMONTHDAY']; 
     10261                } elseif ( is_array($rrule['BYDAY']) ) { 
     10262                    // find the Xth WKDAY in the month 
     10263                    // the starting point for this process is the first of the month set above 
     10264                    $datearray['mday'] = 1 ; 
     10265                    // turn $datearray as it is now back into a timestamp 
     10266                    $offsetchangetimestamp = mktime( 
     10267                        $datearray['hours'], 
     10268                        $datearray['minutes'], 
     10269                        $datearray['seconds'], 
     10270                        $datearray['mon'], 
     10271                        $datearray['mday'], 
     10272                        $datearray['year'] 
     10273                            ); 
     10274                    if ($rrule['BYDAY'][0] > 0) { 
     10275                        // to find Xth WKDAY in month, we find last WKDAY in month before 
     10276                        // we do that by finding first WKDAY in this month and going back one week 
     10277                        // then we add X weeks (below) 
     10278                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp); 
     10279                        $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp); 
     10280                    } else { 
     10281                        // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month 
     10282                        // we do that by going forward one month and going to WKDAY there 
     10283                        // then we subtract X weeks (below) 
     10284                        $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp); 
     10285                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp); 
     10286                    } 
     10287                    // now move forward or back the appropriate number of weeks, into the month we want 
     10288                    $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp); 
     10289                    $datearray = getdate($offsetchangetimestamp); 
     10290                } 
     10291                // convert the date parts back into a timestamp, setting the time parts according to the 
     10292                // original time data which we stored 
     10293                $offsetchangetimestamp = mktime( 
     10294                    $changetime['hours'], 
     10295                    $changetime['minutes'], 
     10296                    $changetime['seconds'] + $diff, 
     10297                    $datearray['mon'], 
     10298                    $datearray['mday'], 
     10299                    $datearray['year'] 
     10300                        ); 
     10301                // add this change to the array of changes 
     10302                $tzdates[] = array( 
     10303                    'timestamp' => $offsetchangetimestamp, 
     10304                    'tzbefore'  => $tzbefore, 
     10305                    'tzafter'   => $tzafter 
     10306                    ); 
     10307                // update counters (timestamp and count) 
     10308                $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp); 
     10309                $count += 1 ; 
     10310            } 
     10311        } 
     10312    } 
     10313    // generate dates according to RDATE lines 
     10314    while ($rdates = $vtzc->getProperty('rdate')) { 
     10315        if ( is_array($rdates) ) { 
     10316 
     10317            foreach ( $rdates as $rdate ) { 
     10318                // convert the explicit change date to a timestamp 
     10319                $offsetchangetimestamp = mktime( 
     10320                        $rdate['hour'], 
     10321                        $rdate['min'], 
     10322                        $rdate['sec'] + $diff, 
     10323                        $rdate['month'], 
     10324                        $rdate['day'], 
     10325                        $rdate['year'] 
     10326                        ) ; 
     10327                // add this change to the array of changes 
     10328                $tzdates[] = array( 
     10329                    'timestamp' => $offsetchangetimestamp, 
     10330                    'tzbefore'  => $tzbefore, 
     10331                    'tzafter'   => $tzafter 
     10332                    ); 
     10333            } 
     10334        } 
     10335    } 
     10336    return $tzdates; 
     10337} 
    691310338?> 
Note: See TracChangeset for help on using the changeset viewer.