Projet

Général

Profil

Paste
Télécharger (110 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / iCalcreator-2.22.1 / lib / iCalUtilityFunctions.class.php @ 9525582e

1
<?php
2
/*********************************************************************************/
3
/**
4
 *
5
 * A PHP implementation of rfc2445/rfc5545.
6
 *
7
 * @copyright Copyright (c) 2007-2015 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
8
 * @link      http://kigkonsult.se/iCalcreator/index.php
9
 * @license   http://kigkonsult.se/downloads/dl.php?f=LGPL
10
 * @package   iCalcreator
11
 * @version   2.22
12
 */
13
/**
14
 * This library is free software; you can redistribute it and/or
15
 * modify it under the terms of the GNU Lesser General Public
16
 * License as published by the Free Software Foundation; either
17
 * version 2.1 of the License, or (at your option) any later version.
18
 *
19
 * This library is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22
 * Lesser General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public
25
 * License along with this library; if not, write to the Free Software
26
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
 */
28
/*********************************************************************************/
29
/**
30
 * moving all utility (static) functions to a utility class
31
 *
32
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
33
 * @since 2.21.11 - 2015-04-03
34
 */
35
class iCalUtilityFunctions {
36
/** @var string  tmp line delimiter, used in convEolChar (parse) */
37
  private static $baseDelim = null;
38
/** @var array protocol prefix, used in _splitContent() */
39
  private static $parValPrefix = array ( 'MStz'   => array( 'utc-', 'utc+', 'gmt-', 'gmt+' )
40
                                       , 'Proto3' => array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' )
41
                                       , 'Proto4' => array( 'crid:', 'news:', 'pres:' )
42
                                       , 'Proto6' => array( 'mailto:' ));
43
/** @var string  output format for geo latitude and longitude (before rtrim) */
44
  public static $geoLatFmt  = '%09.6f';
45
  public static $geoLongFmt = '%8.6f';
46
/** @var array  date/datetime formats */
47
  public static $fmt        = array( 'Ymd'       => '%04d%02d%02d',
48
                                     'His'       => '%02d%02d%02d',
49
                                     'dayOfDays' => 'day %d of %d',
50
                                     'durDHis'   => '%a days, %h hours, %i min, %s sec',
51
                                     'Ymd2'      => 'Y-m-d',
52
                                     'YmdHis2'   => 'Y-m-d H:i:s',
53
                                     'YmdHis2e'  => 'Y-m-d H:i:s e',
54
                                     'YmdHis3'   => 'Y-m-d-H-i-s',
55
                                     'YmdHise'   => '%04d-%02d-%02d %02d:%02d:%02d %s',
56
                                     'YmdTHisO'  => 'Y-m-d\TH:i:s O',
57
                                     'dateKey'   => '%04d%02d%02d%02d%02d%02d000',
58
                                   );
59
/** @var array  component property UID value */
60
  public static $vComps     = array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
61
  public static $mComps     = array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' );
62
  public static $miscComps  = array( 'valarm', 'vtimezone', 'standard', 'daylight' );
63
  public static $tzComps    = array( 'vtimezone', 'standard', 'daylight' );
64
  public static $allComps   = array( 'vtimezone', 'standard', 'daylight', 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm' );
65
/** @var array  component property collections */
66
  public static $mProps1    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
67
  public static $mProps2    = array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
68
                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  );
69
  public static $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
70
  public static $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
71
/** @var object Store the single instance of iCalUtilityFunctions */
72
  private static $m_pInstance;
73
/**
74
 * Private constructor to limit object instantiation to within the class
75
 *
76
 * @access private
77
 */
78
  private function __construct() {
79
    $m_pInstance = FALSE;
80
  }
81
/** @var array component property UID value */
82
/**
83
 * Getter method for creating/returning the single instance of this class
84
 *
85
 * @uses iCalUtilityFunctions::$m_pInstance
86
 */
87
  public static function getInstance() {
88
    if (!self::$m_pInstance)
89
      self::$m_pInstance = new iCalUtilityFunctions();
90
    return self::$m_pInstance;
91
  }
92
/**
93
 * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
94
 *
95
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
96
 * @since 2.16.24 - 2013-06-26
97
 * @param array $datetime
98
 * @param int $parno optional, default FALSE
99
 * @return array
100
 */
101
  public static function _chkDateArr( $datetime, $parno=FALSE ) {
102
    $output = array();
103
    if(( !$parno || ( 6 <= $parno )) && isset( $datetime[3] ) && !isset( $datetime[4] )) { // Y-m-d with tz
104
      $temp        = $datetime[3];
105
      $datetime[3] = $datetime[4] = $datetime[5] = 0;
106
      $datetime[6] = $temp;
107
    }
108
    foreach( $datetime as $dateKey => $datePart ) {
109
      switch ( $dateKey ) {
110
        case '0': case 'year':   $output['year']  = $datePart; break;
111
        case '1': case 'month':  $output['month'] = $datePart; break;
112
        case '2': case 'day':    $output['day']   = $datePart; break;
113
      }
114
      if( 3 != $parno ) {
115
        switch ( $dateKey ) {
116
          case '0':
117
          case '1':
118
          case '2': break;
119
          case '3': case 'hour': $output['hour']  = $datePart; break;
120
          case '4': case 'min' : $output['min']   = $datePart; break;
121
          case '5': case 'sec' : $output['sec']   = $datePart; break;
122
          case '6': case 'tz'  : $output['tz']    = $datePart; break;
123
        }
124
      }
125
    }
126
    if( 3 != $parno ) {
127
      if( !isset( $output['hour'] ))         $output['hour'] = 0;
128
      if( !isset( $output['min']  ))         $output['min']  = 0;
129
      if( !isset( $output['sec']  ))         $output['sec']  = 0;
130
      if( isset( $output['tz'] ) &&
131
        (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
132
                                             $output['tz']   = 'Z';
133
    }
134
    return $output;
135
  }
136
/**
137
 * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
138
 *
139
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
140
 * @since 2.10.30 - 2012-01-16
141
 * @param array $theDate    date to check
142
 * @param int   $parno      no of date parts (i.e. year, month.. .)
143
 * @param array $params     property parameters
144
 * @uses iCalUtilityFunctions::_strdate2date()
145
 * @uses iCalUtilityFunctions::_isOffset()
146
 * @return void
147
 */
148
  public static function _chkdatecfg( $theDate, & $parno, & $params ) {
149
    if( isset( $params['TZID'] ))
150
      $parno = 6;
151
    elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
152
      $parno = 3;
153
    else {
154
      if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
155
        $parno = 7;
156
      if( is_array( $theDate )) {
157
        if( isset( $theDate['timestamp'] ))
158
          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
159
        else
160
          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
161
        if( !empty( $tzid )) {
162
          $parno = 7;
163
          if( !iCalUtilityFunctions::_isOffset( $tzid ))
164
            $params['TZID'] = $tzid; // save only timezone
165
        }
166
        elseif( !$parno && ( 3 == count( $theDate )) &&
167
          ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
168
          $parno = 3;
169
        else
170
          $parno = 6;
171
      }
172
      else { // string
173
        $date = trim( $theDate );
174
        if( 'Z' == substr( $date, -1 ))
175
          $parno = 7; // UTC DATE-TIME
176
        elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
177
          ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
178
          $parno = 3; // DATE
179
        $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
180
        unset( $date['unparsedtext'] );
181
        if( !empty( $date['tz'] )) {
182
          $parno = 7;
183
          if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
184
            $params['TZID'] = $date['tz']; // save only timezone
185
        }
186
        elseif( empty( $parno ))
187
          $parno = 6;
188
      }
189
      if( isset( $params['TZID'] ))
190
        $parno = 6;
191
    }
192
  }
193
/**
194
 * vcalendar sort callback function
195
 *
196
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
197
 * @since 2.16.2 - 2012-12-17
198
 * @param array $a
199
 * @param array $b
200
 * @uses calendarComponent::$objName
201
 * @return int
202
 */
203
  public static function _cmpfcn( $a, $b ) {
204
    if(        empty( $a ))                       return -1;
205
    if(        empty( $b ))                       return  1;
206
    if( 'vtimezone' == $a->objName ) {
207
      if( 'vtimezone' != $b->objName )            return -1;
208
      elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
209
      else                                        return  1;
210
    }
211
    elseif( 'vtimezone' == $b->objName )          return  1;
212
    $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
213
    for( $k = 0; $k < 4 ; $k++ ) {
214
      if(        empty( $a->srtk[$k] ))           return -1;
215
      elseif(    empty( $b->srtk[$k] ))           return  1;
216
      if( is_array( $a->srtk[$k] )) {
217
        if( is_array( $b->srtk[$k] )) {
218
          foreach( $sortkeys as $key ) {
219
            if    ( !isset( $a->srtk[$k][$key] )) return -1;
220
            elseif( !isset( $b->srtk[$k][$key] )) return  1;
221
            if    (  empty( $a->srtk[$k][$key] )) return -1;
222
            elseif(  empty( $b->srtk[$k][$key] )) return  1;
223
            if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
224
                                                  continue;
225
            if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
226
                                                  return -1;
227
            elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
228
                                                  return  1;
229
          }
230
        }
231
        else                                      return -1;
232
      }
233
      elseif( is_array( $b->srtk[$k] ))           return  1;
234
      elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
235
      elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
236
    }
237
    return 0;
238
  }
239
/**
240
 * byte oriented line folding fix
241
 *
242
 * remove any line-endings that may include spaces or tabs
243
 * and convert all line endings (iCal default '\r\n'),
244
 * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
245
 *
246
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
247
 * @since 2.18.16 - 2014-04-04
248
 * @param string $text
249
 * @param string $nl
250
 * @uses iCalUtilityFunctions::$baseDelim
251
 * @return string
252
 */
253
  public static function convEolChar( & $text, $nl ) {
254
            /* fix dummy line separator */
255
    if( empty( iCalUtilityFunctions::$baseDelim )) {
256
      iCalUtilityFunctions::$baseDelim = substr( microtime(), 2, 4 );
257
      $base   = 'aAbB!cCdD"eEfF#gGhHiIjJ%kKlL&mMnN/oOpP(rRsS)tTuU=vVxX?uUvV*wWzZ-1234_5678|90';
258
      $len    = strlen( $base ) - 1;
259
      for( $p = 0; $p < 6; $p++ )
260
        iCalUtilityFunctions::$baseDelim .= $base{mt_rand( 0, $len )};
261
    }
262
            /* fix eol chars */
263
    $text   = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), iCalUtilityFunctions::$baseDelim, $text );
264
            /* fix empty lines */
265
    $text   = str_replace( iCalUtilityFunctions::$baseDelim.iCalUtilityFunctions::$baseDelim, iCalUtilityFunctions::$baseDelim.str_pad( '', 75 ).iCalUtilityFunctions::$baseDelim, $text );
266
            /* fix line folding */
267
    $text   = str_replace( iCalUtilityFunctions::$baseDelim, $nl, $text );
268
    $text   = str_replace( array( $nl.' ', $nl."\t" ), '', $text );
269
            /* split in component/property lines */
270
    $text   = explode( $nl, $text );
271
    return $text;
272
  }
273
/**
274
 * create a calendar timezone and standard/daylight components
275
 *
276
 * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
277
 *
278
 * BEGIN:VTIMEZONE
279
 * TZID:Europe/Stockholm
280
 * BEGIN:STANDARD
281
 * DTSTART:20101031T020000
282
 * TZOFFSETFROM:+0200
283
 * TZOFFSETTO:+0100
284
 * TZNAME:CET
285
 * END:STANDARD
286
 * BEGIN:DAYLIGHT
287
 * DTSTART:20100328T030000
288
 * TZOFFSETFROM:+0100
289
 * TZOFFSETTO:+0200
290
 * TZNAME:CEST
291
 * END:DAYLIGHT
292
 * END:VTIMEZONE
293
 *
294
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
295
 * @since 2.21.11 - 2015-04-03
296
 * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
297
 * Additional changes jpirkey
298
 * @param object $calendar  iCalcreator calendar instance
299
 * @param string $timezone  PHP5 (DateTimeZone) valid timezone
300
 * @param array  $xProp     *[x-propName => x-propValue], optional
301
 * @param int    $from      unix timestamp
302
 * @param int    $to        unix timestamp
303
 * @uses vcalendar::getProperty()
304
 * @uses iCalUtilityFunctions::$fmt
305
 * @uses vcalendar::newComponent()
306
 * @uses calendarComponent::setproperty()
307
 * @uses iCalUtilityFunctions::offsetSec2His()
308
 * @return bool
309
 */
310
  public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
311
    if( empty( $timezone ))
312
      return FALSE;
313
    if( !empty( $from ) && !is_int( $from ))
314
      return FALSE;
315
    if( !empty( $to )   && !is_int( $to ))
316
      return FALSE;
317
    try {
318
      $dtz               = new DateTimeZone( $timezone );
319
      $transitions       = $dtz->getTransitions();
320
      $utcTz             = new DateTimeZone( 'UTC' );
321
    }
322
    catch( Exception $e ) { return FALSE; }
323
    if( empty( $from ) || empty( $to )) {
324
      $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
325
      if( empty( $dates ))
326
        $dates           = array( date( 'Ymd' ));
327
    }
328
    if( ! empty( $from )) {
329
      $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC)
330
      $dateFrom->modify( '-7 month' );                           // set $dateFrom to seven month before the lowest date
331
    }
332
    else {
333
      $from              = reset( $dates );                      // set lowest date to the lowest dtstart date
334
      $dateFrom          = new DateTime( $from.'T000000', $dtz );
335
      $dateFrom->modify( '-7 month' );                           // set $dateFrom to seven month before the lowest date
336
      $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC
337
    }
338
    $dateFromYmd         = $dateFrom->format( iCalUtilityFunctions::$fmt['Ymd2'] );
339
    if( ! empty( $to ))
340
      $dateTo            = new DateTime( "@$to" );               // set end date (UTC)
341
    else {
342
      $to                = end( $dates );                        // set highest date to the highest dtstart date
343
      $dateTo            = new DateTime( $to.'T235959', $dtz );
344
      $dateTo->modify( '+18 month' );                            // set $dateTo to 18 month after the highest date
345
      $dateTo->setTimezone( $utcTz );                            // convert local date to UTC
346
    }
347
    $dateToYmd           = $dateTo->format( iCalUtilityFunctions::$fmt['Ymd2'] );
348
    unset( $dtz );
349
    $transTemp           = array();
350
    $prevOffsetfrom      = 0;
351
    $stdIx  = $dlghtIx   = null;
352
    $prevTrans           = FALSE;
353
    foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
354
      $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC)
355
      $transDateYmd      = $date->format( iCalUtilityFunctions::$fmt['Ymd2'] );
356
      if ( $transDateYmd < $dateFromYmd ) {
357
        $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
358
        $prevTrans       = $trans;                               // save it in case we don't find any that match
359
        $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
360
        continue;
361
      }
362
      if( $transDateYmd > $dateToYmd )
363
        break;                                                   // loop always (?) breaks here
364
      if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
365
        $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
366
        $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
367
        $d               = $date->format( iCalUtilityFunctions::$fmt['YmdHis3'] );
368
        $d               = explode( '-', $d );                   // set date to array to ease up dtstart and (opt) rdate setting
369
        $trans['time']   = array( 'year' => (int) $d[0], 'month' => (int) $d[1], 'day' => (int) $d[2], 'hour' => (int) $d[3], 'min' => (int) $d[4], 'sec' => (int) $d[5] );
370
      }
