%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/tjamichg/cursos.tjamich.gob.mx/vendor/kigkonsult/icalcreator/src/util/
Upload File :
Create Path :
Current File : /home/tjamichg/cursos.tjamich.gob.mx/vendor/kigkonsult/icalcreator/src/util/util.php

<?php
/**
 * iCalcreator, a PHP rfc2445/rfc5545 solution.
 *
 * This file is a part of iCalcreator.
 *
 * Copyright (c) 2007-2017 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
 * Link      http://kigkonsult.se/iCalcreator/index.php
 * Package   iCalcreator
 * Version   2.24
 * License   Subject matter of licence is the software iCalcreator.
 *           The above copyright, link, package and version notices,
 *           this licence notice and the [rfc5545] PRODID as implemented and
 *           invoked in iCalcreator shall be included in all copies or
 *           substantial portions of the iCalcreator.
 *           iCalcreator can be used either under the terms of
 *           a proprietary license, available at <https://kigkonsult.se/>
 *           or the GNU Affero General Public License, version 3:
 *           iCalcreator is free software: you can redistribute it and/or
 *           modify it under the terms of the GNU Affero General Public License
 *           as published by the Free Software Foundation, either version 3 of
 *           the License, or (at your option) any later version.
 *           iCalcreator is distributed in the hope that it will be useful,
 *           but WITHOUT ANY WARRANTY; without even the implied warranty of
 *           MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *           GNU Affero General Public License for more details.
 *           You should have received a copy of the GNU Affero General Public
 *           License along with this program.
 *           If not, see <http://www.gnu.org/licenses/>.
 */
namespace kigkonsult\iCalcreator\util;
/**
 * iCalcreator utility/support class
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.21.11 - 2015-04-03
 */
