* 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 . */ namespace kigkonsult\iCalcreator\util; /** * iCalcreator utility/support class * * @author Kjell-Inge Gustafsson, kigkonsult * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @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 * @since 2.22.23 - 2017-02-19 * Modified to also return original string value by Yitzchok Lavi * @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 * @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 * @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; } }