371
      $prevOffsetfrom    = $trans['offset'];
372
      if( TRUE !== $trans['isdst'] ) {                           // standard timezone
373
        if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order)
374
           ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        &&
375
           ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  &&
376
           ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) {
377
          $transTemp[$stdIx]['rdate'][]        =   $trans['time'];
378
          continue;
379
        }
380
        $stdIx           = $tix;
381
      } // end standard timezone
382
      else {                                                     // daylight timezone
383
        if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
384
           ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         &&
385
           ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   &&
386
           ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) {
387
          $transTemp[$dlghtIx]['rdate'][]        =   $trans['time'];
388
          continue;
389
        }
390
        $dlghtIx         = $tix;
391
      } // end daylight timezone
392
      $transTemp[$tix]   = $trans;
393
    } // end foreach( $transitions as $tix => $trans )
394
    $tz                  = $calendar->newComponent( 'vtimezone' );
395
    $tz->setproperty( 'tzid', $timezone );
396
    if( !empty( $xProp )) {
397
      foreach( $xProp as $xPropName => $xPropValue )
398
        if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
399
          $tz->setproperty( $xPropName, $xPropValue );
400
    }
401
    if( empty( $transTemp )) {      // if no match found
402
      if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info
403
        $date            = new DateTime( "@{$prevTrans['ts']}" );// set transition date (UTC)
404
        $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date
405
        $d               = $date->format( iCalUtilityFunctions::$fmt['YmdHis3'] );
406
        $d               = explode( '-', $d );                   // set date to array to ease up dtstart setting
407
        $prevTrans['time'] = array( 'year' => (int) $d[0], 'month' => (int) $d[1], 'day' => (int) $d[2], 'hour' => (int) $d[3], 'min' => (int) $d[4], 'sec' => (int) $d[5] );
408
        $transTemp[0] = $prevTrans;
409
      }
410
      else {                        // or we use the timezone identifier to BUILD the standard tz info (?)
411
        $date            = new DateTime( 'now', new DateTimeZone( $timezone ));
412
        $transTemp[0]    = array( 'time'       => $date->format( iCalUtilityFunctions::$fmt['YmdTHisO'] ),
413
                                  'offset'     => $date->format( 'Z' ),
414
                                  'offsetfrom' => $date->format( 'Z' ),
415
                                  'isdst'      => FALSE );
416
      }
417
    }
418
    unset( $transitions, $date, $prevTrans );
419
    foreach( $transTemp as $tix => $trans ) { // create standard/daylight subcomponents
420
      $type              = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
421
      $scomp             = $tz->newComponent( $type );
422
      $scomp->setProperty( 'dtstart',         $trans['time'] );
423
//      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ###
424
      if( !empty( $trans['abbr'] ))
425
        $scomp->setProperty( 'tzname',        $trans['abbr'] );
426
      if( isset( $trans['offsetfrom'] ))
427
        $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
428
      $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
429
      if( isset( $trans['rdate'] ))
430
        $scomp->setProperty( 'RDATE',         $trans['rdate'] );
431
    }
432
    return TRUE;
433
  }
434
/**
435
 * creates formatted output for calendar component property data value type date/date-time
436
 *
437
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
438
 * @since 2.21.11 - 2015-03-10
439
 * @param array   $datetime
440
 * @param int     $parno     optional, default 6
441
 * @uses iCalUtilityFunctions::$fmt
442
 * @uses iCalUtilityFunctions::_isOffset()
443
 * @uses iCalUtilityFunctions::_tz2offset()
444
 * @return string
445
 */
446
  public static function _date2strdate( $datetime, $parno=6 ) {
447
    if( !isset( $datetime['year'] )  &&
448
        !isset( $datetime['month'] ) &&
449
        !isset( $datetime['day'] )   &&
450
        !isset( $datetime['hour'] )  &&
451
        !isset( $datetime['min'] )   &&
452
        !isset( $datetime['sec'] ))
453
      return;
454
    $output     = null;
455
    foreach( $datetime as $dkey => & $dvalue )
456
      if( 'tz' != $dkey ) $dvalue = (int) $dvalue;
457
    $output     = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $datetime['year'], $datetime['month'], $datetime['day'] );
458
    if( 3 == $parno )
459
      return $output;
460
    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
461
    if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
462
    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
463
    $output    .= 'T'.sprintf( iCalUtilityFunctions::$fmt['His'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
464
    if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
465
      $datetime['tz'] = trim( $datetime['tz'] );
466
      if( 'Z'  == $datetime['tz'] )
467
        $parno  = 7;
468
      elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
469
        $parno  = 7;
470
        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
471
        try {
472
          $d    = new DateTime( $output, new DateTimeZone( 'UTC' ));
473
          if( 0 != $offset ) // adjust för offset
474
            $d->modify( "$offset seconds" );
475
          $output = $d->format( 'Ymd\THis' );
476
        }
477
        catch( Exception $e ) {
478
          $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
479
        }
480
      }
481
      if( 7 == $parno )
482
        $output .= 'Z';
483
    }
484
    return $output;
485
  }
486
/**
487
 * ensures internal duration format for input in array format
488
 *
489
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
490
 * @since 2.19.4 - 2014-03-14
491
 * @param array $duration
492
 * @return array
493
 */
494
  public static function _duration2arr( $duration ) {
495
    $seconds        = 0;
496
    foreach( $duration as $durKey => $durValue ) {
497
      if( empty( $durValue )) continue;
498
      switch ( $durKey ) {
499
        case '0': case 'week':
500
          $seconds += (((int) $durValue ) * 60 * 60 * 24 * 7 );
501
          break;
502
        case '1': case 'day':
503
          $seconds += (((int) $durValue ) * 60 * 60 * 24 );
504
          break;
505
        case '2': case 'hour':
506
          $seconds += (((int) $durValue ) * 60 * 60 );
507
          break;
508
        case '3': case 'min':
509
          $seconds += (((int) $durValue ) * 60 );
510
          break;
511
        case '4': case 'sec':
512
          $seconds +=   (int) $durValue;
513
          break;
514
      }
515
    }
516
    $output         = array();
517
    $output['week'] = (int) floor( $seconds / ( 60 * 60 * 24 * 7 ));
518
    if(( 0 < $output['week'] ) && ( 0 == ( $seconds % ( 60 * 60 * 24 * 7 ))))
519
      return $output;
520
    unset( $output['week'] );
521
    $output['day']  = (int) floor( $seconds / ( 60 * 60 * 24 ));
522
    $seconds        =            ( $seconds % ( 60 * 60 * 24 ));
523
    $output['hour'] = (int) floor( $seconds / ( 60 * 60 ));
524
    $seconds        =            ( $seconds % ( 60 * 60 ));
525
    $output['min']  = (int) floor( $seconds /   60 );
526
    $output['sec']  =            ( $seconds %   60 );
527
    if( empty( $output['day'] ))
528
      unset( $output['day'] );
529
    if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
530
      unset( $output['hour'], $output['min'], $output['sec'] );
531
    return $output;
532
  }
533
/**
534
 * convert startdate+duration to a array format datetime
535
 *
536
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
537
 * @since 2.21.11 - 2015-03-21
538
 * @param array   $startdate
539
 * @param array   $duration
540
 * @uses iCalUtilityFunctions::$fmt
541
 * @return array, date format
542
 */
543
  public static function _duration2date( $startdate, $duration ) {
544
    $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
545
    $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
546
    $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
547
    $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
548
    $dtend = 0;
549
    if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
550
    if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 );
551
    if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
552
    if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 );
553
    if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec'];
554
    $date     = date( iCalUtilityFunctions::$fmt['YmdHis3'],
555
                      mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
556
    $d        = explode( '-', $date );
557
    $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
558
    if( isset( $startdate['tz'] ))
559
      $dtend2['tz']   = $startdate['tz'];
560
    if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
561
      unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
562
    return $dtend2;
563
  }
564
/**
565
 * ensures internal duration format for an input string (iCal) formatted duration
566
 *
567
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
568
 * @since 2.14.1 - 2012-09-25
569
 * @param string $duration
570
 * @uses iCalUtilityFunctions::_duration2arr()
571
 * @return array
572
 */
573
  public static function _durationStr2arr( $duration ) {
574
    $duration = (string) trim( $duration );
575
    while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
576
      if( 0 < strlen( $duration ))
577
        $duration = substr( $duration, 1 );
578
      else
579
        return false; // no leading P !?!?
580
    }
581
    $duration = substr( $duration, 1 ); // skip P
582
    $duration = str_replace ( 't', 'T', $duration );
583
    $duration = str_replace ( 'T', '', $duration );
584
    $output = array();
585
    $val    = null;
586
    for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
587
      switch( strtoupper( substr( $duration, $ix, 1 ))) {
588
       case 'W':
589
         $output['week'] = $val;
590
         $val            = null;
591
         break;
592
       case 'D':
593
         $output['day']  = $val;
594
         $val            = null;
595
         break;
596
       case 'H':
597
         $output['hour'] = $val;
598
         $val            = null;
599
         break;
600
       case 'M':
601
         $output['min']  = $val;
602
         $val            = null;
603
         break;
604
       case 'S':
605
         $output['sec']  = $val;
606
         $val            = null;
607
         break;
608
       default:
609
         if( !ctype_digit( substr( $duration, $ix, 1 )))
610
           return false; // unknown duration control character  !?!?
611
         else
612
           $val .= substr( $duration, $ix, 1 );
613
      }
614
    }
615
    return iCalUtilityFunctions::_duration2arr( $output );