class util {
/**
 *  @var string  iCal component (lowercase) names
 *  @static
 */
  public static $LCVTIMEZONE = 'vtimezone';
  public static $LCSTANDARD  = 'standard';
  public static $LCDAYLIGHT  = 'daylight';
  public static $LCVEVENT    = 'vevent';
  public static $LCVTODO     = 'vtodo';
  public static $LCVJOURNAL  = 'vjournal';
  public static $LCVFREEBUSY = 'vfreebusy';
  public static $LCVALARM    = 'valarm';
/**
 *  @var array  iCal component (lowercase) collections
 *  @static
 */
  public static $VCOMPS      = ['vevent', 'vtodo', 'vjournal', 'vfreebusy'];
  public static $MCOMPS      = ['vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone'];
  public static $LCSUBCOMPS  = ['valarm', 'vtimezone', 'standard', 'daylight'];
  public static $TZCOMPS     = ['vtimezone', 'standard', 'daylight'];
  public static $ALLCOMPS    = ['vtimezone', 'standard', 'daylight', 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm'];
/**
 *  @var string  iCal property names
 *  @static
 */
  public static $ACTION           = 'ACTION';
  public static $ATTACH           = 'ATTACH';
  public static $ATTENDEE         = 'ATTENDEE';
  public static $CALSCALE         = 'CALSCALE';
  public static $CATEGORIES       = 'CATEGORIES';
  public static $CLASS            = 'CLASS';
  public static $COMMENT          = 'COMMENT';
  public static $COMPLETED        = 'COMPLETED';
  public static $CONTACT          = 'CONTACT';
  public static $CREATED          = 'CREATED';
  public static $DESCRIPTION      = 'DESCRIPTION';
  public static $DTEND            = 'DTEND';
  public static $DTSTAMP          = 'DTSTAMP';
  public static $DTSTART          = 'DTSTART';
  public static $DUE              = 'DUE';
  public static $DURATION         = 'DURATION';
  public static $EXDATE           = 'EXDATE';
  public static $EXRULE           = 'EXRULE';
  public static $FREEBUSY         = 'FREEBUSY';
  public static $GEO              = 'GEO';
  public static $GEOLOCATION      = 'GEOLOCATION';
  public static $LAST_MODIFIED    = 'LAST-MODIFIED';
  public static $LOCATION         = 'LOCATION';
  public static $METHOD           = 'METHOD';
  public static $ORGANIZER        = 'ORGANIZER';
  public static $PERCENT_COMPLETE = 'PERCENT-COMPLETE';
  public static $PRIORITY         = 'PRIORITY';
  public static $PRODID           = 'PRODID';
  public static $RECURRENCE_ID    = 'RECURRENCE-ID';
  public static $RELATED_TO       = 'RELATED-TO';
  public static $REPEAT           = 'REPEAT';
  public static $REQUEST_STATUS   = 'REQUEST-STATUS';
  public static $RESOURCES        = 'RESOURCES';
  public static $RDATE            = 'RDATE';
  public static $RRULE            = 'RRULE';
  public static $SEQUENCE         = 'SEQUENCE';
  public static $STATUS           = 'STATUS';
  public static $SUMMARY          = 'SUMMARY';
  public static $TRANSP           = 'TRANSP';
  public static $TRIGGER          = 'TRIGGER';
  public static $TZID             = 'TZID';
  public static $TZNAME           = 'TZNAME';
  public static $TZOFFSETFROM     = 'TZOFFSETFROM';
  public static $TZOFFSETTO       = 'TZOFFSETTO';
  public static $TZURL            = 'TZURL';
  public static $UID              = 'UID';
  public static $URL              = 'URL';
  public static $VERSION          = 'VERSION';
  public static $X_PROP           = 'X-PROP';
/**
 *  @var string  vcalendar::selectComponents added x-property names
 *  @static
 */
  public static $X_CURRENT_DTSTART = 'X-CURRENT-DTSTART';
  public static $X_CURRENT_DTEND   = 'X-CURRENT-DTEND';
  public static $X_CURRENT_DUE     = 'X-CURRENT-DUE';
  public static $X_RECURRENCE      = 'X-RECURRENCE';
  public static $X_OCCURENCE       = 'X-OCCURENCE';
/**
 *  @var array  iCal component property collections
 *  @static
 */
  public static $PROPNAMES  = ['ACTION', 'ATTACH', 'ATTENDEE', 'CATEGORIES',
                               'CLASS', 'COMMENT', 'COMPLETED', 'CONTACT',
                               'CREATED', 'DESCRIPTION', 'DTEND', 'DTSTAMP',
                               'DTSTART', 'DUE', 'DURATION', 'EXDATE', 'EXRULE',
                               'FREEBUSY', 'GEO', 'LAST-MODIFIED', 'LOCATION',
                               'ORGANIZER', 'PERCENT-COMPLETE',  'PRIORITY',
                               'RECURRENCE-ID', 'RELATED-TO', 'REPEAT',
                               'REQUEST-STATUS', 'RESOURCES', 'RRULE', 'RDATE',
                               'SEQUENCE', 'STATUS', 'SUMMARY', 'TRANSP',
                               'TRIGGER',  'TZNAME', 'TZID', 'TZOFFSETFROM',
                               'TZOFFSETTO', 'TZURL', 'UID', 'URL', 'X-'];
  public static $DATEPROPS  = ['DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED',
                               'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID'];
  public static $OTHERPROPS = ['ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION',
                               'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES',
                               'STATUS', 'SUMMARY', 'UID', 'URL'];
  public static $MPROPS1    = ['ATTENDEE', 'CATEGORIES', 'CONTACT',
                               'RELATED-TO', 'RESOURCES'];
  public static $MPROPS2    = ['ATTACH',   'ATTENDEE', 'CATEGORIES',
                               'COMMENT', 'CONTACT', 'DESCRIPTION',
                               'EXDATE', 'EXRULE', 'FREEBUSY', 'RDATE',
                               'RELATED-TO', 'RESOURCES', 'RRULE',
                               'REQUEST-STATUS', 'TZNAME', 'X-PROP'];
/**
 *  @var string  iCalcreator config keys
 *  @static
 */
  public static $ALLOWEMPTY   = 'ALLOWEMPTY';
  public static $COMPSINFO    = 'COMPSINFO';
  public static $DELIMITER    = 'DELIMITER';
  public static $DIRECTORY    = 'DIRECTORY';
  public static $FILENAME     = 'FILENAME';
  public static $DIRFILE      = 'DIRFILE';
  public static $FILESIZE     = 'FILESIZE';
  public static $FILEINFO     = 'FILEINFO';
  public static $LANGUAGE     = 'LANGUAGE';
  public static $PROPINFO     = 'PROPINFO';
  public static $SETPROPERTYNAMES = 'SETPROPERTYNAMES';
  public static $UNIQUE_ID    = 'UNIQUE_ID';
/**
 *  @var string  iCal date/time parameter key values
 *  @static
 */
  public static $DATE        = 'DATE';
  public static $PERIOD      = 'PERIOD';
  public static $DATE_TIME   = 'DATE-TIME';
  public static $DEFAULTVALUEDATETIME = ['VALUE' => 'DATE-TIME'];
  public static $T           = 'T';
  public static $Z           = 'Z';
  public static $UTC         = 'UTC';
  public static $GMT         = 'GMT';
  public static $LCYEAR      = 'year';
  public static $LCMONTH     = 'month';
  public static $LCDAY       = 'day';
  public static $LCHOUR      = 'hour';
  public static $LCMIN       = 'min';
  public static $LCSEC       = 'sec';
  public static $LCtz        = 'tz';
  public static $LCWEEK      = 'week';
  public static $LCTIMESTAMP = 'timestamp';
/**
 *  @var string  iCal ATTENDEE, ORGANIZER etc param keywords
 *  @static
 */
  public static $CUTYPE          = 'CUTYPE';
  public static $MEMBER          = 'MEMBER';
  public static $ROLE            = 'ROLE';
  public static $PARTSTAT        = 'PARTSTAT';
  public static $RSVP            = 'RSVP';
  public static $DELEGATED_TO    = 'DELEGATED-TO';
  public static $DELEGATED_FROM  = 'DELEGATED-FROM';
  public static $SENT_BY         = 'SENT-BY';
  public static $CN              = 'CN';
  public static $DIR             = 'DIR';
  public static $INDIVIDUAL      = 'INDIVIDUAL';
  public static $NEEDS_ACTION    = 'NEEDS-ACTION';
  public static $REQ_PARTICIPANT = 'REQ-PARTICIPANT';
  public static $false           = 'false';
/**
 *  @var array  iCal ATTENDEE, ORGANIZER etc param collections
 *  @static
 */
  public static $ATTENDEEPARKEYS    = ['DELEGATED-FROM', 'DELEGATED-TO', 'MEMBER'];
  public static $ATTENDEEPARALLKEYS = ['CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT',
                                       'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM',
                                       'SENT-BY', 'CN', 'DIR', 'LANGUAGE'];
/**
 *  @var string  iCal RRULE, EXRULE etc param keywords
 *  @static
 */
  public static $FREQ        = 'FREQ';
  public static $UNTIL       = 'UNTIL';
  public static $COUNT       = 'COUNT';
  public static $INTERVAL    = 'INTERVAL';
  public static $WKST        = 'WKST';
  public static $BYMONTHDAY  = 'BYMONTHDAY';
  public static $BYYEARDAY   = 'BYYEARDAY';
  public static $BYWEEKNO    = 'BYWEEKNO';
  public static $BYMONTH     = 'BYMONTH';
  public static $BYSETPOS    = 'BYSETPOS';
  public static $BYDAY       = 'BYDAY';
  public static $DAY         = 'DAY';
/**
 *  @var string  misc. values
 *  @static
 */
  public static $ALTREP        = 'ALTREP';
  public static $ALTRPLANGARR  = ['ALTREP', 'LANGUAGE'];
  public static $VALUE         = 'VALUE';
  public static $BINARY        = 'BINARY';
  public static $LCvalue       = 'value';
  public static $LCparams      = 'params';
  public static $UNPARSEDTEXT  = 'unparsedtext';
  public static $SERVER_NAME   = 'SERVER_NAME';
  public static $LOCALHOST     = 'localhost';
  public static $EMPTYPROPERTY = '';
  public static $FMTBEGIN      = "BEGIN:%s\r\n";
  public static $FMTEND        = "END:%s\r\n";
  public static $CRLF          = "\r\n";
  public static $COMMA         = ',';
  public static $COLON         = ':';
  public static $QQ            = '"';
  public static $SEMIC         = ';';
  public static $MINUS         = '-';
  public static $PLUS          = '+';
  public static $SP1           = ' ';
  public static $ZERO          = '0';
  public static $DOT           = '.';
  public static $L             = '/';
  public static $YMDHISE       = '%04d-%02d-%02d %02d:%02d:%02d %s';
  public static $YMD           = '%04d%02d%02d';
  public static $HIS           = '%02d%02d%02d';
/**
 *  @var string  util date/datetime formats
 *  @access private
 *  @static
 */
  private static $YMDHIS3 = 'Y-m-d-H-i-s';
/**
 * Initiates configuration, set defaults
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-03-11
 * @param array $config
 * @return array
 * @static
 */
  public static function initConfig( $config ) {
    $config        = array_change_key_case( $config, CASE_UPPER );
    if( ! isset( $config[self::$ALLOWEMPTY] ))
      $config[self::$ALLOWEMPTY] = true;
    if( ! isset( $config[self::$DELIMITER] ))
      $config[self::$DELIMITER]  = DIRECTORY_SEPARATOR;
    if( ! isset( $config[self::$DIRECTORY] ))
      $config[self::$DIRECTORY]  = self::$DOT;
    return $config;
  }
/**
 * Return formatted output for calendar component property
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.20 - 2017-01-30
 * @param string $label       property name
 * @param string $attributes  property attributes
 * @param string $content     property content
 * @return string
 * @static
 */
  public static function createElement( $label, $attributes=null, $content=null ) {
    $output    = strtoupper( $label );
    if( ! empty( $attributes ))
      $output .= trim( $attributes );
    $output   .= util::$COLON . $content;
    return self::size75( $output );
  }
/**
 * Return formatted output for calendar component property parameters
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-01-29
 * @param array  $params
 * @param array  $ctrKeys
 * @param string $lang
 * @return string
 * @static
 */
  public static function createParams( $params=null, $ctrKeys=null, $lang=null ) {
    static $FMTFMTTYPE = ';FMTTYPE=%s%s';
    static $FMTKEQV    = '%s=%s';
    static $ENCODING   = 'ENCODING';
    static $FMTTYPE    = 'FMTTYPE';
    static $RANGE      = 'RANGE';
    static $RELTYPE    = 'RELTYPE';
    static $PARAMSARRAY = null;
    if( is_null( $PARAMSARRAY ))
      $PARAMSARRAY  = [self::$ALTREP,
                       self::$CN,
                       self::$DIR,
                       $ENCODING,
                       $FMTTYPE,
                       self::$LANGUAGE,
                       $RANGE,
                       $RELTYPE,
                       self::$SENT_BY,
                       self::$TZID,
                       self::$VALUE];
    static $FMTQ    = '"%s"';
    static $FMTQTD  = ';%s=%s%s%s';
    static $FMTCMN  = ';%s=%s';
    if( ! is_array( $params ))
      $params       = [];
    if( ! is_array( $ctrKeys ) || empty( $ctrKeys ))
      $ctrKeys      = [];
    if( empty( $params ) && empty( $ctrKeys ))
      return null;
    $attrLANG = $attr1 = $attr2 = null;
    $hasCNattrKey   = ( in_array( self::$CN,       $ctrKeys ));
    $hasLANGattrKey = ( in_array( self::$LANGUAGE, $ctrKeys ));
    $CNattrExist    = false;
    $xparams        = [];
    $params         = array_change_key_case( $params, CASE_UPPER );
    foreach( $params as $paramKey => $paramValue ) {
      if(( false !== strpos( $paramValue, self::$COLON )) ||
         ( false !== strpos( $paramValue, self::$SEMIC )) ||
         ( false !== strpos( $paramValue, self::$COMMA )))
        $paramValue = sprintf( $FMTQ, $paramValue );
      if( ctype_digit( (string) $paramKey )) {
        $xparams[]  = $paramValue;
        continue;
      }
      if( ! in_array( $paramKey, $PARAMSARRAY ))
        $xparams[$paramKey] = $paramValue;
      else
        $params[$paramKey]  = $paramValue;
    }
    ksort( $xparams, SORT_STRING );
    foreach( $xparams as $paramKey => $paramValue ) {
      $attr2       .= util::$SEMIC;
      $attr2       .= ( ctype_digit( (string) $paramKey ))
                    ? $paramValue
                    : sprintf( $FMTKEQV, $paramKey, $paramValue );
    }
    if( isset( $params[$FMTTYPE] ) &&
           ! in_array( $FMTTYPE, $ctrKeys )) {
      $attr1       .= sprintf( $FMTFMTTYPE, $params[$FMTTYPE],
                                            $attr2 );
      $attr2        = null;
    }
    if( isset( $params[$ENCODING] ) &&
           ! in_array( $ENCODING,   $ctrKeys )) {
      if( !empty( $attr2 )) {
        $attr1     .= $attr2;
        $attr2      = null;
      }
      $attr1       .= sprintf( $FMTCMN, $ENCODING,
                                        $params[$ENCODING] );
    }
    if( isset( $params[self::$VALUE] ) &&
           ! in_array( self::$VALUE,   $ctrKeys ))
      $attr1       .= sprintf( $FMTCMN, self::$VALUE,
                                        $params[self::$VALUE] );
    if( isset( $params[self::$TZID] ) &&
           ! in_array( self::$TZID,    $ctrKeys )) {
      $attr1       .= sprintf( $FMTCMN, self::$TZID,
                                        $params[self::$TZID] );
    }
    if( isset( $params[$RANGE] ) &&
           ! in_array( $RANGE,   $ctrKeys ))
      $attr1       .= sprintf( $FMTCMN, $RANGE,
                                        $params[$RANGE] );
    if( isset( $params[$RELTYPE] ) &&
           ! in_array( $RELTYPE, $ctrKeys ))
      $attr1       .= sprintf( $FMTCMN, $RELTYPE,
                                        $params[$RELTYPE] );
    if( isset( $params[self::$CN] ) &&
       $hasCNattrKey ) {
      $attr1        = sprintf( $FMTCMN, self::$CN,
                                        $params[self::$CN] );
      $CNattrExist  = true;
    }
    if( isset( $params[self::$DIR] ) &&
             in_array( self::$DIR, $ctrKeys )) {
      $delim        = ( false !== strpos( $params[self::$DIR], self::$QQ ))
                    ? null : self::$QQ;
      $attr1       .= sprintf( $FMTQTD, self::$DIR,
                                        $delim,
                                        $params[self::$DIR],
                                        $delim );
    }
    if( isset( $params[self::$SENT_BY] ) &&
             in_array( self::$SENT_BY,  $ctrKeys ))
      $attr1       .= sprintf( $FMTCMN, self::$SENT_BY,
                                        $params[self::$SENT_BY] );
    if( isset( $params[self::$ALTREP] ) &&
             in_array( self::$ALTREP, $ctrKeys )) {
      $delim        = ( false !== strpos( $params[self::$ALTREP], self::$QQ ))
                    ? null : self::$QQ;
      $attr1       .= sprintf( $FMTQTD, self::$ALTREP,
                                        $delim,
                                        $params[self::$ALTREP],
                                        $delim );
    }
    if( isset( $params[self::$LANGUAGE] ) && $hasLANGattrKey )
      $attrLANG    .= sprintf( $FMTCMN, self::$LANGUAGE,
                                        $params[self::$LANGUAGE] );
    elseif(( $CNattrExist || $hasLANGattrKey ) && ! empty( $lang ))
      $attrLANG    .= sprintf( $FMTCMN, self::$LANGUAGE,
                                        $lang );
    return $attr1 . $attrLANG . $attr2;
  }
/**
 * Return (conformed) iCal component property parameters
 *
 * Trim quoted values, default parameters may be set, if missing
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-04-08
 * @param array $params
 * @param array $defaults
 * @return array
 * @static
 */
  public static function setParams( $params, $defaults=null ) {
    if( ! is_array( $params ))
      $params = [];
    $output   = [];
    $params   = array_change_key_case( $params, CASE_UPPER );
    foreach( $params as $paramKey => $paramValue ) {
      if( is_array( $paramValue )) {
        foreach( $paramValue as $pkey => $pValue )
          $paramValue[$pkey]  = trim( $pValue, util::$QQ );
      }
      else
        $paramValue = trim( $paramValue, util::$QQ );
      if( self::$VALUE == $paramKey )
        $output[self::$VALUE] = strtoupper( $paramValue );
      else
        $output[$paramKey] = $paramValue;
    } // end foreach
    if( is_array( $defaults ))
      $output = array_merge( array_change_key_case( $defaults, CASE_UPPER ),
                             $output );
    return ( 0 < count( $output )) ? $output : null;
  }
/**
 * Remove expected key/value from array and returns hitval (if found) else returns elseval
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.4.16 - 2008-11-08
 * @param array  $array    iCal property parameters
 * @param string $expkey   expected key
 * @param string $expval   expected value
 * @param int    $hitVal   return value if found
 * @param int    $elseVal  return value if not found
 * @param int    $preSet   return value if already preset
 * @return int
 * @static
 */
  public static function existRem( & $array,
                                     $expkey,
                                     $expval=false,
                                     $hitVal=null,
                                     $elseVal=null,
                                     $preSet=null ) {
    if( $preSet )
      return $preSet;
    if( ( 0 == count( $array )) || ! is_array( $array ))
      return $elseVal;
    foreach( $array as $key => $value ) {
      if( 0 == strcasecmp( $expkey, $key )) {
        if( ! $expval ||
          ( 0 == strcasecmp( $expval, $value ))) {
          unset( $array[$key] );
          return $hitVal;
        }
      }
    }
    return $elseVal;
  }
/**
 * Delete component property value, managing components with multiple occurencies
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.8.8 - 2011-03-15
 * @param array  $multiprop  component (multi-)property
 * @param int    $propix     removal counter
 * @return bool true
 * @static
 */
  public static function deletePropertyM( & $multiprop, & $propix ) {
    if( isset( $multiprop[$propix] ))
      unset( $multiprop[$propix] );
    if( empty( $multiprop )) {
      $multiprop = null;
      unset( $propix );
      return false;
    }
    return true;
  }
/**
 * Recount property propix, used at consecutive getProperty calls
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-18
 * @param array  $prop     component (multi-)property
 * @param int    $propix   getter counter
 * @return bool true
 * @static
 */
  public static function recountMvalPropix( & $prop, & $propix ) {
    if( ! is_array( $prop ) || empty( $prop ))
      return false;
    $last = key( array_slice( $prop, -1, 1, TRUE ));
    while( ! isset( $prop[$propix] ) &&
                ( $last > $propix  ))
      $propix++;
    return true;
  }
/**
 * Check index and set (an indexed) content in a multiple value array
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-04-08
 * @param array $valArr
 * @param mixed $value
 * @param array $params
 * @param array $defaults
 * @param int $index
 * @static
 */
  public static function setMval( & $valArr,
                                    $value,
                                    $params=null,
                                    $defaults=null,
                                    $index=null ) {
    if( ! is_array( $valArr ))
      $valArr = [];
    if( ! is_null( $params ))
      $params = self::setParams( $params, $defaults );
    if( is_null( $index )) { // i.e. next
      $valArr[] = [self::$LCvalue  => $value,
                   self::$LCparams => $params];
      return;
    }
    $index    = $index - 1;
    if( isset( $valArr[$index] )) { // replace
      $valArr[$index] = [self::$LCvalue  => $value,
                         self::$LCparams => $params];
      return;
    }
    $valArr[$index] = [self::$LCvalue  => $value,
                       self::$LCparams => $params];
    ksort( $valArr ); // order
    return true;
  }
/**
 * Return datestamp for calendar component object instance dtstamp
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @return array
 * @static
 */
  public static function makeDtstamp() {
    $date = explode( self::$MINUS,  gmdate( self::$YMDHIS3, time()));
    return [self::$LCvalue  => [self::$LCYEAR  => $date[0],
                                self::$LCMONTH => $date[1],
                                self::$LCDAY   => $date[2],
                                self::$LCHOUR  => $date[3],
                                self::$LCMIN   => $date[4],
                                self::$LCSEC   => $date[5],
                                self::$LCtz    => self::$Z],
            self::$LCparams => null];
  }
/**
 * Return an unique id for a calendar component object instance
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param string $unique_id
 * @return array
 * @static
 */
  public static function makeUid( $unique_id ) {
    static $FMT     = '%s-%s@%s';
    static $TMDTHIS = 'Ymd\THisT';
    return [self::$LCvalue  => sprintf( $FMT, date( $TMDTHIS ),
                                              substr( microtime(), 2, 4) . self::getRandChars( 6 ),
                                              $unique_id ),
            self::$LCparams => null];
  }
/**
 * Return a random (and unique) sequence of characters
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-18
 * @param int $cnt
 * @return string
 * @access private
 * @static
 */
  private static function getRandChars( $cnt ) {
    $cnt  = (int) floor( $cnt / 2 );
    $x    = 0;
    do {
      $randChars = bin2hex( openssl_random_pseudo_bytes( $cnt, $cStrong ));
      $x += 1;
    } while(( 3 > $x ) && ( false == $cStrong ));
    return $randChars;
  }
/**
 * Return true if a date property has NO date parts
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param array  $content
 * @return bool
 * @static
 */
  public static function hasNodate( $content ) {
    return( ! isset( $content[self::$LCvalue][self::$LCYEAR] )  &&
            ! isset( $content[self::$LCvalue][self::$LCMONTH] ) &&
            ! isset( $content[self::$LCvalue][self::$LCDAY] )   &&
            ! isset( $content[self::$LCvalue][self::$LCHOUR] )  &&
            ! isset( $content[self::$LCvalue][self::$LCMIN] )   &&
            ! isset( $content[self::$LCvalue][self::$LCSEC] ));
  }
/**
 * Return true if property parameter VALUE is set to argument, otherwise false
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-12
 * @param array  $content
 * @param string $arg
 * @return bool
 * @static
 */
  public static function isParamsValueSet( array $content, $arg ) {
    return (   isset( $content[self::$LCparams][self::$VALUE] ) &&
            ( $arg == $content[self::$LCparams][self::$VALUE] ));
  }
/**
 * Return bool true if name is X-prefixed
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param string $name
 * @return bool
 * @static
 */
  public static function isXprefixed( $name ) {
    static $X_ = 'X-';
    return ( 0 == strcasecmp( $X_, substr( $name, 0, 2 )));
  }
/**
 * Return bool true if object class is a DateTime (sub-)class
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.5 - 2017-04-14
 * @param object $object
 * @return bool
 * @static
 */
  public static function isDateTimeClass( $object ) {
    static $DATETIMEobj     = 'DateTime';
    return ( is_object( $object ) &&
           ( 0 == strcasecmp( $DATETIMEobj, substr( get_class( $object ), -8 ))));
  }
/**
 * Return property name  and  opt.params and property value
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-16
 * @param string $row
 * @return string
 * @static
 */
  public static function getPropName( $row ) {
    static $COLONSEMICARR = [':', ';'];
    $propName = null;
    $cix      = 0;
    $len      = strlen( $row );
    while( $cix < $len ) {
      if( in_array( $row[$cix], $COLONSEMICARR ))
        break;
      $propName .= $row[$cix];
      $cix++;
    } // end while...
    if( isset( $row[$cix] ))
      $row = substr( $row, $cix);
    else {
      $propName = self::trimTrailNL( $propName ); // property without colon and content
      $row      = null;
    }
    return [$propName, $row];
  }
/**
 * Return array from content split by '\,'
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-16
 * @param string $content
 * @return array
 * @static
 */
  public static function commaSplit( $content ) {
    static $DBBS = "\\";
    $output      = [0 => null];
    $cix = $lix  = 0;
    $len         = strlen( $content );
    while( $lix < $len ) {
      if(( self::$COMMA  ==  $content[$lix] ) &&
                ( $DBBS  !=  $content[( $lix - 1 )]))
        $output[++$cix]   = null;
      else
        $output[$cix] .= $content[$lix];
      $lix++;
    }
    return array_filter( $output );
  }
/**
 * Return concatenated calendar rows, one row for each property
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param array $rows
 * @return array
 * @static
 */
  public static function concatRows( $rows ) {
    $output = [];
    $cnt    = count( $rows );
    for( $i = 0; $i < $cnt; $i++ ) {
      $line = rtrim( $rows[$i], self::$CRLF );
      while(  isset( $rows[$i+1] ) &&
           !  empty( $rows[$i+1] ) &&
           ( self::$SP1 == $rows[$i+1]{0} ))
        $line .= rtrim( substr( $rows[++$i], 1 ), self::$CRLF );
      $output[] = $line;
    }
    return $output;
  }
/**
 * Return string with removed ical line folding
 *
 * Remove any line-endings that may include spaces or tabs
 * and convert all line endings (iCal default '\r\n'),
 * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-03-01
 * @param string $text
 * @return string
 * @static
 */
  public static function convEolChar( & $text ) {
    static $BASEDELIM  = null;
    static $BASEDELIMs = null;
    static $EMPTYROW   = null;
    static $FMT        = '%1$s%2$75s%1$s';
    static $SP0        = '';
    static $CRLFs      = ["\r\n", "\n\r", "\n", "\r"];
    static $CRLFexts   = ["\r\n ", "\n\r\t"];
            /* fix dummy line separator etc */
    if( empty( $BASEDELIM )) {
      $BASEDELIM  = self::getRandChars( 16 );
      $BASEDELIMs = $BASEDELIM . $BASEDELIM;
      $EMPTYROW   = sprintf( $FMT, $BASEDELIM, $SP0 );
    }
            /* fix eol chars */
    $text = str_replace( $CRLFs, $BASEDELIM, $text );
            /* fix empty lines */
    $text = str_replace( $BASEDELIMs, $EMPTYROW, $text );
            /* fix line folding */
    $text = str_replace( $BASEDELIM, util::$CRLF, $text );
    $text = str_replace( $CRLFexts, null, $text );
            /* split in component/property lines */
    return explode( util::$CRLF, $text );
  }
/**
 * Return wrapped string with (byte oriented) line breaks at pos 75
 *
 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
 * break. Long content lines SHOULD be split into a multiple line
 * representations using a line "folding" technique. That is, a long
 * line can be split between any two characters by inserting a CRLF
 * immediately followed by a single linear white space character (i.e.,
 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
 * of CRLF followed immediately by a single linear white space character
 * is ignored (i.e., removed) when processing the content type.
 *
 * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
 * the reserved expression "\n" in the arg $string could be broken up by the
 * folding of lines, causing ambiguity in the return string.
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-03-01
 * @param string $string
 * @return string
 * @access private
 * @static
 * @link http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
 */
  private static function size75( $string ) {
    static $DBS      = '\\';
    static $LCN      = 'n';
    static $UCN      = 'N';
    static $SPBSLCN  = ' \n';
    static $SP1      = ' ';
    $tmp             = $string;
    $string          = null;
    $cCnt = $x       = 0;
    while( true ) {
      if( ! isset( $tmp[$x] )) {
        $string     .= util::$CRLF;        // loop breakes here
        break;
      }
      elseif(( 74    <= $cCnt ) &&
             ( $DBS  == $tmp[$x] ) &&
             (( $LCN == $tmp[$x+1] ) || ( $UCN == $tmp[$x+1] ))) {
        $string     .= util::$CRLF . $SPBSLCN; // don't break lines inside '\n'
        $x          += 2;
        if( ! isset( $tmp[$x] )) {
          $string   .= util::$CRLF;
          break;
        }
        $cCnt        = 3;
      }
      elseif( 75    <= $cCnt ) {
        $string     .= util::$CRLF . $SP1;
        $cCnt        = 1;
      }
      $byte          = ord( $tmp[$x] );
      $string       .= $tmp[$x];
      switch( true ) {
        case(( $byte >= 0x20 ) && ( $byte <= 0x7F )) :
          $cCnt     += 1;                 // characters U-00000000 - U-0000007F (same as ASCII)
          break;                          // add a one byte character
        case(( $byte & 0xE0) == 0xC0 ) :  // characters U-00000080 - U-000007FF, mask 110XXXXX
          if( isset( $tmp[$x+1] )) {
            $cCnt   += 1;
            $string .= $tmp[$x+1];
            $x      += 1;                 // add a two bytes character
          }
          break;
        case(( $byte & 0xF0 ) == 0xE0 ) : // characters U-00000800 - U-0000FFFF, mask 1110XXXX
          if( isset( $tmp[$x+2] )) {
            $cCnt   += 1;
            $string .= $tmp[$x+1] . $tmp[$x+2];
            $x      += 2;                 // add a three bytes character
          }
          break;
        case(( $byte & 0xF8 ) == 0xF0 ) : // characters U-00010000 - U-001FFFFF, mask 11110XXX
          if( isset( $tmp[$x+3] )) {
            $cCnt   += 1;
            $string .= $tmp[$x+1] . $tmp[$x+2] . $tmp[$x+3];
            $x      += 3;                 // add a four bytes character
          }
          break;
        case(( $byte & 0xFC ) == 0xF8 ) : // characters U-00200000 - U-03FFFFFF, mask 111110XX
          if( isset( $tmp[$x+4] )) {
            $cCnt   += 1;
            $string .= $tmp[$x+1] . $tmp[$x+2] . $tmp[$x+3] . $tmp[$x+4];
            $x      += 4;                 // add a five bytes character
          }
          break;
        case(( $byte & 0xFE ) == 0xFC ) : // characters U-04000000 - U-7FFFFFFF, mask 1111110X
          if( isset( $tmp[$x+5] )) {
            $cCnt   += 1;
            $string .= $tmp[$x+1] . $tmp[$x+2] . $tmp[$x+3] . $tmp[$x+4] . $tmp[$x+5];
            $x      += 5;                 // add a six bytes character
          }
          break;
        default:                          // add any other byte without counting up $cCnt
          break;
      } // end switch( true )
      $x            += 1;                 // next 'byte' to test
    } // end while( true )
    return $string;
  }
/**
 * Separate (string) to iCal property value and attributes
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.13 - 2017-05-02
 * @param string $line      property content
 * @param array  $propAttr  property parameters
 * @static
 * @TODO same as in util::calAddressCheck() ??
 */
  public static function splitContent( & $line, & $propAttr=null ) {
    static $CSS    = '://';
    static $MSTZ   = ['utc-', 'utc+', 'gmt-', 'gmt+'];
    static $PROTO3 = ['fax:', 'cid:', 'sms:', 'tel:', 'urn:'];
    static $PROTO4 = ['crid:', 'news:', 'pres:'];
    static $PROTO6 = ['mailto:'];
    static $EQ     = '=';
    $attr         = [];
    $attrix       = -1;
    $clen         = strlen( $line );
    $WithinQuotes = false;
    $len          = strlen( $line );
    $cix          = 0;
    while( $cix < $len ) {
      if(  ! $WithinQuotes  &&   ( self::$COLON == $line[$cix] )            &&
                                 ( substr( $line,$cix,  3 )   != $CSS )     &&
         ( ! in_array( strtolower( substr( $line,$cix - 6, 4 )), $MSTZ ))   &&
         ( ! in_array( strtolower( substr( $line,$cix - 3, 4 )), $PROTO3 )) &&
         ( ! in_array( strtolower( substr( $line,$cix - 4, 5 )), $PROTO4 )) &&
         ( ! in_array( strtolower( substr( $line,$cix - 6, 7 )), $PROTO6 ))) {
        $attrEnd = true;
        if(( $cix < ( $clen - 4 )) &&
             ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
          for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
            if( $CSS == substr( $line, $c2ix - 2, 3 )) {
              $attrEnd = false;
              break; // an URI with a portnr!!
            }
          }
        }
        if( $attrEnd) {
          $line = substr( $line, ( $cix + 1 ));
          break;
        }
        $cix++;
      } // end if(  ! $WithinQuotes...
      if( self::$QQ    == $line[$cix] ) // '"'
        $WithinQuotes = ! $WithinQuotes;
      if( self::$SEMIC == $line[$cix] ) // ';'
        $attr[++$attrix] = null;
      else {
        if( 0 > $attrix )
          $attrix = 0;
        $attr[$attrix] .= $line[$cix];
      }
      $cix++;
    } // end while...
            /* make attributes in array format */
    $propAttr = [];
    foreach( $attr as $attribute ) {
      $attrsplit = explode( $EQ, $attribute, 2 );
      if( 1 < count( $attrsplit ))
        $propAttr[$attrsplit[0]] = $attrsplit[1];
    }
  }
/**
 * Special characters management output
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-17
 * @param string $string
 * @return string
 * @static
 */
  public static function strrep( $string ) {
    static $BSLCN    = '\n';
    static $SPECCHAR = ['n', 'N', 'r', ',', ';'];
    static $DBS      = "\\";
    static $SQ       = "'";
    static $BSCOMMA  = '\,';
    static $BSSEMIC  = '\;';
    static $BSLCR    = "\r";
    static $QBSLCN   = "\n";
    static $BSUCN    = '\N';
    $string = (string) $string;
    $strLen = strlen( $string );
    $pos = 0;
    while( $pos < $strLen ) {
      if( false === ( $pos = strpos( $string, $DBS, $pos )))
        break;
      if( ! in_array( substr( $string, $pos, 1 ), $SPECCHAR )) {
        $string = substr( $string, 0, $pos ) . $DBS . substr( $string, ( $pos + 1 ));
        $pos += 1;
      }
      $pos += 1;
    }
    if( false !== strpos( $string, self::$QQ ))
      $string   = str_replace( self::$QQ,    $SQ,      $string);
    if( false !== strpos( $string, self::$COMMA ))
      $string   = str_replace( self::$COMMA, $BSCOMMA, $string);
    if( false !== strpos( $string, self::$SEMIC ))
      $string   = str_replace( self::$SEMIC, $BSSEMIC, $string);
    if( false !== strpos( $string, self::$CRLF ))
      $string   = str_replace( self::$CRLF,  $BSLCN,   $string);
    elseif( false !== strpos( $string, $BSLCR ))
      $string   = str_replace( $BSLCR,       $BSLCN,   $string);
    elseif( false !== strpos( $string, $QBSLCN ))
      $string   = str_replace( $QBSLCN,      $BSLCN,   $string);
    if( false !== strpos( $string, $BSUCN ))
      $string   = str_replace( $BSUCN,       $BSLCN,   $string);
    $string     = str_replace( self::$CRLF,  $BSLCN,   $string);
    return $string;
  }
/**
 * Special characters management input
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.2 - 2015-06-25
 * @param string $string
 * @return string
 * @static
 */
  public static function strunrep( $string ) {
    static $BS4     = '\\\\';
    static $BS2     = '\\';
    static $BSCOMMA = '\,';
    static $BSSEMIC = '\;';
    $string = str_replace( $BS4,     $BS2,         $string);
    $string = str_replace( $BSCOMMA, self::$COMMA, $string);
    $string = str_replace( $BSSEMIC, self::$SEMIC, $string);
    return $string;
  }
/**
 * Return string with trimmed trailing \n
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param string $value
 * @return string
 * @static
 */
  public static function trimTrailNL( $value ) {
    static $NL = '\n';
    if( $NL == strtolower( substr( $value, -2 )))
      $value = substr( $value, 0, ( strlen( $value ) -2 ));
    return $value;
  }
/**
 * Return internal date (format) with parameters based on input date
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.21.11 - 2015-03-21
 * @param mixed  $year
 * @param mixed  $month
 * @param int    $day
 * @param int    $hour
 * @param int    $min
 * @param int    $sec
 * @param string $tz
 * @param array  $params
 * @param string $caller
 * @param string $objName
 * @param string $tzid
 * @return array
 * @static
 */
  public static function setDate( $year,
                                  $month=null,
                                  $day=null,
                                  $hour=null,
                                  $min=null,
                                  $sec=null,
                                  $tz=null,
                                  $params=null,
                                  $caller=null,
                                  $objName=null,
                                  $tzid=null ) {
    $input = $parno = null;
    $localtime = (( self::$DTSTART == $caller ) &&
                  in_array( $objName, self::$TZCOMPS )) ? true : false;
    self::strDate2arr( $year );
    if( self::isArrayDate( $year )) {
      $input[self::$LCvalue]  = self::chkDateArr( $year );
      if( 100 > $input[self::$LCvalue][self::$LCYEAR] )
        $input[self::$LCvalue][self::$LCYEAR] += 2000;
      if( $localtime )
        unset( $month[self::$VALUE], $month[self::$TZID] );
      elseif( ! isset( $month[self::$TZID] ) && isset( $tzid ))
        $month[self::$TZID] = $tzid;
      if(         isset( $input[self::$LCvalue][self::$LCtz] ) &&
        self::isOffset( $input[self::$LCvalue][self::$LCtz] ))
        unset( $month[self::$TZID] );
      elseif(   ! isset( $input[self::$LCvalue][self::$LCtz] ) &&
                  isset( $month[self::$TZID] ) &&
        self::isOffset( $month[self::$TZID] )) {
        $input[self::$LCvalue][self::$LCtz] = $month[self::$TZID];
        unset( $month[self::$TZID] );
      }
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
      $hitval       = ( isset( $input[self::$LCvalue][self::$LCtz] )) ? 7 : 6;
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE_TIME,
                                      $hitval );
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE,
                                      3,
                                      count( $input[self::$LCvalue] ),
                                      $parno );
      if( 6 > $parno )
        unset( $input[self::$LCvalue][self::$LCtz],
               $input[self::$LCparams][self::$TZID],
               $tzid );
      if(( 6 <= $parno ) &&
            isset( $input[self::$LCvalue][self::$LCtz] ) &&
          ( self::$Z != $input[self::$LCvalue][self::$LCtz] ) &&
          self::isOffset( $input[self::$LCvalue][self::$LCtz] )) {
        $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                   (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                   (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                   (int) $input[self::$LCvalue][self::$LCDAY],
                                                                   (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                   (int) $input[self::$LCvalue][self::$LCMIN],
                                                                   (int) $input[self::$LCvalue][self::$LCSEC],
                                                                         $input[self::$LCvalue][self::$LCtz] ),
                                                          $parno );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT],
               $input[self::$LCparams][self::$TZID] );
      }
      if(          isset( $input[self::$LCvalue][self::$LCtz] ) &&
        ! self::isOffset( $input[self::$LCvalue][self::$LCtz] )) {
        $input[self::$LCparams][self::$TZID] = $input[self::$LCvalue][self::$LCtz];
        unset( $input[self::$LCvalue][self::$LCtz] );
      }
    } // end if( self::isArrayDate( $year ))
    elseif( self::isArrayTimestampDate( $year )) {
      if( $localtime )
        unset( $month[self::$LCvalue], $month[self::$TZID] );
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE,
                                      3 );
      $hitval       = 7;
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE_TIME,
                                      $hitval,
                                      $parno );
      if( isset( $year[self::$LCtz] ) && ! empty( $year[self::$LCtz] )) {
        if( !self::isOffset( $year[self::$LCtz] )) {
          $input[self::$LCparams][self::$TZID] = $year[self::$LCtz];
          unset( $year[self::$LCtz], $tzid );
        }
        else {
          if( isset( $input[self::$LCparams][self::$TZID] ) &&
            ! empty( $input[self::$LCparams][self::$TZID] )) {
            if( !self::isOffset( $input[self::$LCparams][self::$TZID] ))
              unset( $tzid );
            else
              unset( $input[self::$LCparams][self::$TZID]);
          }
          elseif( isset( $tzid ) && ! self::isOffset( $tzid ))
            $input[self::$LCparams][self::$TZID] = $tzid;
        }
      }
      elseif( isset( $input[self::$LCparams][self::$TZID] ) &&
            ! empty( $input[self::$LCparams][self::$TZID] )) {
        if( self::isOffset( $input[self::$LCparams][self::$TZID] )) {
          $year[self::$LCtz] = $input[self::$LCparams][self::$TZID];
          unset( $input[self::$LCparams][self::$TZID]);
          if( isset( $tzid ) &&
            ! empty( $tzid ) &&
            ! self::isOffset( $tzid ))
            $input[self::$LCparams][self::$TZID] = $tzid;
        }
      }
      elseif( isset( $tzid ) && ! empty( $tzid )) {
        if( self::isOffset( $tzid )) {
          $year[self::$LCtz] = $tzid;
          unset( $input[self::$LCparams][self::$TZID]);
        }
        else
          $input[self::$LCparams][self::$TZID] = $tzid;
      }
      $input[self::$LCvalue]  = self::timestamp2date( $year, $parno );
    } // end elseif( self::isArrayTimestampDate( $year ))
    elseif( 8 <= strlen( trim((string) $year ))) { // string ex. "2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]"
      if( $localtime )
        unset( $month[self::$LCvalue], $month[self::$TZID] );
      elseif( ! isset( $month[self::$TZID] ) && ! empty( $tzid ))
        $month[self::$TZID] = $tzid;
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE_TIME,
                                      7,
                                      $parno );
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE,
                                      3,
                                      $parno,
                                      $parno );
      $input[self::$LCvalue]  = self::strDate2ArrayDate( $year, $parno );
      if( 3 == $parno )
        unset( $input[self::$LCvalue][self::$LCtz],
               $input[self::$LCparams][self::$TZID] );
      unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
      if( isset( $input[self::$LCvalue][self::$LCtz] )) {
        if( self::isOffset( $input[self::$LCvalue][self::$LCtz] )) {
          $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                     (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                     (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                     (int) $input[self::$LCvalue][self::$LCDAY],
                                                                     (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                     (int) $input[self::$LCvalue][self::$LCMIN],
                                                                     (int) $input[self::$LCvalue][self::$LCSEC],
                                                                           $input[self::$LCvalue][self::$LCtz] ),
                                                            7 );
          unset( $input[self::$LCvalue][self::$UNPARSEDTEXT],
                 $input[self::$LCparams][self::$TZID] );
        }
        else {
          $input[self::$LCparams][self::$TZID] = $input[self::$LCvalue][self::$LCtz];
          unset( $input[self::$LCvalue][self::$LCtz] );
        }
      }
      elseif(    isset( $input[self::$LCparams][self::$TZID] ) &&
        self::isOffset( $input[self::$LCparams][self::$TZID] )) {
        $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                   (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                   (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                   (int) $input[self::$LCvalue][self::$LCDAY],
                                                                   (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                   (int) $input[self::$LCvalue][self::$LCMIN],
                                                                   (int) $input[self::$LCvalue][self::$LCSEC],
                                                                         $input[self::$LCparams][self::$TZID] ),
                                                          7 );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT],
               $input[self::$LCparams][self::$TZID] );
      }
    } // end elseif( 8 <= strlen( trim((string) $year )))
    else { // using all (?) args
      if( 100 > $year )
        $year += 2000;
      if( is_array( $params ))
        $input[self::$LCparams] = self::setParams( $params,
                                                   self::$DEFAULTVALUEDATETIME );
      elseif( is_array( $tz )) {
        $input[self::$LCparams] = self::setParams( $tz,
                                                   self::$DEFAULTVALUEDATETIME );
        $tz = false;
      }
      elseif( is_array( $hour )) {
        $input[self::$LCparams] = self::setParams( $hour,
                                                   self::$DEFAULTVALUEDATETIME );
        $hour = $min = $sec = $tz = false;
      }
      if( $localtime )
        unset ( $input[self::$LCparams][self::$LCvalue],
                $input[self::$LCparams][self::$TZID] );
      elseif( ! isset( $tz ) &&
              ! isset( $input[self::$LCparams][self::$TZID] ) &&
              ! empty( $tzid ))
        $input[self::$LCparams][self::$TZID] = $tzid;
      elseif( isset( $tz ) && self::isOffset( $tz ))
        unset( $input[self::$LCparams][self::$TZID] );
      elseif(     isset( $input[self::$LCparams][self::$TZID] ) &&
        self::isOffset( $input[self::$LCparams][self::$TZID] )) {
        $tz         = $input[self::$LCparams][self::$TZID];
        unset( $input[self::$LCparams][self::$TZID] );
      }
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE,
                                      3 );
      $hitval       = ( self::isOffset( $tz )) ? 7 : 6;
      $parno        = self::existRem( $input[self::$LCparams],
                                      self::$VALUE,
                                      self::$DATE_TIME,
                                      $hitval,
                                      $parno,
                                      $parno );
      $input[self::$LCvalue]  = [self::$LCYEAR  => $year,
                                 self::$LCMONTH => $month,
                                 self::$LCDAY   => $day];
      if( 3 != $parno ) {
        $input[self::$LCvalue][self::$LCHOUR] = ( $hour ) ? $hour : '0';
        $input[self::$LCvalue][self::$LCMIN]  = ( $min )  ? $min  : '0';
        $input[self::$LCvalue][self::$LCSEC]  = ( $sec )  ? $sec  : '0';
        if( ! empty( $tz ))
          $input[self::$LCvalue][self::$LCtz] = $tz;
        $strdate       = self::date2strdate( $input[self::$LCvalue], $parno );
        if( ! empty( $tz ) && !self::isOffset( $tz ))
          $strdate    .= ( self::$Z == $tz ) ? $tz : ' '.$tz;
        $input[self::$LCvalue] = self::strDate2ArrayDate( $strdate, $parno );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
        if( isset( $input[self::$LCvalue][self::$LCtz] )) {
          if( self::isOffset( $input[self::$LCvalue][self::$LCtz] )) {
            $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                       (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                       (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                       (int) $input[self::$LCvalue][self::$LCDAY],
                                                                       (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                       (int) $input[self::$LCvalue][self::$LCMIN],
                                                                       (int) $input[self::$LCvalue][self::$LCSEC],
                                                                             $input[self::$LCvalue][self::$LCtz] ),
                                                              7 );
            unset( $input[self::$LCvalue][self::$UNPARSEDTEXT],
                   $input[self::$LCparams][self::$TZID] );
          }
          else {
            $input[self::$LCparams][self::$TZID] = $input[self::$LCvalue][self::$LCtz];
            unset( $input[self::$LCvalue][self::$LCtz] );
          }
        }
        elseif( isset( $input[self::$LCparams][self::$TZID] ) &&
          self::isOffset( $input[self::$LCparams][self::$TZID] )) {
          $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                     (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                     (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                     (int) $input[self::$LCvalue][self::$LCDAY],
                                                                     (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                     (int) $input[self::$LCvalue][self::$LCMIN],
                                                                     (int) $input[self::$LCvalue][self::$LCSEC],
                                                                           $input[self::$LCparams][self::$TZID] ),
                                                            7 );
          unset( $input[self::$LCvalue][self::$UNPARSEDTEXT],
                 $input[self::$LCparams][self::$TZID] );
        }
      }
    } // end else (i.e. using all arguments)
    if(( 3 == $parno ) || self::isParamsValueSet( $input, self::$DATE )) {
      $input[self::$LCparams][self::$VALUE] = self::$DATE;
      unset( $input[self::$LCvalue][self::$LCHOUR],
             $input[self::$LCvalue][self::$LCMIN],
             $input[self::$LCvalue][self::$LCSEC],
             $input[self::$LCvalue][self::$LCtz],
             $input[self::$LCparams][self::$TZID] );
    }
    elseif( isset( $input[self::$LCparams][self::$TZID] )) {
      if(( 0 == strcasecmp( self::$UTC, $input[self::$LCparams][self::$TZID] )) ||
         ( 0 == strcasecmp( self::$GMT, $input[self::$LCparams][self::$TZID] ))) {
        $input[self::$LCvalue][self::$LCtz] = self::$Z;
        unset( $input[self::$LCparams][self::$TZID] );
      }
      else
        unset( $input[self::$LCvalue][self::$LCtz] );
    }
    elseif( isset( $input[self::$LCvalue][self::$LCtz] )) {
      if(( 0 == strcasecmp( self::$UTC, $input[self::$LCvalue][self::$LCtz] )) ||
         ( 0 == strcasecmp( self::$GMT, $input[self::$LCvalue][self::$LCtz] )))
        $input[self::$LCvalue][self::$LCtz] = self::$Z;
      if( self::$Z != $input[self::$LCvalue][self::$LCtz] ) {
        $input[self::$LCparams][self::$TZID] = $input[self::$LCvalue][self::$LCtz];
        unset( $input[self::$LCvalue][self::$LCtz] );
      }
      else
        unset( $input[self::$LCparams][self::$TZID] );
    }
    if( $localtime )
      unset( $input[self::$LCvalue][self::$LCtz], $input[self::$LCparams][self::$TZID] );
    return $input;
  }