616
  }
617
/**
618
 * creates formatted output for calendar component property data value type duration
619
 *
620
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
621
 * @since 2.15.8 - 2012-10-30
622
 * @param array $duration, array( week, day, hour, min, sec )
623
 * @return string
624
 */
625
  public static function _duration2str( array $duration ) {
626
    if( isset( $duration['week'] ) ||
627
        isset( $duration['day'] )  ||
628
        isset( $duration['hour'] ) ||
629
        isset( $duration['min'] )  ||
630
        isset( $duration['sec'] ))
631
       $ok = TRUE;
632
    else
633
      return;
634
    if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
635
      return 'P'.$duration['week'].'W';
636
    $output = 'P';
637
    if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
638
      $output .= $duration['day'].'D';
639
    if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
640
       ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
641
       ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) {
642
      $output .= 'T';
643
      $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
644
      $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M';
645
      $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S';
646
    }
647
    if( 'P' == $output )
648
      $output = 'PT0H0M0S';
649
    return $output;
650
  }
651
/**
652
 * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
653
 *
654
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
655
 * @since 2.4.16 - 2008-11-08
656
 * @param array  $array    iCal property parameters
657
 * @param string $expkey   expected key
658
 * @param string $expval   expected value
659
 * @param int    $hitVal   return value if found
660
 * @param int    $elseVal  return value if not found
661
 * @param int    $preSet   return value if already preset
662
 * @return int
663
 */
664
  public static function _existRem( & $array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
665
    if( $preSet )
666
      return $preSet;
667
    if( !is_array( $array ) || ( 0 == count( $array )))
668
      return $elseVal;
669
    foreach( $array as $key => $value ) {
670
      if( strtoupper( $expkey ) == strtoupper( $key )) {
671
        if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
672
          unset( $array[$key] );
673
          return $hitVal;
674
        }
675
      }
676
    }
677
    return $elseVal;
678
  }
679
/**
680
 * check if dates are in scope
681
 *
682
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
683
 * @since 2.21.7 - 2015-03-25
684
 * @param object $start       datetime
685
 * @param object $scopeStart  datetime
686
 * @param object $end         datetime
687
 * @param object $scopeEnd    datetime
688
 * @param string $format
689
 * @return bool
690
 */
691
  public static function _inScope( $start, $scopeStart, $end, $scopeEnd, $format ) {
692
    return (( $start->format( $format ) >= $scopeStart->format( $format )) &&
693
            ( $end->format( $format )   <= $scopeEnd->format( $format )));
694
}
695
/**
696
 * mgnt geo part output
697
 *
698
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
699
 * @since 2.8.10 - 2013-09-02
700
 * @param float $ll
701
 * @param string $format
702
 * @return string
703
 */
704
  public static function _geo2str2( $ll, $format ) {
705
    if( 0.0 < $ll )
706
      $sign   = '+';
707
    else
708
      $sign   = ( 0.0 > $ll ) ? '-' : '';
709
    return rtrim( rtrim( $sign.sprintf( $format, abs( $ll )), '0' ), '.' );
710
  }
711
/**
712
 * checks if input contains a (array formatted) date/time
713
 *
714
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
715
 * @since 2.16.24 - 2013-07-02
716
 * @param array $input
717
 * @uses iCalUtilityFunctions::_strdate2date()
718
 * @return bool
719
 */
720
  public static function _isArrayDate( $input ) {
721
    if( !is_array( $input ) || isset( $input['week'] ) || isset( $input['timestamp'] ) || ( 3 > count( $input )))
722
      return FALSE;
723
    if( 7 == count( $input ))
724
      return TRUE;
725
    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
726
      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
727
    if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
728
      return FALSE;
729
    if(( 0 == $input[0] ) || ( 0 == $input[1] ) || ( 0 == $input[2] ))
730
      return FALSE;
731
    if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
732
      return FALSE;
733
    if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
734
         checkdate((int) $input[1], (int) $input[2], (int) $input[0] ))
735
      return TRUE;
736
    $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
737
    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
738
      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
739
    return FALSE;
740
  }
741
/**
742
 * checks if input array contains a timestamp date
743
 *
744
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
745
 * @since 2.4.16 - 2008-10-18
746
 * @param array $input
747
 * @return bool
748
 */
749
  public static function _isArrayTimestampDate( $input ) {
750
    return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
751
  }
752
/**
753
 * controls if input string contains (trailing) UTC/iCal offset
754
 *
755
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
756
 * @since 2.14.1 - 2012-09-21
757
 * @param string $input
758
 * @return bool
759
 */
760
  public static function _isOffset( $input ) {
761
    $input         = trim( (string) $input );
762
    if( 'Z' == substr( $input, -1 ))
763
      return TRUE;
764
    elseif((   5 <= strlen( $input )) &&
765
       ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
766
       (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
767
      return TRUE;
768
    elseif((    7 <= strlen( $input )) &&
769
       ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
770
       ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
771
      return TRUE;
772
    return FALSE;
773
  }
774
/**
775
 * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
776
 * matching (MS) UCT offset and time zone descriptors
777
 *
778
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
779
 * @since 2.14.1 - 2012-09-16
780
 * @param string $timezone     to convert
781
 * @uses iCalUtilityFunctions::_tz2offset()
782
 * @return bool
783
 */
784
  public static function ms2phpTZ( & $timezone ) {
785
    if( empty( $timezone ))
786
      return FALSE;
787
    $search = str_replace( '"', '', $timezone );
788
    $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
789
    if( '(UTC' != substr( $search, 0, 4 ))
790
      return FALSE;
791
    if( FALSE === ( $pos = strpos( $search, ')' )))
792
      return FALSE;
793
    $pos    = strpos( $search, ')' );
794
    $searchOffset = substr( $search, 4, ( $pos - 4 ));
795
    $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
796
    while( ' ' ==substr( $search, ( $pos + 1 )))
797
      $pos += 1;
798
    $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
799
    $searchWords  = explode( ' ', $searchText );
800
    $timezone_abbreviations = DateTimeZone::listAbbreviations();
801
    $hits = array();
802
    foreach( $timezone_abbreviations as $name => $transitions ) {
803
      foreach( $transitions as $cnt => $transition ) {
804
        if( empty( $transition['offset'] )      ||
805
            empty( $transition['timezone_id'] ) ||
806
          ( $transition['offset'] != $searchOffset ))
807
        continue;
808
        $cWords = explode( '/', $transition['timezone_id'] );
809
        $cPrio   = $hitCnt = $rank = 0;
810
        foreach( $cWords as $cWord ) {
811
          if( empty( $cWord ))
812
            continue;
813
          $cPrio += 1;
814
          $sPrio  = 0;
815
          foreach( $searchWords as $sWord ) {
816
            if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
817
              continue;
818
            $sPrio += 1;
819
            if( strtolower( $cWord ) == strtolower( $sWord )) {
820
              $hitCnt += 1;
821
              $rank   += ( $cPrio + $sPrio );
822
            }
823
            else
824
              $rank += 10;
825
          }
826
        }
827
        if( 0 < $hitCnt ) {
828
          $hits[$rank][] = $transition['timezone_id'];
829
        }
830
      }
831
    }
832
    unset( $timezone_abbreviations );
833
    if( empty( $hits ))
834
      return FALSE;
835
    ksort( $hits );
836
    foreach( $hits as $rank => $tzs ) {
837
      if( !empty( $tzs )) {
838
        $timezone = reset( $tzs );
839
        return TRUE;
840
      }
841
    }
842
    return FALSE;
843
  }
844
/**
845
 * transforms offset in seconds to [-/+]hhmm[ss]
846
 *
847
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
848
 * @since 2011-05-02
849
 * @param string $seconds
850
 * @return string
851
 */
852
  public static function offsetSec2His( $seconds ) {
853
    if( '-' == substr( $seconds, 0, 1 )) {
854
      $prefix  = '-';
855
      $seconds = substr( $seconds, 1 );
856
    }
857
    elseif( '+' == substr( $seconds, 0, 1 )) {
858
      $prefix  = '+';
859
      $seconds = substr( $seconds, 1 );
860
    }
861
    else
862
      $prefix  = '+';
863
    $output  = '';
864
    $hour    = (int) floor( $seconds / 3600 );
865
    if( 10 > $hour )
866
      $hour  = '0'.$hour;
867
    $seconds = $seconds % 3600;
868
    $min     = (int) floor( $seconds / 60 );
869
    if( 10 > $min )
870
      $min   = '0'.$min;
871
    $output  = $hour.$min;
872
    $seconds = $seconds % 60;
873
    if( 0 < $seconds) {
874
      if( 9 < $seconds)
875
        $output .= $seconds;
876
      else
877
        $output .= '0'.$seconds;
878
    }
879
    return $prefix.$output;
880
  }
881
/**
882
 * updates an array with dates based on a recur pattern
883
 *
884
 * if missing, UNTIL is set 1 year from startdate (emergency break)
885
 *
886
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
887
 * @since 2.21.11 - 2015-03-10
888
 * @param array $result    array to update, array([Y-m-d] => bool)
889
 * @param array $recur     pattern for recurrency (only value part, params ignored)
890
 * @param mixed $wdate     component start date, string / array / (datetime) obj
891
 * @param mixed $fcnStart  start date, string / array / (datetime) obj
892
 * @param mixed $fcnEnd    end date, string / array / (datetime) obj
893
 * @uses iCalUtilityFunctions::_strDate2arr()
894
 * @uses iCalUtilityFunctions::$fmt
895
 * @uses iCalUtilityFunctions::_stepdate()
896
 * @uses iCalUtilityFunctions::_recurIntervalIx()
897
 * @uses iCalUtilityFunctions::_recurBYcntcheck()
898
 * @return void
899
 * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start OR not at all
900
 */
901
  public static function _recur2date( & $result, $recur, $wdate, $fcnStart, $fcnEnd=FALSE ) {
902
    if( is_string( $wdate ))
903
      iCalUtilityFunctions::_strDate2arr( $wdate );
904
    elseif( is_a( $wdate, 'DateTime' )) {
905
      $wdate = $wdate->format( iCalUtilityFunctions::$fmt['YmdHis2'] );
906
      iCalUtilityFunctions::_strDate2arr( $wdate );
907
    }
908
    foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
909
    $wdateYMD     = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $wdate['year'], $wdate['month'], $wdate['day'] );
910
    $wdateHis     = sprintf( iCalUtilityFunctions::$fmt['His'], $wdate['hour'], $wdate['min'],   $wdate['sec'] );
911
    $untilHis     = $wdateHis;
912
    if( is_string( $fcnStart ))
913
      iCalUtilityFunctions::_strDate2arr( $fcnStart );
914
    elseif( is_a( $fcnStart, 'DateTime' )) {
915
      $fcnStart = $fcnStart->format( iCalUtilityFunctions::$fmt['YmdHis2'] );
916
      iCalUtilityFunctions::_strDate2arr( $fcnStart );
917
    }
918
    foreach( $fcnStart as $k => $v ) if( ctype_digit( $v )) $fcnStart[$k] = (int) $v;
919
    $fcnStartYMD = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $fcnStart['year'], $fcnStart['month'], $fcnStart['day'] );
920
    if( is_string( $fcnEnd ))
921
      iCalUtilityFunctions::_strDate2arr( $fcnEnd );
922
    elseif( is_a( $fcnEnd, 'DateTime' )) {
923
      $fcnEnd = $fcnEnd->format( iCalUtilityFunctions::$fmt['YmdHis2'] );
924
      iCalUtilityFunctions::_strDate2arr( $fcnEnd );
925
    }
926
    if( !$fcnEnd ) {
927
      $fcnEnd = $fcnStart;
928
      $fcnEnd['year'] += 1;
929
    }
930
    foreach( $fcnEnd as $k => $v ) if( ctype_digit( $v )) $fcnEnd[$k] = (int) $v;
931
    $fcnEndYMD = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $fcnEnd['year'], $fcnEnd['month'], $fcnEnd['day'] );
932
// echo "<b>recur _in_ comp</b> start ".implode('-',$wdate)." period start ".implode('-',$fcnStart)." period end ".implode('-',$fcnEnd)."<br>\n";
933
// echo 'recur='.str_replace( array( PHP_EOL, ' ' ), '', var_export( $recur, TRUE ))."<br> \n"; // test ###
934
    if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
935
      $recur['UNTIL'] = $fcnEnd; // create break
936
    if( isset( $recur['UNTIL'] )) {
937
      foreach( $recur['UNTIL'] as $k => $v ) if( ctype_digit( $v )) $recur['UNTIL'][$k] = (int) $v;
938
      unset( $recur['UNTIL']['tz'] );
939
      if( $fcnEnd > $recur['UNTIL'] ) {
940
        $fcnEnd = $recur['UNTIL']; // emergency break
941
        $fcnEndYMD = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $fcnEnd['year'], $fcnEnd['month'], $fcnEnd['day'] );
942
      }
943
      if( isset( $recur['UNTIL']['hour'] ))
944
        $untilHis  = sprintf( iCalUtilityFunctions::$fmt['His'], $recur['UNTIL']['hour'], $recur['UNTIL']['min'], $recur['UNTIL']['sec'] );
945
      else
946
        $untilHis  = sprintf( iCalUtilityFunctions::$fmt['His'], 23, 59, 59 );
947
// echo 'recurUNTIL='.str_replace( array( PHP_EOL, ' ' ), '', var_export( $recur['UNTIL'], TRUE )).", untilHis={$untilHis}<br> \n"; // test ###
948
    }
949
// echo 'fcnEnd:'.$fcnEndYMD.$untilHis."<br>\n";//test
950
    if( $wdateYMD > $fcnEndYMD ) {
951
// echo 'recur out of date, '.implode('-',$wdate).', end='.implode('-',$fcnEnd)."<br>\n";//test
952
      return array(); // nothing to do.. .
953
    }
954
    if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
955
      $recur['FREQ'] = 'DAILY'; // ??
956
    $wkst         = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
957
    if( !isset( $recur['INTERVAL'] ))
958
      $recur['INTERVAL'] = 1;
959
    $countcnt     = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
960
            /* find out how to step up dates and set index for interval count */
961
    $step = array();
962
    if( 'YEARLY' == $recur['FREQ'] )
963
      $step['year']  = 1;
964
    elseif( 'MONTHLY' == $recur['FREQ'] )
965
      $step['month'] = 1;
966
    elseif( 'WEEKLY' == $recur['FREQ'] )
967
      $step['day']   = 7;
968
    else
969
      $step['day']   = 1;
970
    if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
971
      $step = array( 'month' => 1 );
972
    if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
973
      $step = array( 'day' => 7 );
974
    if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
975
      $step = array( 'day' => 1 );
976
    $intervalarr = array();
977
    if( 1 < $recur['INTERVAL'] ) {
978
      $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
979
      $intervalarr = array( $intervalix => 0 );
980
    }
981
    if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
982
      $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
983
// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br>\n"; // test ###
984
      if( is_array( $recur['BYSETPOS'] )) {
985
        foreach( $recur['BYSETPOS'] as $bix => $bval )
986
          $recur['BYSETPOS'][$bix] = (int) $bval;
987
      }
988
      else
989
        $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
990
      if( 'YEARLY' == $recur['FREQ'] ) {
991
        $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
992
        $wdateYMD = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $wdate['year'], $wdate['month'], $wdate['day'] );
993
        iCalUtilityFunctions::_stepdate( $fcnEnd, $fcnEndYMD, array( 'year' => 1 )); // make sure to count whole last year
994
      }
995
      elseif( 'MONTHLY' == $recur['FREQ'] ) {
996
        $wdate['day']   = 1; // start from beginning of month
997
        $wdateYMD = sprintf( iCalUtilityFunctions::$fmt['Ymd'], $wdate['year'], $wdate['month'], $wdate['day'] );
998
        iCalUtilityFunctions::_stepdate( $fcnEnd, $fcnEndYMD, array( 'month' => 1 )); // make sure to count whole last month
999
      }
1000
      else
1001
        iCalUtilityFunctions::_stepdate( $fcnEnd, $fcnEndYMD, $step); // make sure to count whole last period
1002
// echo "BYSETPOS endDat =".implode('-',$fcnEnd).' step='.var_export($step,TRUE)."<br>\n";//test###
1003
      $bysetposWold = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year'] ));
1004
      $bysetposYold = $wdate['year'];
1005
      $bysetposMold = $wdate['month'];
1006
      $bysetposDold = $wdate['day'];
1007
    }
1008
    else
1009
      iCalUtilityFunctions::_stepdate( $wdate, $wdateYMD, $step);
1010
    $year_old      = null;
1011
    static $daynames = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
1012
             /* MAIN LOOP */
1013
// echo "recur start:$wdateYMD, end:$fcnEndYMD<br>\n";//test
1014
    while( TRUE ) {
1015
// echo "recur while:$wdateYMD, end:$fcnEndYMD<br>\n";//test
1016
      if( $wdateYMD.$wdateHis > $fcnEndYMD.$untilHis )
1017
        break;
1018
      if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
1019
        break;
1020
      if( $year_old != $wdate['year'] ) {
1021
        $year_old   = $wdate['year'];
1022
        $daycnts    = array();
1023
        $yeardays   = $weekno = 0;
1024
        $yeardaycnt = array();
1025
        foreach( $daynames as $dn )
1026
          $yeardaycnt[$dn] = 0;
1027
        for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
1028
          $daycnts[$m] = array();
1029
          $weekdaycnt = array();
1030
          foreach( $daynames as $dn )
1031
            $weekdaycnt[$dn] = 0;
1032
          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
1033
          for( $d   = 1; $d <= $mcnt; $d++ ) {
1034
            $daycnts[$m][$d] = array();
1035
            if( isset( $recur['BYYEARDAY'] )) {
1036
              $yeardays++;
1037
              $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
1038
            }
1039
            if( isset( $recur['BYDAY'] )) {
1040
              $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
1041
              $day    = $daynames[$day];
1042
              $daycnts[$m][$d]['DAY'] = $day;
1043
              $weekdaycnt[$day]++;
1044
              $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
1045
              $yeardaycnt[$day]++;
1046
              $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
1047
            }
1048
            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
1049
              $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
1050
          } // end for( $d   = 1; $d <= $mcnt; $d++ )
1051
        } // end for( $m = 1; $m <= 12; $m++ )
1052
        $daycnt = 0;
1053
        $yeardaycnt = array();
1054
        if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
1055
          $weekno = null;
1056
          for( $d=31; $d > 25; $d-- ) { // get last weekno for year
1057
            if( !$weekno )
1058
              $weekno = $daycnts[12][$d]['weekno_up'];
1059
            elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
1060
              $weekno = $daycnts[12][$d]['weekno_up'];
1061
              break;
1062
            }
1063
          }
1064
        }
1065
        for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
1066
          $weekdaycnt = array();
1067
          foreach( $daynames as $dn )
1068
            $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
1069
          $monthcnt = 0;
1070
          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
1071
          for( $d   = $mcnt; $d > 0; $d-- ) {
1072
            if( isset( $recur['BYYEARDAY'] )) {
1073
              $daycnt -= 1;
1074
              $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
1075
            }
1076
            if( isset( $recur['BYMONTHDAY'] )) {
1077
              $monthcnt -= 1;
1078
              $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
1079
            }
1080
            if( isset( $recur['BYDAY'] )) {
1081
              $day  = $daycnts[$m][$d]['DAY'];
1082
              $weekdaycnt[$day] -= 1;
1083
              $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
1084
              $yeardaycnt[$day] -= 1;
1085
              $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
1086
            }
1087
            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
1088
              $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
1089
          }
1090
        } // end for( $m = 12; $m > 0; $m-- )
1091
      } // end if( $year_old != $wdate['year'] )
1092
            /* check interval */
1093
      if( 1 < $recur['INTERVAL'] ) {
1094
            /* create interval index */
1095
        $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
1096
            /* check interval */
1097
        $currentKey = array_keys( $intervalarr );
1098
        $currentKey = end( $currentKey ); // get last index
1099
        if( $currentKey != $intervalix )
1100
          $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
1101
        if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
1102
           ( 0 != $intervalarr[$intervalix] )) {
1103
            /* step up date */
1104
// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
1105
          iCalUtilityFunctions::_stepdate( $wdate, $wdateYMD, $step);
1106
          continue;
1107
        }
1108
        else // continue within the selected interval
1109
          $intervalarr[$intervalix] = 0;
1110
// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
1111
      } // endif( 1 < $recur['INTERVAL'] )
1112
      $updateOK = TRUE;
1113
      if( $updateOK && isset( $recur['BYMONTH'] ))
1114
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
1115
                                           , $wdate['month']
1116
                                           ,($wdate['month'] - 13));
1117
      if( $updateOK && isset( $recur['BYWEEKNO'] ))
1118
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
1119
                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
1120
                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
1121
      if( $updateOK && isset( $recur['BYYEARDAY'] ))
1122
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
1123
                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
1124
                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
1125
      if( $updateOK && isset( $recur['BYMONTHDAY'] ))
1126
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
1127
                                           , $wdate['day']
1128
                                           , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
1129
// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n";//test###
1130
      if( $updateOK && isset( $recur['BYDAY'] )) {
1131
        $updateOK = FALSE;
1132
        $m = $wdate['month'];
1133
        $d = $wdate['day'];
1134
        if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
1135
          $daynoexists = $daynosw = $daynamesw =  FALSE;
1136
          if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
1137
            $daynamesw = TRUE;
1138
          if( isset( $recur['BYDAY'][0] )) {
1139
            $daynoexists = TRUE;
1140
            if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
1141
              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
1142
                                                , $daycnts[$m][$d]['monthdayno_up']
1143
                                                , $daycnts[$m][$d]['monthdayno_down'] );
1144
            elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
1145
              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
1146
                                                , $daycnts[$m][$d]['yeardayno_up']
1147
                                                , $daycnts[$m][$d]['yeardayno_down'] );
1148
          }
1149
          if((  $daynoexists &&  $daynosw && $daynamesw ) ||
1150
             ( !$daynoexists && !$daynosw && $daynamesw )) {
1151
            $updateOK = TRUE;
1152
// 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 ###
1153
          }
1154
// 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 ###
1155
        }
1156
        else {
1157
          foreach( $recur['BYDAY'] as $bydayvalue ) {
1158
            $daynoexists = $daynosw = $daynamesw = FALSE;
1159
            if( isset( $bydayvalue['DAY'] ) &&
1160
                     ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
1161
              $daynamesw = TRUE;
1162
            if( isset( $bydayvalue[0] )) {
1163
              $daynoexists = TRUE;
1164
              if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
1165
                   isset( $recur['BYMONTH'] ))
1166
                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
1167
                                                  , $daycnts[$m][$d]['monthdayno_up']
1168
                                                  , $daycnts[$m][$d]['monthdayno_down'] );
1169
              elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
1170
                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
1171
                                                  , $daycnts[$m][$d]['yeardayno_up']
1172
                                                  , $daycnts[$m][$d]['yeardayno_down'] );
1173
            }
1174
// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br>\n"; // test ###
1175
            if((  $daynoexists &&  $daynosw && $daynamesw ) ||
1176
               ( !$daynoexists && !$daynosw && $daynamesw )) {
1177
              $updateOK = TRUE;
1178
              break;
1179
            }
1180
          }
1181
        }
1182
      }
1183
// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n"; // test ###
1184
            /* check BYSETPOS */
1185
      if( $updateOK ) {
1186
        if( isset( $recur['BYSETPOS'] ) &&
1187
          ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
1188
          if( isset( $recur['WEEKLY'] )) {
1189
            if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
1190
              $bysetposw1[] = $wdateYMD;
1191
            else
1192
              $bysetposw2[] = $wdateYMD;
1193
          }
1194
          else {
1195
            if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
1196
                                            ( $bysetposYold == $wdate['year'] ))   ||
1197
               ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
1198
                                           (( $bysetposYold == $wdate['year'] )  &&
1199
                                            ( $bysetposMold == $wdate['month'] ))) ||
1200
               ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
1201
                                           (( $bysetposYold == $wdate['year'] )  &&
1202
                                            ( $bysetposMold == $wdate['month'])  &&
1203
                                            ( $bysetposDold == $wdate['day'] )))) {
1204
// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
1205
              $bysetposymd1[] = $wdateYMD;
1206
            }
1207
            else {
1208
// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
1209
              $bysetposymd2[] = $wdateYMD;
1210
            }
1211
          }
1212
        }
1213
        else {
1214
          if( checkdate($wdate['month'], $wdate['day'], $wdate['year'] )) {
1215
            /* update result array if BYSETPOS is not set */
1216
            $countcnt++;
1217
            if( $fcnStartYMD <= $wdateYMD ) { // only output within period
1218
              $result[$wdateYMD] = TRUE;
1219
// echo "recur $wdateYMD<br>\n";//test
1220
            }
1221
          }
1222
// else echo "recur, no date $wdateYMD<br>\n";//test
1223
          $updateOK = FALSE;
1224
        }