/**
 * Return input (UTC) date to internal date with parameters
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-17
 * @param mixed $year
 * @param mixed $month
 * @param int   $day
 * @param int   $hour
 * @param int   $min
 * @param int   $sec
 * @param array $params
 * @return array
 * @static
 */
  public static function setDate2( $year,
                                   $month=null,
                                   $day=null,
                                   $hour=null,
                                   $min=null,
                                   $sec=null,
                                   $params=null ) {
    $input = null;
    self::strDate2arr( $year );
    if( self::isArrayDate( $year )) {
      $input[self::$LCvalue]  = self::chkDateArr( $year, 7 );
      if( isset( $input[self::$LCvalue][self::$LCYEAR] ) &&
         ( 100 > $input[self::$LCvalue][self::$LCYEAR] ))
        $input[self::$LCvalue][self::$LCYEAR] += 2000;
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
      if( isset( $input[self::$LCvalue][self::$LCtz] ) &&
          self::isOffset( $input[self::$LCvalue][self::$LCtz] ))
        $tzid = $input[self::$LCvalue][self::$LCtz];
      elseif( isset( $input[self::$LCparams][self::$TZID] ) &&
        self::isOffset( $input[self::$LCparams][self::$TZID] ))
        $tzid = $input[self::$LCparams][self::$TZID];
      else
        $tzid = null;
      if( ! empty( $tzid ) && ( self::$Z != $tzid ) && self::isOffset( $tzid )) {
        $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                   (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                   (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                   (int) $input[self::$LCvalue][self::$LCDAY],
                                                                   (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                   (int) $input[self::$LCvalue][self::$LCMIN],
                                                                   (int) $input[self::$LCvalue][self::$LCSEC],
                                                                         $tzid ),
                                                           7 );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
      }
    } // end if( self::isArrayDate( $year ))
    elseif( self::isArrayTimestampDate( $year )) {
      if( isset( $year[self::$LCtz] ) &&
         ! self::isOffset( $year[self::$LCtz] ))
        $year[self::$LCtz]    = self::$UTC;
      elseif( isset( $input[self::$LCparams][self::$TZID] ) &&
        self::isOffset( $input[self::$LCparams][self::$TZID] ))
        $year[self::$LCtz]    = $input[self::$LCparams][self::$TZID];
      else
        $year[self::$LCtz]    = self::$UTC;
      $input[self::$LCvalue]  = self::timestamp2date( $year, 7 );
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
    } // end elseif( self::isArrayTimestampDate( $year ))
    elseif( 8 <= strlen( trim((string) $year ))) { // ex. 2006-08-03 10:12:18
      $input[self::$LCvalue]  = self::strDate2ArrayDate( $year, 7 );
      unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
      $input[self::$LCparams] = self::setParams( $month,
                                                 self::$DEFAULTVALUEDATETIME );
      if(( ! isset( $input[self::$LCvalue][self::$LCtz] ) ||
             empty( $input[self::$LCvalue][self::$LCtz] )) &&
         isset( $input[self::$LCparams][self::$TZID] ) &&
         self::isOffset( $input[self::$LCparams][self::$TZID] )) {
        $input[self::$LCvalue] = self::strDate2ArrayDate( sprintf( self::$YMDHISE,
                                                                   (int) $input[self::$LCvalue][self::$LCYEAR],
                                                                   (int) $input[self::$LCvalue][self::$LCMONTH],
                                                                   (int) $input[self::$LCvalue][self::$LCDAY],
                                                                   (int) $input[self::$LCvalue][self::$LCHOUR],
                                                                   (int) $input[self::$LCvalue][self::$LCMIN],
                                                                   (int) $input[self::$LCvalue][self::$LCSEC],
                                                                         $input[self::$LCparams][self::$TZID] ),
                                                           7 );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
      }
    } // end elseif( 8 <= strlen( trim((string) $year )))
    else {
      if( 100 > $year )
        $year += 2000;
      $input[self::$LCvalue]  = [self::$LCYEAR  => $year,
                                 self::$LCMONTH => $month,
                                 self::$LCDAY   => $day,
                                 self::$LCHOUR  => $hour,
                                 self::$LCMIN   => $min,
                                 self::$LCSEC   => $sec];
      if(  isset( $tz ))
        $input[self::$LCvalue][self::$LCtz] = $tz;
      if(( isset( $tz ) && self::isOffset( $tz )) ||
         ( isset( $input[self::$LCparams][self::$TZID] ) &&
           self::isOffset( $input[self::$LCparams][self::$TZID] ))) {
          if( ! isset( $tz ) &&
            isset( $input[self::$LCparams][self::$TZID] ) &&
            self::isOffset( $input[self::$LCparams][self::$TZID] ))
            $input[self::$LCvalue][self::$LCtz] = $input[self::$LCparams][self::$TZID];
          unset( $input[self::$LCparams][self::$TZID] );
        $strdate        = self::date2strdate( $input[self::$LCvalue], 7 );
        $input[self::$LCvalue] = self::strDate2ArrayDate( $strdate, 7 );
        unset( $input[self::$LCvalue][self::$UNPARSEDTEXT] );
      }
      $input[self::$LCparams] = self::setParams( $params,
                                                 self::$DEFAULTVALUEDATETIME );
    } // end else
    unset( $input[self::$LCparams][self::$VALUE], $input[self::$LCparams][self::$TZID]  );
    if( ! isset( $input[self::$LCvalue][self::$LCHOUR] ))
      $input[self::$LCvalue][self::$LCHOUR] = 0;
    if( ! isset( $input[self::$LCvalue][self::$LCMIN] ))
      $input[self::$LCvalue][self::$LCMIN]  = 0;
    if( ! isset( $input[self::$LCvalue][self::$LCSEC] ))
      $input[self::$LCvalue][self::$LCSEC]  = 0;
    $input[self::$LCvalue][self::$LCtz] = self::$Z;
    return $input;
  }
/**
 * Return array (in internal format) for an input date-time/date array (keyed or unkeyed)
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-19
 * @param array $datetime
 * @param int $parno  default null, 3: DATE(Ymd), 6: YmdHis, 7: YmdHis + offset/timezone
 * @return array
 * @static
 */
  public static function chkDateArr( $datetime, $parno=null ) {
    static $PLUS4ZERO  = '+0000';
    static $MINUS4ZERO = '-0000';
    static $PLUS6ZERO  = '+000000';
    static $MINUS6ZERO = '-000000';
    $output = [];
    if(( is_null( $parno ) || ( 6 <= $parno )) &&
         isset( $datetime[3] ) &&
       ! isset( $datetime[4] )) { // Y-m-d with tz
      $temp        = $datetime[3];
      $datetime[3] = $datetime[4] = $datetime[5] = 0;
      $datetime[6] = $temp;
    }
    foreach( $datetime as $dateKey => $datePart ) {
      switch ( $dateKey ) {
        case '0':
        case self::$LCYEAR :
          $output[self::$LCYEAR]  = $datePart;
          break;
        case '1':
        case self::$LCMONTH :
          $output[self::$LCMONTH] = $datePart;
          break;
        case '2':
        case self::$LCDAY :
          $output[self::$LCDAY]   = $datePart;
          break;
      }
      if( 3 != $parno ) {
        switch ( $dateKey ) {
          case '0':
          case '1':
          case '2':
            break;
          case '3':
          case self::$LCHOUR:
            $output[self::$LCHOUR]  = $datePart;
            break;
          case '4':
          case self::$LCMIN :
            $output[self::$LCMIN]   = $datePart;
            break;
          case '5':
          case self::$LCSEC :
            $output[self::$LCSEC]   = $datePart;
            break;
          case '6':
          case self::$LCtz  :
            $output[self::$LCtz]    = $datePart;
            break;
        }
      }
    }
    if( 3 != $parno ) {
      if( ! isset( $output[self::$LCHOUR] ))
        $output[self::$LCHOUR] = 0;
      if( ! isset( $output[self::$LCMIN]  ))
        $output[self::$LCMIN]  = 0;
      if( ! isset( $output[self::$LCSEC]  ))
        $output[self::$LCSEC]  = 0;
      if( isset( $output[self::$LCtz] ) &&
        (( $PLUS4ZERO  == $output[self::$LCtz] ) ||
         ( $MINUS4ZERO == $output[self::$LCtz] ) ||
         ( $PLUS6ZERO  == $output[self::$LCtz] ) ||
         ( $MINUS6ZERO == $output[self::$LCtz] )))
        $output[self::$LCtz]   = self::$Z;
    }
    return $output;
  }
/**
 * Return iCal formatted string for (internal array) date/date-time
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-24
 * @param array   $datetime
 * @param int     $parno     default 6
 * @return string
 * @static
 */
  public static function date2strdate( $datetime, $parno=null ) {
    static $SECONDS = ' seconds';
    static $YMDYHIS = 'Ymd\THis';
    if( ! isset( $datetime[self::$LCYEAR] )  &&
        ! isset( $datetime[self::$LCMONTH] ) &&
        ! isset( $datetime[self::$LCDAY] )   &&
        ! isset( $datetime[self::$LCHOUR] )  &&
        ! isset( $datetime[self::$LCMIN] )   &&
        ! isset( $datetime[self::$LCSEC] ))
      return null;
    if( is_null( $parno ))
      $parno    = 6;
    $output     = null;
    foreach( $datetime as $dkey => & $dvalue ) {
      if( self::$LCtz != $dkey )
        $dvalue = (int) $dvalue;
    }
    $output     = sprintf( self::$YMD, $datetime[self::$LCYEAR],
                                       $datetime[self::$LCMONTH],
                                       $datetime[self::$LCDAY] );
    if( 3 == $parno )
      return $output;
    if( ! isset( $datetime[self::$LCHOUR] ))
      $datetime[self::$LCHOUR] = 0;
    if( ! isset( $datetime[self::$LCMIN] ))
      $datetime[self::$LCMIN]  = 0;
    if( ! isset( $datetime[self::$LCSEC] ))
      $datetime[self::$LCSEC]  = 0;
    $output    .= self::$T . sprintf( self::$HIS, $datetime[self::$LCHOUR],
                                                  $datetime[self::$LCMIN],
                                                  $datetime[self::$LCSEC] );
    if( isset( $datetime[self::$LCtz] )) {
      $datetime[self::$LCtz] = trim( $datetime[self::$LCtz] );
      if( ! empty( $datetime[self::$LCtz] )) {
        if( self::$Z  == $datetime[self::$LCtz] )
          $parno  = 7;
        elseif( self::isOffset( $datetime[self::$LCtz] )) {
          $parno  = 7;
          $offset = self::tz2offset( $datetime[self::$LCtz] );
          try {
            $timezone = new \DateTimeZone( self::$UTC );
            $d        = new \DateTime( $output, $timezone );
            if( 0 != $offset ) // adjust för offset
              $d->modify( $offset . $SECONDS );
            $output = $d->format( $YMDYHIS );
          }
          catch( \Exception $e ) {
            $output = date( $YMDYHIS, mktime( $datetime[self::$LCHOUR],
                                              $datetime[self::$LCMIN],
                                            ( $datetime[self::$LCSEC] - $offset ),
                                              $datetime[self::$LCMONTH],
                                              $datetime[self::$LCDAY],
                                              $datetime[self::$LCYEAR] ));
          }
        }
        if( 7 == $parno )
          $output .= self::$Z;
      } // end if( ! empty( $datetime[self::$LCtz] ))
    }
    return $output;
  }
/**
 * Return array (in internal format) for a (array) duration
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.19.4 - 2014-03-14
 * @param array $duration
 * @return array
 * @static
 */
  public static function duration2arr( $duration ) {
    $seconds        = 0;
    foreach( $duration as $durKey => $durValue ) {
      if( empty( $durValue )) continue;
      switch ( $durKey ) {
        case '0': case self::$LCWEEK:
          $seconds += (((int) $durValue ) * 60 * 60 * 24 * 7 );
          break;
        case '1': case self::$LCDAY:
          $seconds += (((int) $durValue ) * 60 * 60 * 24 );
          break;
        case '2': case self::$LCHOUR:
          $seconds += (((int) $durValue ) * 60 * 60 );
          break;
        case '3': case self::$LCMIN:
          $seconds += (((int) $durValue ) * 60 );
          break;
        case '4': case self::$LCSEC:
          $seconds +=   (int) $durValue;
          break;
      }
    }
    $output         = [];
    $output[self::$LCWEEK] = (int) floor( $seconds / ( 60 * 60 * 24 * 7 ));
    if(( 0 < $output[self::$LCWEEK] ) &&
       ( 0 == ( $seconds % ( 60 * 60 * 24 * 7 ))))
      return $output;
    unset( $output[self::$LCWEEK] );
    $output[self::$LCDAY]  = (int) floor( $seconds / ( 60 * 60 * 24 ));
    $seconds        =            ( $seconds % ( 60 * 60 * 24 ));
    $output[self::$LCHOUR] = (int) floor( $seconds / ( 60 * 60 ));
    $seconds        =            ( $seconds % ( 60 * 60 ));
    $output[self::$LCMIN]  = (int) floor( $seconds /   60 );
    $output[self::$LCSEC]  =            ( $seconds %   60 );
    if( empty( $output[self::$LCDAY] ))
      unset( $output[self::$LCDAY] );
    if(( 0 == $output[self::$LCHOUR] ) &&
       ( 0 == $output[self::$LCMIN] ) &&
       ( 0 == $output[self::$LCSEC] ))
      unset(  $output[self::$LCHOUR],
              $output[self::$LCMIN],
              $output[self::$LCSEC] );
    return $output;
  }
/**
 * Return datetime array (in internal format) for startdate + duration
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.21.11 - 2015-03-21
 * @param array   $startdate
 * @param array   $duration
 * @return array, date format
 * @static
 */
  public static function duration2date( $startdate, $duration ) {
    $dateOnly          = ( isset( $startdate[self::$LCHOUR] ) ||
                           isset( $startdate[self::$LCMIN] ) ||
                           isset( $startdate[self::$LCSEC] )) ? false : true;
    $startdate[self::$LCHOUR] = ( isset( $startdate[self::$LCHOUR] ))
                              ? $startdate[self::$LCHOUR] : 0;
    $startdate[self::$LCMIN]  = ( isset( $startdate[self::$LCMIN] ))
                              ? $startdate[self::$LCMIN]  : 0;
    $startdate[self::$LCSEC]  = ( isset( $startdate[self::$LCSEC] ))
                              ? $startdate[self::$LCSEC]  : 0;
    $dtend = 0;
    if(    isset( $duration[self::$LCWEEK] ))
      $dtend += ( $duration[self::$LCWEEK] * 7 * 24 * 60 * 60 );
    if(    isset( $duration[self::$LCDAY] ))
      $dtend += ( $duration[self::$LCDAY] * 24 * 60 * 60 );
    if(    isset( $duration[self::$LCHOUR] ))
      $dtend += ( $duration[self::$LCHOUR] * 60 *60 );
    if(    isset( $duration[self::$LCMIN] ))
      $dtend += ( $duration[self::$LCMIN] * 60 );
    if(    isset( $duration[self::$LCSEC] ))
      $dtend +=   $duration[self::$LCSEC];
    $date     = date( self::$YMDHIS3,
                      mktime((int) $startdate[self::$LCHOUR],
                             (int) $startdate[self::$LCMIN],
                             (int) ( $startdate[self::$LCSEC] + $dtend ),
                             (int) $startdate[self::$LCMONTH],
                             (int) $startdate[self::$LCDAY],
                             (int) $startdate[self::$LCYEAR] ));
    $d        = explode( self::$MINUS, $date );
    $dtend2   = [self::$LCYEAR  => $d[0],
                 self::$LCMONTH => $d[1],
                 self::$LCDAY   => $d[2],
                 self::$LCHOUR  => $d[3],
                 self::$LCMIN   => $d[4],
                 self::$LCSEC   => $d[5]];
    if( isset( $startdate[self::$LCtz] ))
      $dtend2[self::$LCtz]   = $startdate[self::$LCtz];
    if( $dateOnly &&
       (( 0 == $dtend2[self::$LCHOUR] ) &&
        ( 0 == $dtend2[self::$LCMIN] ) &&
        ( 0 == $dtend2[self::$LCSEC] )))
      unset( $dtend2[self::$LCHOUR],
             $dtend2[self::$LCMIN],
             $dtend2[self::$LCSEC] );
    return $dtend2;
  }
/**
 * Return an iCal formatted string from (internal array) duration
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.15.8 - 2012-10-30
 * @param array $duration, array( week, day, hour, min, sec )
 * @return string
 * @static
 */
  public static function duration2str( array $duration ) {
    static $P  = 'P';
    static $W  = 'W';
    static $D  = 'D';
    static $H  = 'H';
    static $OH = '0H';
    static $M  = 'M';
    static $OM = '0M';
    static $S  = 'S';
    static $OS = '0S';
    static $PT0H0M0S = 'PT0H0M0S';
    if( isset( $duration[self::$LCWEEK] ) ||
        isset( $duration[self::$LCDAY] )  ||
        isset( $duration[self::$LCHOUR] ) ||
        isset( $duration[self::$LCMIN] )  ||
        isset( $duration[self::$LCSEC] ))
       $ok = true;
    else
      return null;
    if( isset( $duration[self::$LCWEEK] ) &&
         ( 0 < $duration[self::$LCWEEK] ))
      return $P . $duration[self::$LCWEEK] . $W;
    $output = $P;
    if( isset($duration[self::$LCDAY] ) &&
        ( 0 < $duration[self::$LCDAY] ))
      $output .= $duration[self::$LCDAY] . $D;
    if(( isset( $duration[self::$LCHOUR]) &&
          ( 0 < $duration[self::$LCHOUR] )) ||
       ( isset( $duration[self::$LCMIN])  &&
          ( 0 < $duration[self::$LCMIN] ))  ||
       ( isset( $duration[self::$LCSEC])  &&
          ( 0 < $duration[self::$LCSEC] ))) {
      $output .= self::$T;
      $output .= ( isset( $duration[self::$LCHOUR]) &&
                    ( 0 < $duration[self::$LCHOUR] ))
               ? $duration[self::$LCHOUR] . $H : $OH;
      $output .= ( isset( $duration[self::$LCMIN])  &&
                    ( 0 < $duration[self::$LCMIN] ))
               ? $duration[self::$LCMIN]  . $M : $OM;
      $output .= ( isset( $duration[self::$LCSEC])  &&
                    ( 0 < $duration[self::$LCSEC] ))
               ? $duration[self::$LCSEC]  . $S : $OS;
    }
    if( $P == $output )
      $output = $PT0H0M0S;
    return $output;
  }
/**
 * Return array (in internal format) from string duration
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-17
 * @param array $duration
 * @return array|bool  false on error
 * @static
 */
  public static function durationStr2arr( $duration ) {
    static $P  = 'P';
    static $Tt = ['t', 'T'];
    static $W  = 'W';
    static $D  = 'D';
    static $H  = 'H';
    static $M  = 'M';
    static $S  = 'S';
    $duration  = (string) trim( $duration );
    while( 0 != strcasecmp( $P, $duration[0] )) {
      if( 0 < strlen( $duration ))
        $duration = substr( $duration, 1 );
      else
        return false; // no leading P !?!?
    }
    $duration = substr( $duration, 1 ); // skip P
    $duration = str_replace( $Tt, null, $duration );
    $output = [];
    $val    = null;
    $durLen = strlen( $duration );
    for( $ix=0; $ix < $durLen; $ix++ ) {
      switch( strtoupper( $duration[$ix] )) {
       case $W :
         $output[self::$LCWEEK] = $val;
         $val    = null;
         break;
       case $D :
         $output[self::$LCDAY]  = $val;
         $val    = null;
         break;
       case $H :
         $output[self::$LCHOUR] = $val;
         $val    = null;
         break;
       case $M :
         $output[self::$LCMIN]  = $val;
         $val    = null;
         break;
       case $S :
         $output[self::$LCSEC]  = $val;
         $val    = null;
         break;
       default:
         if( ! ctype_digit( $duration[$ix] ))
           return false; // unknown duration control character  !?!?
         else
           $val .= $duration[$ix];
      }
    }
    return self::duration2arr( $output );
  }
/**
 * Return bool true if input contains a date/time (in array format)
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.16.24 - 2013-07-02
 * @param array $input
 * @return bool
 * @static
 */
  public static function isArrayDate( $input ) {
    if( ! is_array( $input ) ||
             isset( $input[self::$LCWEEK] ) ||
             isset( $input[self::$LCTIMESTAMP] ) ||
       ( 3 > count( $input )))
      return false;
    if( 7 == count( $input ))
      return true;
    if( isset( $input[self::$LCYEAR] ) &&
        isset( $input[self::$LCMONTH] ) &&
        isset( $input[self::$LCDAY] ))
      return checkdate( (int) $input[self::$LCMONTH],
                        (int) $input[self::$LCDAY],
                        (int) $input[self::$LCYEAR] );
    if( isset( $input[self::$LCDAY] )  ||
        isset( $input[self::$LCHOUR] ) ||
        isset( $input[self::$LCMIN] )  ||
        isset( $input[self::$LCSEC] ))
      return false;
    if(( 0 == $input[0] ) ||
       ( 0 == $input[1] ) ||
       ( 0 == $input[2] ))
      return false;
    if(( 1970 > $input[0] ) ||
         ( 12 < $input[1] ) ||
         ( 31 < $input[2] ))
      return false;
    if(( isset( $input[0] ) &&
         isset( $input[1] ) &&
         isset( $input[2] )) &&
         checkdate((int) $input[1],
                   (int) $input[2],
                   (int) $input[0] ))
      return true;
    $input = self::strDate2ArrayDate( $input[1] .
                                      self::$L .
                                      $input[2] .
                                      self::$L .
                                      $input[0], 3 ); //  m - d - Y
    if( isset( $input[self::$LCYEAR] ) &&
        isset( $input[self::$LCMONTH] ) &&
        isset( $input[self::$LCDAY] ))
      return checkdate( (int) $input[self::$LCMONTH],
                        (int) $input[self::$LCDAY],
                        (int) $input[self::$LCYEAR] );
    return false;
  }
/**
 * Return bool true if input array contains a (keyed) timestamp date
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.4.16 - 2008-10-18
 * @param array $input
 * @return bool
 * @static
 */
  public static function isArrayTimestampDate( $input ) {
    return ( is_array( $input ) && isset( $input[self::$LCTIMESTAMP] ));
  }
/**
 * Return bool true if input string contains (trailing) UTC/iCal offset
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.14.1 - 2012-09-21
 * @param string $input
 * @return bool
 * @static
 */
  public static function isOffset( $input ) {
    static $PLUSMINUSARR = ['+', '-'];
    static $ZERO4 = '0000';
    static $NINE4 = '9999';
    static $ZERO6 = '000000';
    static $NINE6 = '999999';
    $input         = trim( (string) $input );
    if( self::$Z == substr( $input, -1 ))
      return true;
    elseif((   5 <= strlen( $input )) &&
        ( in_array( substr( $input, -5, 1 ), $PLUSMINUSARR )) &&
        ( $ZERO4 <= substr( $input, -4 )) && ( $NINE4 >= substr( $input, -4 )))
      return true;
    elseif((   7 <= strlen( $input )) &&
        ( in_array( substr( $input, -7, 1 ), $PLUSMINUSARR )) &&
        ( $ZERO6 <= substr( $input, -6 )) && ( $NINE6 >= substr( $input, -6 )))
      return true;
    return false;
  }
/**
 * Convert a date from string to (internal, keyed) array format, return true on success
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.11.8 - 2012-01-27
 * @param mixed $date
 * @return bool, true on success
 * @static
 */
  public static function strDate2arr( & $date ) {
    static $ET = [' ', 't', 'T'];
    if( is_array( $date ))
      return false;
    if( 5 > strlen( (string) $date ))
      return false;
    $work = $date;
    if( 2 == substr_count( $work, self::$MINUS ))
      $work = str_replace( self::$MINUS, null, $work );
    if( 2 == substr_count( $work, self::$L ))
      $work = str_replace( self::$L, null, $work );
    if( ! ctype_digit( substr( $work, 0, 8 )))
      return false;
    $temp = [self::$LCYEAR  => (int) substr( $work,  0, 4 ),
             self::$LCMONTH => (int) substr( $work,  4, 2 ),
             self::$LCDAY   => (int) substr( $work,  6, 2 )];
    if( ! checkdate( $temp[self::$LCMONTH],
                     $temp[self::$LCDAY],
                     $temp[self::$LCYEAR] ))
      return false;
    if( 8 == strlen( $work )) {
      $date = $temp;
      return true;
    }
    if( in_array( $work[8], $ET ))
      $work =  substr( $work, 9 );
    elseif( ctype_digit( $work[8] ))
      $work = substr( $work, 8 );
    else
     return false;
    if( 2 == substr_count( $work, self::$COLON ))
      $work = str_replace( self::$COLON, null, $work );
    if( ! ctype_digit( substr( $work, 0, 4 )))
      return false;
    $temp[self::$LCHOUR]  = substr( $work, 0, 2 );
    $temp[self::$LCMIN]   = substr( $work, 2, 2 );
    if((( 0 > $temp[self::$LCHOUR] ) || ( $temp[self::$LCHOUR] > 23 )) ||
       (( 0 > $temp[self::$LCMIN] )  || ( $temp[self::$LCMIN]  > 59 )))
      return false;
    if( ctype_digit( substr( $work, 4, 2 ))) {
      $temp[self::$LCSEC] = substr( $work, 4, 2 );
      if((  0 > $temp[self::$LCSEC] ) || ( $temp[self::$LCSEC]  > 59 ))
        return false;
      $len = 6;
    }
    else {
      $temp[self::$LCSEC] = 0;
      $len = 4;
    }
    if( $len < strlen( $work))
      $temp[self::$LCtz] = trim( substr( $work, 6 ));
    $date = $temp;
    return true;
  }
/**
 * Return string date-time/date as array (in internal format, keyed)
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.22.23 - 2017-02-19
 * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
 * @param string $datetime
 * @param int    $parno   default false
 * @param mixed  $wtz     default null
 * @return array
 * @static
 */
  public static function strDate2ArrayDate( $datetime,
                                            $parno=null,
                                            $wtz=null ) {
    static $SECONDS   = ' seconds';
    $unparseddatetime = $datetime;
    $datetime   = (string) trim( $datetime );
    $tz         = null;
    $offset     = 0;
    $tzSts      = false;
    $len        = strlen( $datetime );
    if( self::$Z == substr( $datetime, -1 )) {
      $tz       = self::$Z;
      $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
      $tzSts    = true;
    }
    if( self::isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
      $tz       = substr( $datetime, -5, 5 );
      $datetime = trim( substr( $datetime, 0, ($len - 5)));
    }
    elseif( self::isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
      $tz       = substr( $datetime, -7, 7 );
      $datetime = trim( substr( $datetime, 0, ($len - 7)));
    }
    elseif( empty( $wtz ) &&
            ctype_digit( substr( $datetime, 0, 4 ))  &&
            ctype_digit( substr( $datetime, -2, 2 )) &&
            self::strDate2arr( $datetime )) {
      $output = $datetime;
      if( ! empty( $tz ))
        $output[self::$LCtz] = self::$Z;
      $output[self::$UNPARSEDTEXT] = $unparseddatetime;
      return $output;
    }
    else {
      $tx  = 0;  //  find any TRAILING timezone or offset
      $len = strlen( $datetime );
      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
        $char = substr( $datetime, $cx, 1 );
        if(( self::$SP1 == $char ) || ctype_digit( $char ))
          break;       // if exists, tz ends here.. . ?
        else
           $tx--;      // tz length counter
      }
      if( 0 > $tx ) {  // if any timezone or offset found
        $tz     = substr( $datetime, $tx );
        $datetime = trim( substr( $datetime, 0, $len + $tx ));
      }
      if((  ctype_digit( substr( $datetime,  0, 8 )) &&
          ( self::$T ==          $datetime[8] )      &&
            ctype_digit( substr( $datetime, -6, 6 ))) ||
          ( ctype_digit( substr( $datetime,  0, 14 ))))
        $tzSts  = true;
    }
    if( empty( $tz ) && ! empty( $wtz ))
      $tz       = $wtz;
    if( 3 == $parno )
      $tz       = null;
    if( ! empty( $tz )) { // tz set
      if(( self::$Z != $tz ) && ( self::isOffset( $tz ))) {
        $offset = (string) self::tz2offset( $tz ) * -1;
        $tz     = self::$UTC;
        $tzSts  = true;
      }
      elseif( ! empty( $wtz ))
        $tzSts  = true;
      $tz       = trim( $tz );
      if(( 0 == strcasecmp( self::$Z, $tz )) ||
         ( 0 == strcasecmp( self::$GMT, $tz )))
        $tz     = self::$UTC;
      if( 0 < substr_count( $datetime, self::$MINUS ))
        $datetime = str_replace( self::$MINUS, self::$L, $datetime );
      try {
        $timezone = new \DateTimeZone( $tz );
        $d        = new \DateTime( $datetime, $timezone );
        if( 0  != $offset )  // adjust for offset
          $d->modify( $offset . $SECONDS );
        $datestring = $d->format( self::$YMDHIS3 );
        unset( $d );
      }
      catch( \Exception $e ) {
        $datestring = date( self::$YMDHIS3, strtotime( $datetime ));
      }
    } // end if( ! empty( $tz ))
    else
      $datestring = date( self::$YMDHIS3, strtotime( $datetime ));
    if( self::$UTC == $tz )
      $tz         = self::$Z;
    $d            = explode( self::$MINUS, $datestring );
    $output       = [self::$LCYEAR  => $d[0],
                     self::$LCMONTH => $d[1],
                     self::$LCDAY   => $d[2]];
    if( ! empty( $parno ) || ( 3 != $parno )) { // parno is set to 6 or 7
      $output[self::$LCHOUR] = $d[3];
      $output[self::$LCMIN]  = $d[4];
      $output[self::$LCSEC]  = $d[5];
      if(( $tzSts || ( 7 == $parno )) && ! empty( $tz ))
        $output[self::$LCtz] = $tz;
    }
    // return original string in the array in case strtotime failed to make sense of it
    $output[self::$UNPARSEDTEXT] = $unparseddatetime;
    return $output;
  }