1225
      }
1226
            /* step up date */
1227
      iCalUtilityFunctions::_stepdate( $wdate, $wdateYMD, $step);
1228
            /* check if BYSETPOS is set for updating result array */
1229
      if( $updateOK && isset( $recur['BYSETPOS'] )) {
1230
        $bysetpos       = FALSE;
1231
        if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
1232
          ( $bysetposYold != $wdate['year'] )) {
1233
          $bysetpos     = TRUE;
1234
          $bysetposYold = $wdate['year'];
1235
        }
1236
        elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
1237
         (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
1238
          $bysetpos     = TRUE;
1239
          $bysetposYold = $wdate['year'];
1240
          $bysetposMold = $wdate['month'];
1241
        }
1242
        elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
1243
          $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
1244
          if( $bysetposWold != $weekno ) {
1245
            $bysetposWold = $weekno;
1246
            $bysetpos     = TRUE;
1247
          }
1248
        }
1249
        elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
1250
         (( $bysetposYold != $wdate['year'] )  ||
1251
          ( $bysetposMold != $wdate['month'] ) ||
1252
          ( $bysetposDold != $wdate['day'] ))) {
1253
          $bysetpos     = TRUE;
1254
          $bysetposYold = $wdate['year'];
1255
          $bysetposMold = $wdate['month'];
1256
          $bysetposDold = $wdate['day'];
1257
        }
1258
        if( $bysetpos ) {
1259
          if( isset( $recur['BYWEEKNO'] )) {
1260
            $bysetposarr1 = & $bysetposw1;
1261
            $bysetposarr2 = & $bysetposw2;
1262
          }
1263
          else {
1264
            $bysetposarr1 = & $bysetposymd1;
1265
            $bysetposarr2 = & $bysetposymd2;
1266
          }
1267

    
1268
          foreach( $recur['BYSETPOS'] as $ix ) {
1269
            if( 0 > $ix ) // both positive and negative BYSETPOS allowed
1270
              $ix = ( count( $bysetposarr1 ) + $ix + 1);
1271
            $ix--;
1272
            if( isset( $bysetposarr1[$ix] )) {
1273
              if( $fcnStartYMD <= $bysetposarr1[$ix] ) { // only output within period
1274
//                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, (int) substr( $bysetposarr1[$ix], 4, 2 ), (int) substr( $bysetposarr1[$ix], 6, 2 ), (int) substr( $bysetposarr1[$ix], 0, 3 ))); // test ###
1275
// echo " testYMD (weekno)=$bysetposarr1[$ix] ($testweekno)";   // test ###
1276
                $result[$bysetposarr1[$ix]] = TRUE;
1277
              }
1278
              $countcnt++;
1279
            }
1280
            if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
1281
              break;
1282
          }
1283
// echo "<br>\n"; // test ###
1284
          $bysetposarr1 = $bysetposarr2;
1285
          $bysetposarr2 = array();
1286
        } // end if( $bysetpos )
1287
      } // end if( $updateOK && isset( $recur['BYSETPOS'] ))
1288
    } // end while( TRUE )
1289
// echo 'output='.str_replace( array( PHP_EOL, ' ' ), '', var_export( $result, TRUE ))."<br> \n"; // test ###
1290
  }
1291
/**
1292
 * _recur2date help function, checking BYDAY (etc) hits
1293
 *
1294
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1295
 * @since 2.6.12 - 2011-01-03
1296
 * @param array $BYvalue
1297
 * @param int   $upValue
1298
 * @param int   $downValue
1299
 * @return bool
1300
 */
1301
  public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
1302
    if( is_array( $BYvalue ) &&
1303
      ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
1304
      return TRUE;
1305
    elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
1306
      return TRUE;
1307
    else
1308
      return FALSE;
1309
  }
1310
/**
1311
 * _recur2date help function, (re-)calculate internal index
1312
 *
1313
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1314
 * @since 2.6.12 - 2011-01-03
1315
 * @param string $freq
1316
 * @param array  $date
1317
 * @param int    $wkst
1318
 * @return bool
1319
 */
1320
  public static function _recurIntervalIx( $freq, $date, $wkst ) {
1321
            /* create interval index */
1322
    switch( $freq ) {
1323
      case 'YEARLY':
1324
        $intervalix = $date['year'];
1325
        break;
1326
      case 'MONTHLY':
1327
        $intervalix = $date['year'].'-'.$date['month'];
1328
        break;
1329
      case 'WEEKLY':
1330
        $intervalix = (int) date( 'W', mktime( 0, 0, $wkst, (int) $date['month'], (int) $date['day'], (int) $date['year'] ));
1331
       break;
1332
      case 'DAILY':
1333
           default:
1334
        $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
1335
        break;
1336
    }
1337
    return $intervalix;
1338
  }
1339
/**
1340
 * sort recur dates
1341
 *
1342
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1343
 * @since 2.6.12 - 2011-01-03
1344
 * @param array  $bydaya
1345
 * @param array  $bydayb
1346
 * @return int
1347
 */
1348
  public static function _recurBydaySort( $bydaya, $bydayb ) {
1349
    static $days = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 );
1350
    return ( $days[substr( $bydaya, -2 )] < $days[substr( $bydayb, -2 )] ) ? -1 : 1;
1351
  }
1352
/**
1353
 * convert input format for exrule and rrule to internal format
1354
 *
1355
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1356
 * @since 2.21.11 - 2015-03-10
1357
 * @param array $rexrule
1358
 * @uses iCalUtilityFunctions::_strDate2arr()
1359
 * @uses iCalUtilityFunctions::_isArrayTimestampDate()
1360
 * @uses iCalUtilityFunctions::_timestamp2date()
1361
 * @uses iCalUtilityFunctions::_chkDateArr()
1362
 * @uses iCalUtilityFunctions::_isOffset()
1363
 * @uses iCalUtilityFunctions::$fmt
1364
 * @uses iCalUtilityFunctions::_strdate2date()
1365
 * @return array
1366
 */
1367
  public static function _setRexrule( $rexrule ) {
1368
    $input          = array();
1369
    if( empty( $rexrule ))
1370
      return $input;
1371
    $rexrule        = array_change_key_case( $rexrule, CASE_UPPER );
1372
    foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
1373
      if( 'UNTIL'  != $rexrulelabel )
1374
        $input[$rexrulelabel]   = $rexrulevalue;
1375
      else {
1376
        iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
1377
        if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
1378
          $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
1379
        elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
1380
          $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
1381
          $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
1382
          if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
1383
            $strdate              = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $d['tz'] );
1384
            $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1385
            unset( $input[$rexrulelabel]['unparsedtext'] );
1386
          }
1387
          else
1388
           $input[$rexrulelabel] = $d;
1389
        }
1390
        elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
1391
          $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
1392
          unset( $input['$rexrulelabel']['unparsedtext'] );
1393
        }
1394
        if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
1395
          $input[$rexrulelabel]['tz'] = 'Z';
1396
      }
1397
    }
1398
            /* set recurrence rule specification in rfc2445 order */
1399
    $input2 = array();
1400
    if( isset( $input['FREQ'] ))
1401
      $input2['FREQ']       = $input['FREQ'];
1402
    if( isset( $input['UNTIL'] ))
1403
      $input2['UNTIL']      = $input['UNTIL'];
1404
    elseif( isset( $input['COUNT'] ))
1405
      $input2['COUNT']      = $input['COUNT'];
1406
    if( isset( $input['INTERVAL'] ))
1407
      $input2['INTERVAL']   = $input['INTERVAL'];
1408
    if( isset( $input['BYSECOND'] ))
1409
      $input2['BYSECOND']   = $input['BYSECOND'];
1410
    if( isset( $input['BYMINUTE'] ))
1411
      $input2['BYMINUTE']   = $input['BYMINUTE'];
1412
    if( isset( $input['BYHOUR'] ))
1413
      $input2['BYHOUR']     = $input['BYHOUR'];
1414
    if( isset( $input['BYDAY'] )) {
1415
      if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
1416
        $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
1417
      else {
1418
        foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
1419
          if( 'DAY'        == strtoupper( $BYDAYx ))
1420
             $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
1421
          elseif( !is_array( $BYDAYv )) {
1422
             $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
1423
          }
1424
          else {
1425
            foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
1426
              if( 'DAY'    == strtoupper( $BYDAYx2 ))
1427
                 $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
1428
              else
1429
                 $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
1430
            }
1431
          }
1432
        }
1433
      }
1434
    }
1435
    if( isset( $input['BYMONTHDAY'] ))
1436
      $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
1437
    if( isset( $input['BYYEARDAY'] ))
1438
      $input2['BYYEARDAY']  = $input['BYYEARDAY'];
1439
    if( isset( $input['BYWEEKNO'] ))
1440
      $input2['BYWEEKNO']   = $input['BYWEEKNO'];
1441
    if( isset( $input['BYMONTH'] ))
1442
      $input2['BYMONTH']    = $input['BYMONTH'];
1443
    if( isset( $input['BYSETPOS'] ))
1444
      $input2['BYSETPOS']   = $input['BYSETPOS'];
1445
    if( isset( $input['WKST'] ))
1446
      $input2['WKST']       = $input['WKST'];
1447
    return $input2;
1448
  }
1449
/**
1450
 * convert format for input date to internal date with parameters
1451
 *
1452
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1453
 * @since 2.21.11 - 2015-03-21
1454
 * @param mixed  $year
1455
 * @param mixed  $month   optional
1456
 * @param int    $day     optional
1457
 * @param int    $hour    optional
1458
 * @param int    $min     optional
1459
 * @param int    $sec     optional
1460
 * @param string $tz      optional
1461
 * @param array  $params  optional
1462
 * @param string $caller  optional
1463
 * @param string $objName optional
1464
 * @param string $tzid    optional
1465
 * @uses iCalUtilityFunctions::$tzComps
1466
 * @uses iCalUtilityFunctions::_strDate2arr()
1467
 * @uses iCalUtilityFunctions::_isArrayDate()
1468
 * @uses iCalUtilityFunctions::_chkDateArr()
1469
 * @uses iCalUtilityFunctions::_isOffset()
1470
 * @uses iCalUtilityFunctions::_setParams()
1471
 * @uses iCalUtilityFunctions::_existRem()
1472
 * @uses iCalUtilityFunctions::$fmt
1473
 * @uses iCalUtilityFunctions::_isArrayTimestampDate()
1474
 * @uses iCalUtilityFunctions::_timestamp2date()
1475
 * @uses iCalUtilityFunctions::_strdate2date()
1476
 * @return array
1477
 */
1478
  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 ) {
1479
    $input = $parno = null;
1480
    $localtime = (( 'dtstart' == $caller ) && in_array( $objName, iCalUtilityFunctions::$tzComps )) ? TRUE : FALSE;
1481
    iCalUtilityFunctions::_strDate2arr( $year );
1482
    if( iCalUtilityFunctions::_isArrayDate( $year )) {
1483
      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, FALSE ); //$parno );
1484
      if( 100 > $input['value']['year'] )
1485
        $input['value']['year'] += 2000;
1486
      if( $localtime )
1487
        unset( $month['VALUE'], $month['TZID'] );
1488
      elseif( !isset( $month['TZID'] ) && isset( $tzid ))
1489
        $month['TZID'] = $tzid;
1490
      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
1491
        unset( $month['TZID'] );
1492
      elseif( !isset( $input['value']['tz'] ) &&  isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
1493
        $input['value']['tz'] = $month['TZID'];
1494
        unset( $month['TZID'] );
1495
      }
1496
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1497
      $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6;
1498
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
1499
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
1500
      if( 6 > $parno )
1501
        unset( $input['value']['tz'], $input['params']['TZID'], $tzid );
1502
      if(( 6 <= $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
1503
        $d             = $input['value'];
1504
        $strdate       = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $d['tz'] );
1505
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
1506
        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
1507
      }
1508
      if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
1509
        $input['params']['TZID'] = $input['value']['tz'];
1510
        unset( $input['value']['tz'] );
1511
      }
1512
    } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
1513
    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
1514
      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
1515
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1516
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
1517
      $hitval          = 7;
1518
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
1519
      if( isset( $year['tz'] ) && !empty( $year['tz'] )) {
1520
        if( !iCalUtilityFunctions::_isOffset( $year['tz'] )) {
1521
          $input['params']['TZID'] = $year['tz'];
1522
          unset( $year['tz'], $tzid );
1523
        }
1524
        else {
1525
          if( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
1526
            if( !iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
1527
              unset( $tzid );
1528
            else
1529
              unset( $input['params']['TZID']);
1530
          }
1531
          elseif( isset( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
1532
            $input['params']['TZID'] = $tzid;
1533
        }
1534
      }
1535
      elseif( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
1536
        if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
1537
          $year['tz'] = $input['params']['TZID'];
1538
          unset( $input['params']['TZID']);
1539
          if( isset( $tzid ) && !empty( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
1540
            $input['params']['TZID'] = $tzid;
1541
        }
1542
      }
1543
      elseif( isset( $tzid ) && !empty( $tzid )) {
1544
        if( iCalUtilityFunctions::_isOffset( $tzid )) {
1545
          $year['tz'] = $tzid;
1546
          unset( $input['params']['TZID']);
1547
        }
1548
        else
1549
          $input['params']['TZID'] = $tzid;
1550
      }
1551
      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
1552
    } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
1553
    elseif( 8 <= strlen( trim((string) $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
1554
      if( $localtime )
1555
        unset( $month['VALUE'], $month['TZID'] );
1556
      elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
1557
        $month['TZID'] = $tzid;
1558
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1559
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
1560
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
1561
      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno );
1562
      if( 3 == $parno )
1563
        unset( $input['value']['tz'], $input['params']['TZID'] );
1564
      unset( $input['value']['unparsedtext'] );
1565
      if( isset( $input['value']['tz'] )) {
1566
        if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
1567
          $d           = $input['value'];
1568
          $strdate     = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $d['tz'] );
1569
          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1570
          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
1571
        }
1572
        else {
1573
          $input['params']['TZID'] = $input['value']['tz'];
1574
          unset( $input['value']['tz'] );
1575
        }
1576
      }
1577
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
1578
        $d             = $input['value'];
1579
        $strdate       = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $input['params']['TZID'] );
1580
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1581
        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
1582
      }
1583
    } // end elseif( 8 <= strlen( trim((string) $year )))
1584
    else {
1585
      if( is_array( $params ))
1586
        $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
1587
      elseif( is_array( $tz )) {
1588
        $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
1589
        $tz = FALSE;
1590
      }
1591
      elseif( is_array( $hour )) {
1592
        $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
1593
        $hour = $min = $sec = $tz = FALSE;
1594
      }
1595
      if( $localtime )
1596
        unset ( $input['params']['VALUE'], $input['params']['TZID'] );
1597
      elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
1598
        $input['params']['TZID'] = $tzid;
1599
      elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
1600
        unset( $input['params']['TZID'] );
1601
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
1602
        $tz            = $input['params']['TZID'];
1603
        unset( $input['params']['TZID'] );
1604
      }
1605
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
1606
      $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
1607
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
1608
      $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
1609
      if( 3 != $parno ) {
1610
        $input['value']['hour'] = ( $hour ) ? $hour : '0';
1611
        $input['value']['min']  = ( $min )  ? $min  : '0';
1612
        $input['value']['sec']  = ( $sec )  ? $sec  : '0';
1613
        if( !empty( $tz ))
1614
          $input['value']['tz'] = $tz;
1615
        $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
1616
        if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
1617
          $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
1618
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
1619
        unset( $input['value']['unparsedtext'] );
1620
        if( isset( $input['value']['tz'] )) {
1621
          if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
1622
            $d           = $input['value'];
1623
            $strdate     = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $d['tz'] );
1624
            $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1625
            unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
1626
          }
1627
          else {
1628
            $input['params']['TZID'] = $input['value']['tz'];
1629
            unset( $input['value']['tz'] );
1630
          }
1631
        }
1632
        elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
1633
          $d             = $input['value'];
1634
          $strdate       = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $input['params']['TZID'] );
1635
          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1636
          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
1637
        }
1638
      }
1639
    } // end else (i.e. using all arguments)
1640
    if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
1641
      $input['params']['VALUE'] = 'DATE';
1642
      unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
1643
    }
1644
    elseif( isset( $input['params']['TZID'] )) {
1645
      if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
1646
        $input['value']['tz'] = 'Z';
1647
        unset( $input['params']['TZID'] );
1648
      }
1649
      else
1650
        unset( $input['value']['tz'] );
1651
    }
1652
    elseif( isset( $input['value']['tz'] )) {
1653
      if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
1654
        $input['value']['tz'] = 'Z';
1655
      if( 'Z' != $input['value']['tz'] ) {
1656
        $input['params']['TZID'] = $input['value']['tz'];
1657
        unset( $input['value']['tz'] );
1658
      }
1659
      else
1660
        unset( $input['params']['TZID'] );
1661
    }
1662
    if( $localtime )
1663
      unset( $input['value']['tz'], $input['params']['TZID'] );
1664
    return $input;
1665
  }
1666
/**
1667
 * convert format for input date (UTC) to internal date with parameters
1668
 *
1669
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1670
 * @since 2.21.11 - 2015-03-10
1671
 * @param mixed $year
1672
 * @param mixed $month  optional
1673
 * @param int   $day    optional
1674
 * @param int   $hour   optional
1675
 * @param int   $min    optional
1676
 * @param int   $sec    optional
1677
 * @param array $params optional
1678
 * @uses iCalUtilityFunctions::_strDate2arr()
1679
 * @uses iCalUtilityFunctions::_isArrayDate()
1680
 * @uses iCalUtilityFunctions::_chkDateArr()
1681
 * @uses iCalUtilityFunctions::_setParams()
1682
 * @uses iCalUtilityFunctions::_isOffset()
1683
 * @uses iCalUtilityFunctions::$fmt
1684
 * @uses iCalUtilityFunctions::_strdate2date()
1685
 * @uses iCalUtilityFunctions::_isArrayTimestampDate()
1686
 * @uses iCalUtilityFunctions::_timestamp2date()
1687
 * @uses iCalUtilityFunctions::_date2strdate()
1688
 * @uses iCalUtilityFunctions::_existRem()
1689
 * @return array
1690
 */
1691
  public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
1692
    $input = null;
1693
    iCalUtilityFunctions::_strDate2arr( $year );
1694
    if( iCalUtilityFunctions::_isArrayDate( $year )) {
1695
      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 );
1696
      if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
1697
        $input['value']['year'] += 2000;
1698
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1699
      unset( $input['params']['VALUE']  );
1700
      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
1701
        $tzid = $input['value']['tz'];
1702
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
1703
        $tzid = $input['params']['TZID'];
1704
      else
1705
        $tzid = '';
1706
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
1707
      if( !empty( $tzid ) && ( 'Z' != $tzid ) && iCalUtilityFunctions::_isOffset( $tzid )) {
1708
        $d             = $input['value'];
1709
        $strdate       = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $tzid );
1710
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1711
        unset( $input['value']['unparsedtext'] );
1712
      }
1713
    }
1714
    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
1715
      if( isset( $year['tz'] ) && ! iCalUtilityFunctions::_isOffset( $year['tz'] ))
1716
        $year['tz']    = 'UTC';
1717
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
1718
        $year['tz']    = $input['params']['TZID'];
1719
      else
1720
        $year['tz']    = 'UTC';
1721
      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
1722
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1723
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
1724
    }
1725
    elseif( 8 <= strlen( trim((string) $year ))) { // ex. 2006-08-03 10:12:18
1726
      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 );
1727
      unset( $input['value']['unparsedtext'] );
1728
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
1729
      if(( !isset( $input['value']['tz'] ) || empty( $input['value']['tz'] )) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
1730
        $d             = $input['value'];
1731
        $strdate       = sprintf( iCalUtilityFunctions::$fmt['YmdHise'], (int) $d['year'], (int) $d['month'], (int) $d['day'], (int) $d['hour'], (int) $d['min'], (int) $d['sec'], $input['params']['TZID'] );
1732
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1733
        unset( $input['value']['unparsedtext'] );
1734
      }
1735
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
1736
    }
1737
    else {
1738
      $input['value']  = array( 'year'  => $year
1739
                              , 'month' => $month
1740
                              , 'day'   => $day
1741
                              , 'hour'  => $hour
1742
                              , 'min'   => $min
1743
                              , 'sec'   => $sec );
1744
      if(  isset( $tz )) $input['value']['tz'] = $tz;
1745
      if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
1746
         ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
1747
          if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
1748
            $input['value']['tz'] = $input['params']['TZID'];
1749
          unset( $input['params']['TZID'] );
1750
        $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
1751
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
1752
        unset( $input['value']['unparsedtext'] );
1753
      }
1754
      $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
1755
      unset( $input['params']['VALUE']  );
1756
    }
1757
    $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
1758
    if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
1759
    if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0;
1760
    if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0;
1761
    $input['value']['tz'] = 'Z';
1762
    return $input;
1763
  }
1764
/**
1765
 * check index and set (an indexed) content in multiple value array
1766
 *
1767
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1768
 * @since 2.6.12 - 2011-01-03
1769
 * @param array $valArr
1770
 * @param mixed $value
1771
 * @param array $params
1772
 * @param array $defaults
1773
 * @param int $index
1774
 * @uses iCalUtilityFunctions::_setParams()
1775
 * @return void
1776
 */
1777
  public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
1778
    if( !is_array( $valArr )) $valArr = array();
1779
    if( $index )
1780
      $index = $index - 1;
1781
    elseif( 0 < count( $valArr )) {
1782
      $keys  = array_keys( $valArr );
1783
      $index = end( $keys ) + 1;
1784
    }
1785
    else
1786
      $index = 0;
1787
    $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
1788
    ksort( $valArr );
1789
  }
1790
/**
1791
 * set input (formatted) parameters- component property attributes
1792
 *
1793
 * default parameters can be set, if missing
1794
 *
1795
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1796
 * @since 2.18.10 - 2013-09-04
1797
 * @param array $params
1798
 * @param array $defaults
1799
 * @return array
1800
 */
1801
  public static function _setParams( $params, $defaults=FALSE ) {
1802
    if( !is_array( $params))
1803
      $params = array();
1804
    $input    = array();
1805
    $params   = array_change_key_case( $params, CASE_UPPER );
1806
    foreach( $params as $paramKey => $paramValue ) {
1807
      if( is_array( $paramValue )) {
1808
        foreach( $paramValue as $pkey => $pValue ) {
1809
          if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
1810
            $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
1811
        }
1812
      }
1813
      elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
1814
        $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
1815
      if( 'VALUE' == $paramKey )
1816
        $input['VALUE']   = strtoupper( $paramValue );
1817
      else
1818
        $input[$paramKey] = $paramValue;
1819
    }
1820
    if( is_array( $defaults )) {
1821
      foreach( $defaults as $paramKey => $paramValue ) {
1822
        if( !isset( $input[$paramKey] ))
1823
          $input[$paramKey] = $paramValue;
1824
      }
1825
    }
1826
    return (0 < count( $input )) ? $input : null;
1827
  }
1828
/**
1829
 * set sort arguments/parameters in component
1830
 *
1831
 *
1832
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1833
 * @since 2.21.11 - 2015-03-21
1834
 * @param object $c       valendar component
1835
 * @param string $sortArg
1836
 * @uses calendarComponent::$srtk
1837
 * @uses calendarComponent::$objName
1838
 * @uses calendarComponent::$getProperty()
1839
 * @uses iCalUtilityFunctions::$mProps1
1840
 * @uses calendarComponent::_getProperties()
1841
 * @uses iCalUtilityFunctions::_date2strdate()
1842
 * @return void
1843
 */
1844
  public static function _setSortArgs( $c, $sortArg=FALSE ) {
1845
    $c->srtk = array( '0', '0', '0', '0' );
1846
    if( 'vtimezone' == $c->objName ) {
1847
      if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
1848
        $c->srtk[0] = 0;
1849
      return;
1850
    }
1851
    elseif( $sortArg ) {
1852
      if( in_array( $sortArg, iCalUtilityFunctions::$mProps1 )) {
1853
        $propValues = array();
1854
        $c->_getProperties( $sortArg, $propValues );
1855
        if( !empty( $propValues )) {
1856
          $sk         = array_keys( $propValues );
1857
          $c->srtk[0] = $sk[0];
1858
          if( 'RELATED-TO'  == $sortArg )
1859
            $c->srtk[0] .= $c->getProperty( 'uid' );
1860
        }
1861
        elseif( 'RELATED-TO'  == $sortArg )
1862
          $c->srtk[0] = $c->getProperty( 'uid' );
1863
      }
1864
      elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
1865
        $c->srtk[0] = $d;
1866
        if( 'UID' == $sortArg ) {
1867
          if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
1868
            $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
1869
            if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
1870
              $c->srtk[2] = PHP_INT_MAX;
1871
          }
1872
          else
1873
            $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
1874
        }
1875
      }