/**
 * Return string/array timestamp(+ offset/timezone (default UTC)) as array (in internal format, keyed).
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.21.11 - 2015-03-07
 * @param mixed   $timestamp
 * @param int     $parno
 * @param string  $wtz
 * @return array
 * @static
 */
  public static function timestamp2date( $timestamp, $parno=6, $wtz=null ) {
    static $FMTTIMESTAMP = '@%s';
    static $SPSEC        = ' seconds';
    if( is_array( $timestamp )) {
      $tz        = ( isset( $timestamp[self::$LCtz] ))
                 ? $timestamp[self::$LCtz] : $wtz;
      $timestamp = $timestamp[self::$LCTIMESTAMP];
    }
    $tz          = ( isset( $tz )) ? $tz : $wtz;
    $offset      = 0;
    if( empty( $tz ) ||
       ( self::$Z == $tz ) ||
       ( 0 == strcasecmp( self::$GMT, $tz )))
      $tz        = self::$UTC;
    elseif( self::isOffset( $tz )) {
      $offset    = self::tz2offset( $tz );
    }
    try {
      $timestamp = sprintf( $FMTTIMESTAMP, $timestamp );
      $d         = new \DateTime( $timestamp );     // set UTC date
      if(  0 != $offset )                           // adjust for offset
        $d->modify( $offset . $SPSEC );
      elseif( self::$UTC != $tz )
        $d->setTimezone( new \DateTimeZone( $tz )); // convert to local date
      $date      = $d->format( self::$YMDHIS3 );
    }
    catch( \Exception $e ) {
      $date      = date( self::$YMDHIS3, $timestamp );
    }
    $date        = explode( self::$MINUS, $date );
    $output      = [self::$LCYEAR  => $date[0],
                    self::$LCMONTH => $date[1],
                    self::$LCDAY   => $date[2]];
    if( 3 != $parno ) {
      $output[self::$LCHOUR] = $date[3];
      $output[self::$LCMIN]  = $date[4];
      $output[self::$LCSEC]  = $date[5];
      if(( self::$UTC == $tz ) || ( 0 == $offset ))
        $output[self::$LCtz] = self::$Z;
    }
    return $output;
  }