1876
      return;
1877
    } // end elseif( $sortArg )
1878
    if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
1879
      $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
1880
      unset( $c->srtk[0]['unparsedtext'] );
1881
    }
1882
    elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
1883
      $c->srtk[0] = 0;                                                // sortkey 0 : dtstart
1884
    if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
1885
      $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );     // sortkey 1 : dtend/due(/duration)
1886
      unset( $c->srtk[1]['unparsedtext'] );
1887
    }
1888
    elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
1889
      if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
1890
        $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
1891
        unset( $c->srtk[1]['unparsedtext'] );
1892
      }
1893
      elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
1894
        if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
1895
          $c->srtk[1] = 0;
1896
    }
1897
    if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
1898
      if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
1899
        $c->srtk[2] = 0;
1900
    if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
1901
      $c->srtk[3] = 0;
1902
  }
1903
/**
1904
 * break lines at pos 75
1905
 *
1906
 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
1907
 * break. Long content lines SHOULD be split into a multiple line
1908
 * representations using a line "folding" technique. That is, a long
1909
 * line can be split between any two characters by inserting a CRLF
1910
 * immediately followed by a single linear white space character (i.e.,
1911
 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
1912
 * of CRLF followed immediately by a single linear white space character
1913
 * is ignored (i.e., removed) when processing the content type.
1914
 *
1915
 * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
1916
 * the reserved expression "\n" in the arg $string could be broken up by the
1917
 * folding of lines, causing ambiguity in the return string.
1918
 *
1919
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1920
 * @since 2.16.2 - 2012-12-18
1921
 * @param string $string
1922
 * @param string $nl
1923
 * @return string
1924
 */
1925
  public static function _size75( $string, $nl ) {
1926
    $tmp             = $string;
1927
    $string          = '';
1928
    $cCnt = $x       = 0;
1929
    while( TRUE ) {
1930
      if( !isset( $tmp[$x] )) {
1931
        $string     .= $nl;                           // loop breakes here
1932
        break;
1933
      }
1934
      elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
1935
        $string     .= $nl.' \n';                     // don't break lines inside '\n'
1936
        $x          += 2;
1937
        if( !isset( $tmp[$x] )) {
1938
          $string   .= $nl;
1939
          break;
1940
        }
1941
        $cCnt        = 3;
1942
      }
1943
      elseif( 75    <= $cCnt ) {
1944
        $string     .= $nl.' ';
1945
        $cCnt        = 1;
1946
      }
1947
      $byte          = ord( $tmp[$x] );
1948
      $string       .= $tmp[$x];
1949
      switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1950
        case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
1951
          $cCnt     += 1;
1952
          break;                                      // add a one byte character
1953
        case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX
1954
          if( isset( $tmp[$x+1] )) {
1955
            $cCnt   += 1;
1956
            $string  .= $tmp[$x+1];
1957
            $x       += 1;                            // add a two bytes character
1958
          }
1959
          break;
1960
        case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX
1961
          if( isset( $tmp[$x+2] )) {
1962
            $cCnt   += 1;
1963
            $string .= $tmp[$x+1].$tmp[$x+2];
1964
            $x      += 2;                             // add a three bytes character
1965
          }
1966
          break;
1967
        case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX
1968
          if( isset( $tmp[$x+3] )) {
1969
            $cCnt   += 1;
1970
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
1971
            $x      += 3;                             // add a four bytes character
1972
          }
1973
          break;
1974
        case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX
1975
          if( isset( $tmp[$x+4] )) {
1976
            $cCnt   += 1;
1977
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
1978
            $x      += 4;                             // add a five bytes character
1979
          }
1980
          break;
1981
        case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X
1982
          if( isset( $tmp[$x+5] )) {
1983
            $cCnt   += 1;
1984
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
1985
            $x      += 5;                             // add a six bytes character
1986
          }
1987
        default:                                      // add any other byte without counting up $cCnt
1988
          break;
1989
      } // end switch( TRUE )
1990
      $x         += 1;                                // next 'byte' to test
1991
    } // end while( TRUE ) {
1992
    return $string;
1993
  }
1994
/**
1995
 * sort callback function for exdate
1996
 *
1997
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1998
 * @since 2.21.11 - 2015-03-07
1999
 * @param array $a
2000
 * @param array $b
2001
 * @uses iCalUtilityFunctions::$fmt
2002
 * @return int
2003
 */
2004
  public static function _sortExdate1( $a, $b ) {
2005
    $as  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $a['year'], (int) $a['month'], (int) $a['day'] );
2006
    $as .= ( isset( $a['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $a['hour'], (int) $a['min'], (int) $a['sec'] ) : '';
2007
    $bs  = sprintf( iCalUtilityFunctions::$fmt['His'], (int) $b['year'], (int) $b['month'], (int) $b['day'] );
2008
    $bs .= ( isset( $b['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $b['hour'], (int) $b['min'], (int) $b['sec'] ) : '';
2009
    return strcmp( $as, $bs );
2010
  }
2011
/**
2012
 * sort callback function for exdate
2013
 *
2014
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2015
 * @since 2.21.11 - 2015-03-07
2016
 * @param array $a
2017
 * @param array $b
2018
 * @uses iCalUtilityFunctions::$fmt
2019
 * @return int
2020
 */
2021
  public static function _sortExdate2( $a, $b ) {
2022
    $val = reset( $a['value'] );
2023
    $as  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2024
    $as .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2025
    $val = reset( $b['value'] );
2026
    $bs  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2027
    $bs .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2028
    return strcmp( $as, $bs );
2029
  }
2030
/**
2031
 * sort callback function for rdate
2032
 *
2033
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2034
 * @since 2.21.11 - 2015-03-07
2035
 * @param array $a
2036
 * @param array $b
2037
 * @uses iCalUtilityFunctions::$fmt
2038
 * @return int
2039
 */
2040
  public static function _sortRdate1( $a, $b ) {
2041
    $val = isset( $a['year'] ) ? $a : $a[0];
2042
    $as  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2043
    $as .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2044
    $val = isset( $b['year'] ) ? $b : $b[0];
2045
    $bs  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2046
    $bs .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2047
    return strcmp( $as, $bs );
2048
  }
2049
/**
2050
 * sort callback function for rdate
2051
 *
2052
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2053
 * @since 2.21.11 - 2015-03-07
2054
 * @param array $a
2055
 * @param array $b
2056
 * @uses iCalUtilityFunctions::$fmt
2057
 * @return int
2058
 */
2059
  public static function _sortRdate2( $a, $b ) {
2060
    $val   = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
2061
    if( empty( $val ))
2062
      $as  = '';
2063
    else {
2064
      $as  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2065
      $as .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2066
    }
2067
    $val   = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
2068
    if( empty( $val ))
2069
      $bs  = '';
2070
    else {
2071
      $bs  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $val['year'], (int) $val['month'], (int) $val['day'] );
2072
      $bs .= ( isset( $val['hour'] )) ? sprintf( iCalUtilityFunctions::$fmt['His'], (int) $val['hour'], (int) $val['min'], (int) $val['sec'] ) : '';
2073
    }
2074
    return strcmp( $as, $bs );
2075
  }
2076
/**
2077
 * separate property attributes from property value
2078
 *
2079
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2080
 * @since 2.18.6 - 2013-08-29
2081
 * @param string $line      property content
2082
 * @param array  $propAttr  property parameters
2083
 * @uses iCalUtilityFunctions::$parValPrefix
2084
 * @return void
2085
 */
2086
  public static function _splitContent( & $line, & $propAttr=null ) {
2087
    $attr         = array();
2088
    $attrix       = -1;
2089
    $clen         = strlen( $line );
2090
    $WithinQuotes = FALSE;
2091
    $cix          = 0;
2092
    while( FALSE !== substr( $line, $cix, 1 )) {
2093
      if(  ! $WithinQuotes  &&   (  ':' == $line[$cix] )                         &&
2094
                                 ( substr( $line,$cix,     3 )  != '://' )       &&
2095
         ( ! in_array( strtolower( substr( $line,$cix - 6, 4 )), iCalUtilityFunctions::$parValPrefix['MStz'] ))   &&
2096
         ( ! in_array( strtolower( substr( $line,$cix - 3, 4 )), iCalUtilityFunctions::$parValPrefix['Proto3'] )) &&
2097
         ( ! in_array( strtolower( substr( $line,$cix - 4, 5 )), iCalUtilityFunctions::$parValPrefix['Proto4'] )) &&
2098
         ( ! in_array( strtolower( substr( $line,$cix - 6, 7 )), iCalUtilityFunctions::$parValPrefix['Proto6'] ))) {
2099
        $attrEnd = TRUE;
2100
        if(( $cix < ( $clen - 4 )) &&
2101
             ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
2102
          for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
2103
            if( '://' == substr( $line, $c2ix - 2, 3 )) {
2104
              $attrEnd = FALSE;
2105
              break; // an URI with a portnr!!
2106
            }
2107
          }
2108
        }
2109
        if( $attrEnd) {
2110
          $line = substr( $line, ( $cix + 1 ));
2111
          break;
2112
        }
2113
        $cix++;
2114
      }
2115
      if( '"' == $line[$cix] )
2116
        $WithinQuotes = ! $WithinQuotes;
2117
      if( ';' == $line[$cix] )
2118
        $attr[++$attrix] = null;
2119
      else
2120
        $attr[$attrix] .= $line[$cix];
2121
      $cix++;
2122
    }
2123
            /* make attributes in array format */
2124
    $propAttr = array();
2125
    foreach( $attr as $attribute ) {
2126
      $attrsplit = explode( '=', $attribute, 2 );
2127
      if( 1 < count( $attrsplit ))
2128
        $propAttr[$attrsplit[0]] = $attrsplit[1];
2129
    }
2130
  }
2131
/**
2132
 * step date, return updated date, array and timpstamp
2133
 *
2134
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2135
 * @since 2.21.11 - 2015-03-10
2136
 * @param array  $date     date to step
2137
 * @param string $dateYMD  date YMD
2138
 * @param array  $step     default array( 'day' => 1 )
2139
 * @uses iCalUtilityFunctions::$fmt
2140
 * @return void
2141
 */
2142
  public static function _stepdate( & $date, & $dateYMD, $step=array( 'day' => 1 )) {
2143
    if( !isset( $date['hour'] )) $date['hour'] = 0;
2144
    if( !isset( $date['min'] ))  $date['min']  = 0;
2145
    if( !isset( $date['sec'] ))  $date['sec']  = 0;
2146
    if( isset( $step['day'] ))
2147
      $mcnt        = date( 't', mktime( (int) $date['hour'], (int) $date['min'], (int) $date['sec'], (int) $date['month'], (int) $date['day'], (int) $date['year'] ));
2148
    foreach( $step as $stepix => $stepvalue )
2149
      $date[$stepix]   += $stepvalue;
2150
    if( isset( $step['month'] )) {
2151
      if( 12 < $date['month'] ) {
2152
        $date['year']  += 1;
2153
        $date['month'] -= 12;
2154
      }
2155
    }
2156
    elseif( isset( $step['day'] )) {
2157
      if( $mcnt < $date['day'] ) {
2158
        $date['day']   -= $mcnt;
2159
        $date['month'] += 1;
2160
        if( 12 < $date['month'] ) {
2161
          $date['year']  += 1;
2162
          $date['month'] -= 12;
2163
        }
2164
      }
2165
    }
2166
    $dateYMD       = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $date['year'], (int) $date['month'], (int) $date['day'] );
2167
    unset( $mcnt );
2168
  }
2169
/**
2170
 * convert a date from specific string to array format
2171
 *
2172
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2173
 * @since 2.11.8 - 2012-01-27
2174
 * @param mixed $input
2175
 * @return bool, TRUE on success
2176
 */
2177
  public static function _strDate2arr( & $input ) {
2178
    if( is_array( $input ))
2179
      return FALSE;
2180
    if( 5 > strlen( (string) $input ))
2181
      return FALSE;
2182
    $work = $input;
2183
    if( 2 == substr_count( $work, '-' ))
2184
      $work = str_replace( '-', '', $work );
2185
    if( 2 == substr_count( $work, '/' ))
2186
      $work = str_replace( '/', '', $work );
2187
    if( !ctype_digit( substr( $work, 0, 8 )))
2188
      return FALSE;
2189
    $temp = array( 'year'  => (int) substr( $work,  0, 4 )
2190
                 , 'month' => (int) substr( $work,  4, 2 )
2191
                 , 'day'   => (int) substr( $work,  6, 2 ));
2192
    if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
2193
      return FALSE;
2194
    if( 8 == strlen( $work )) {
2195
      $input = $temp;
2196
      return TRUE;
2197
    }
2198
    if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
2199
      $work =  substr( $work, 9 );
2200
    elseif( ctype_digit( substr( $work, 8, 1 )))
2201
      $work = substr( $work, 8 );
2202
    else
2203
     return FALSE;
2204
    if( 2 == substr_count( $work, ':' ))
2205
      $work = str_replace( ':', '', $work );
2206
    if( !ctype_digit( substr( $work, 0, 4 )))
2207
      return FALSE;
2208
    $temp['hour']  = substr( $work, 0, 2 );
2209
    $temp['min']   = substr( $work, 2, 2 );
2210
    if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
2211
       (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
2212
      return FALSE;
2213
    if( ctype_digit( substr( $work, 4, 2 ))) {
2214
      $temp['sec'] = substr( $work, 4, 2 );
2215
      if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 ))
2216
        return FALSE;
2217
      $len = 6;
2218
    }
2219
    else {
2220
      $temp['sec'] = 0;
2221
      $len = 4;
2222
    }
2223
    if( $len < strlen( $work))
2224
      $temp['tz'] = trim( substr( $work, 6 ));
2225
    $input = $temp;
2226
    return TRUE;
2227
  }
2228
/**
2229
 * ensures internal date-time/date format for input date-time/date in string fromat
2230
 *
2231
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2232
 * @since 2.21.11 - 2015-03-15
2233
 * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
2234
 * @param array $datetime
2235
 * @param int   $parno optional, default FALSE
2236
 * @param moxed $wtz optional, default null
2237
 * @uses iCalUtilityFunctions::_isOffset()
2238
 * @uses iCalUtilityFunctions::_strDate2arr()
2239
 * @uses iCalUtilityFunctions::_isOffset()
2240
 * @uses iCalUtilityFunctions::_tz2offset()
2241
 * @uses iCalUtilityFunctions::$fmt
2242
 * @return array
2243
 */
2244
  public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
2245
    $unparseddatetime = $datetime;
2246
    $datetime   = (string) trim( $datetime );
2247
    $tz         = null;
2248
    $offset     = 0;
2249
    $tzSts      = FALSE;
2250
    $len        = strlen( $datetime );
2251
    if( 'Z' == substr( $datetime, -1 )) {
2252
      $tz       = 'Z';
2253
      $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
2254
      $tzSts    = TRUE;
2255
    }
2256
    if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
2257
      $tz       = substr( $datetime, -5, 5 );
2258
      $datetime = trim( substr( $datetime, 0, ($len - 5)));
2259
    }
2260
    elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
2261
      $tz       = substr( $datetime, -7, 7 );
2262
      $datetime = trim( substr( $datetime, 0, ($len - 7)));
2263
    }
2264
    elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
2265
      $output = $datetime;
2266
      if( !empty( $tz ))
2267
        $output['tz'] = 'Z';
2268
      $output['unparsedtext'] = $unparseddatetime;
2269
      return $output;
2270
    }
2271
    else {
2272
      $cx  = $tx = 0;    //  find any trailing timezone or offset
2273
      $len = strlen( $datetime );
2274
      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
2275
        $char = substr( $datetime, $cx, 1 );
2276
        if(( ' ' == $char ) || ctype_digit( $char ))
2277
          break; // if exists, tz ends here.. . ?
2278
        else
2279
           $tx--; // tz length counter
2280
      }
2281
      if( 0 > $tx ) { // if any
2282
        $tz     = substr( $datetime, $tx );
2283
        $datetime = trim( substr( $datetime, 0, $len + $tx ));
2284
      }
2285
      if(( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
2286
         ( ctype_digit( substr( $datetime, 0, 14 ))))
2287
        $tzSts  = TRUE;
2288
    }
2289
    if( empty( $tz ) && !empty( $wtz ))
2290
      $tz       = $wtz;
2291
    if( 3 == $parno )
2292
      $tz       = null;
2293
    if( !empty( $tz )) { // tz set
2294
      if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
2295
        $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
2296
        $tz     = 'UTC';
2297
        $tzSts  = TRUE;
2298
      }
2299
      elseif( !empty( $wtz ))
2300
        $tzSts  = TRUE;
2301
      $tz       = trim( $tz );
2302
      if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
2303
        $tz     = 'UTC';
2304
      if( 0 < substr_count( $datetime, '-' ))
2305
        $datetime = str_replace( '-', '/', $datetime );
2306
      try {
2307
        $d        = new DateTime( $datetime, new DateTimeZone( $tz ));
2308
        if( 0  != $offset )  // adjust for offset
2309
          $d->modify( $offset.' seconds' );
2310
        $datestring = $d->format( iCalUtilityFunctions::$fmt['YmdHis3'] );
2311
        unset( $d );
2312
      }
2313
      catch( Exception $e ) {
2314
        $datestring = date( iCalUtilityFunctions::$fmt['YmdHis3'], strtotime( $datetime ));
2315
      }
2316
    } // end if( !empty( $tz ))
2317
    else
2318
      $datestring = date( iCalUtilityFunctions::$fmt['YmdHis3'], strtotime( $datetime ));
2319
    if( 'UTC' == $tz )
2320
      $tz         = 'Z';
2321
    $d            = explode( '-', $datestring );
2322
    $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
2323
    if( !$parno || ( 3 != $parno )) { // parno is set to 6 or 7
2324
      $output['hour'] = $d[3];
2325
      $output['min']  = $d[4];
2326
      $output['sec']  = $d[5];
2327
      if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
2328
        $output['tz'] = $tz;
2329
    }
2330
    // return original string in the array in case strtotime failed to make sense of it
2331
    $output['unparsedtext'] = $unparseddatetime;
2332
    return $output;
2333
  }
2334
/********************************************************************************/
2335
/**
2336
 * special characters management output
2337
 *
2338
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2339
 * @since 2.16.2 - 2012-12-18
2340
 * @param string $string
2341
 * @param string $format
2342
 * @param string $nl
2343
 * @return string
2344
 */
2345
  public static function _strrep( $string, $format, $nl ) {
2346
    switch( $format ) {
2347
      case 'xcal':
2348
        $string = str_replace( '\n',  $nl, $string);
2349
        $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
2350
        break;
2351
      default:
2352
        $pos = 0;
2353
        $specChars = array( 'n', 'N', 'r', ',', ';' );
2354
        while( isset( $string[$pos] )) {
2355
          if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
2356
            break;
2357
          if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
2358
            $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
2359
            $pos += 1;
2360
          }
2361
          $pos += 1;
2362
        }
2363
        if( FALSE !== strpos( $string, '"' ))
2364
          $string = str_replace('"',   "'",       $string);
2365
        if( FALSE !== strpos( $string, ',' ))
2366
          $string = str_replace(',',   '\,',      $string);
2367
        if( FALSE !== strpos( $string, ';' ))
2368
          $string = str_replace(';',   '\;',      $string);
2369
        if( FALSE !== strpos( $string, "\r\n" ))
2370
          $string = str_replace( "\r\n", '\n',    $string);
2371
        elseif( FALSE !== strpos( $string, "\r" ))
2372
          $string = str_replace( "\r", '\n',      $string);
2373
        elseif( FALSE !== strpos( $string, "\n" ))
2374
          $string = str_replace( "\n", '\n',      $string);
2375
        if( FALSE !== strpos( $string, '\N' ))
2376
          $string = str_replace( '\N', '\n',      $string);
2377
//        if( FALSE !== strpos( $string, $nl ))
2378
          $string = str_replace( $nl, '\n', $string);
2379
        break;
2380
    }
2381
    return $string;
2382
  }
2383
/**
2384
 * special characters management input (from iCal file)
2385
 *
2386
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2387
 * @since 2.16.2 - 2012-12-18
2388
 * @param string $string
2389
 * @return string
2390
 */
2391
  public static function _strunrep( $string ) {
2392
    $string = str_replace( '\\\\', '\\',     $string);
2393
    $string = str_replace( '\,',   ',',      $string);
2394
    $string = str_replace( '\;',   ';',      $string);
2395
//    $string = str_replace( '\n',  $nl, $string); // ??
2396
    return $string;
2397
  }
2398
/**
2399
 * convert timestamp to date array, default UTC or adjusted for offset/timezone
2400
 *
2401
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2402
 * @since 2.21.11 - 2015-03-07
2403
 * @param mixed   $timestamp
2404
 * @param int     $parno
2405
 * @param string  $wtz
2406
 * @uses iCalUtilityFunctions::_isOffset()
2407
 * @uses iCalUtilityFunctions::_tz2offset()
2408
 * @uses iCalUtilityFunctions::$fmt
2409
 * @return array
2410
 */
2411
  public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
2412
    if( is_array( $timestamp )) {
2413
      $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
2414
      $timestamp = $timestamp['timestamp'];
2415
    }
2416
    $tz          = ( isset( $tz )) ? $tz : $wtz;
2417
    $offset      = 0;
2418
    if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
2419
      $tz        = 'UTC';
2420
    elseif( iCalUtilityFunctions::_isOffset( $tz )) {
2421
      $offset    = iCalUtilityFunctions::_tz2offset( $tz );
2422
    }
2423
    try {
2424
      $d         = new DateTime( "@$timestamp" );  // set UTC date
2425
      if(  0 != $offset )                          // adjust for offset
2426
        $d->modify( $offset.' seconds' );
2427
      elseif( 'UTC' != $tz )
2428
        $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
2429
      $date      = $d->format( iCalUtilityFunctions::$fmt['YmdHis3'] );
2430
      unset( $d );
2431
    }
2432
    catch( Exception $e ) {
2433
      $date      = date( iCalUtilityFunctions::$fmt['YmdHis3'], $timestamp );
2434
    }
2435
    $date        = explode( '-', $date );
2436
    $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
2437
    if( 3 != $parno ) {
2438
      $output['hour'] = $date[3];
2439
      $output['min']  = $date[4];
2440
      $output['sec']  = $date[5];
2441
      if(( 'UTC' == $tz ) || ( 0 == $offset ))
2442
        $output['tz'] = 'Z';
2443
    }
2444
    return $output;
2445
  }
2446
/**
2447
 * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
2448
 *
2449
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2450
 * @since 2.15.1 - 2012-10-17
2451
 * @param mixed  $date    date to alter
2452
 * @param string $tzFrom  PHP valid 'from' timezone
2453
 * @param string $tzTo    PHP valid 'to' timezone, default 'UTC'
2454
 * @param string $format  date output format, default 'Ymd\THis'
2455
 * @uses iCalUtilityFunctions::_isArrayDate()
2456
 * @uses iCalUtilityFunctions::_date2strdate()
2457
 * @uses iCalUtilityFunctions::_chkDateArr()
2458
 * @return bool
2459
 */
2460
  public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
2461
    if( is_array( $date ) && isset( $date['timestamp'] )) {
2462
      try {
2463
        $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
2464
        $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
2465
      }
2466
      catch( Exception $e ) { return FALSE; }
2467
    }
2468
    else {
2469
      if( iCalUtilityFunctions::_isArrayDate( $date )) {
2470
        if( isset( $date['tz'] ))
2471
          unset( $date['tz'] );
2472
        $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
2473
      }
2474
      if( 'Z' == substr( $date, -1 ))
2475
        $date = substr( $date, 0, ( strlen( $date ) - 2 ));
2476
      try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
2477
      catch( Exception $e ) { return FALSE; }
2478
    }
2479
    try { $d->setTimezone( new DateTimeZone( $tzTo )); }
2480
    catch( Exception $e ) { return FALSE; }
2481
    $date = $d->format( $format );
2482
    return TRUE;
2483
  }
2484
/**
2485
 * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
2486
 *
2487
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2488
 * @since 2.11.4 - 2012-01-11
2489
 * @param string $tz
2490
 * @return integer
2491
 */
2492
  public static function _tz2offset( $tz ) {
2493
    $tz           = trim( (string) $tz );
2494
    $offset       = 0;
2495
    if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) ||
2496
       ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
2497
       (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
2498
           (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
2499
      return $offset;
2500
    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
2501
    $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
2502
    $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
2503
    $offset       = $hours2sec + $min2sec + $sec;
2504
    $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
2505
    return $offset;
2506
  }
2507
}