/**
 * Return seconds based on an offset, [+/-]HHmm[ss], used when correcting UTC to localtime or v.v.
 *
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @since 2.23.8 - 2017-04-17
 * @param string $tz
 * @return integer
 * @static
 */
  public static function tz2offset( $tz ) {
    static $ZERO4 = '0000';
    static $NINE4 = '9999';
    static $ZERO2 = '00';
    static $NINE2 = '99';
    $tz           = trim( (string) $tz );
    $offset       = 0;
    if(((          5  != strlen( $tz )) &&
                 ( 7  != strlen( $tz )))          ||
      ((  self::$PLUS != $tz[0]  &&
       ( self::$MINUS != $tz[0] )))               ||
       ((      $ZERO4 >= substr( $tz, 1, 4 )) &&
             ( $NINE4 <  substr( $tz, 1, 4 )))    ||
                (( 7  == strlen( $tz )) &&
                 ( $ZERO2 > substr( $tz, 5, 2 )) &&
                 ( $NINE2 < substr( $tz, 5, 2 ))))
      return $offset;
    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
    $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
    $sec          = ( 7  == strlen( $tz ))
                  ? (int) substr( $tz, -2 ) : $ZERO2;
    $offset       = $hours2sec + $min2sec + $sec;
    $offset       = ( self::$MINUS == $tz[0] )
                  ? $offset * -1 : $offset;
    return $offset;
  }
}

Zerion Mini Shell 1.0