PluginProbe
Import Social Events / 1.8.5
Import Social Events v1.8.5
1.9.1 1.9.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.3.0 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 All 61 releases
import-facebook-events / includes / lib / icalcreator / src / Util / RegulateTimezoneFactory.php

RegulateTimezoneFactory.php in Import Social Events 1.8.5, at includes/lib/icalcreator/src/Util/RegulateTimezoneFactory.php

886 lines 31.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
4 *
5 * copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
6 * Link https://kigkonsult.se
7 * Package iCalcreator
8 * Version 2.30
9 * License Subject matter of licence is the software iCalcreator.
10 * The above copyright, link, package and version notices,
11 * this licence notice and the invariant [rfc5545] PRODID result use
12 * as implemented and invoked in iCalcreator shall be included in
13 * all copies or substantial portions of the iCalcreator.
14 *
15 * iCalcreator is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as published
17 * by the Free Software Foundation, either version 3 of the License,
18 * or (at your option) any later version.
19 *
20 * iCalcreator is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
27 *
28 * This file is a part of iCalcreator.
29 */
30
31 namespace Kigkonsult\Icalcreator\Util;
32
33 use DateTime;
34 use DateTimeZone;
35 use Exception;
36 use InvalidArgumentException;
37 use Kigkonsult\Icalcreator\Vcalendar;
38 use RuntimeException;
39 use UnexpectedValueException;
40
41 use function array_keys;
42 use function arsort;
43 use function explode;
44 use function implode;
45 use function key;
46 use function reset;
47 use function sprintf;
48 use function str_replace;
49 use function strlen;
50 use function strpos;
51 use function timezone_abbreviations_list;
52 use function timezone_name_from_abbr;
53
54 /**
55 * Class RegulateTimezoneFactory
56 *
57 * Review timezones, opt. alter to PHP timezones
58 *
59 * @see https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones
60 * Cover Vtimezone property TZID and component date properties DTSTART, DTEND, DUE, RECURRENCE-ID, EXDATE, RDATE
61 *
62 * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
63 * @since 2.29.10 - 2019-09-02
64 */
65 class RegulateTimezoneFactory
66 {
67 /**
68 * Add MS timezone and offset to (internal) MStimezoneToOffset
69 *
70 * @param string $msTz
71 * @param string $offset (+/-)HH:mm
72 */
73 public static function addMStimezoneToOffset( $msTz, $offset )
74 {
75 self::$MStimezoneToOffset[$msTz] = $offset;
76 }
77
78 /**
79 * 4 GMT/UTC(-suffixed)
80 * 59 matches on NOT dst, OK
81 * 5 hits on dst (aka daylight saving time)...
82 * 5 PHP timezones specified
83 *
84 * @var array MS timezones with corr. UTC offset, 73 items
85 */
86 public static $MStimezoneToOffset = [
87 'Afghanistan Standard Time' => '+04:30',
88 'Arab Standard Time' => '+03:00',
89 'Arabian Standard Time' => '+04:00',
90 'Arabic Standard Time' => '+03:00',
91 'Argentina Standard Time' => '-03:00',
92 'Atlantic Standard Time' => '-04:00',
93 'AUS Eastern Standard Time' => '+10:00',
94 'Azerbaijan Standard Time' => '+04:00',
95 'Bangladesh Standard Time' => '+06:00',
96 'Belarus Standard Time' => '+03:00',
97 'Cape Verde Standard Time' => '-01:00',
98 'Caucasus Standard Time' => '+04:00',
99 'Central America Standard Time' => '-06:00',
100 'Central Asia Standard Time' => '+06:00',
101 'Central Europe Standard Time' => '+01:00',
102 'Central European Standard Time' => '+01:00',
103 'Central Pacific Standard Time' => '+11:00',
104 'Central Standard Time (Mexico)' => '-06:00',
105 'China Standard Time' => '+08:00',
106 'E. Africa Standard Time' => '+03:00',
107 'E. Europe Standard Time' => '+02:00',
108 'E. South America Standard Time' => '-03:00',
109 'Eastern Standard Time' => '-05:00',
110 'Egypt Standard Time' => '+02:00',
111 'Fiji Standard Time' => '+12:00',
112 'FLE Standard Time' => '+02:00',
113 'Georgian Standard Time' => '+04:00',
114 'GMT Standard Time' => '',
115 'Greenland Standard Time' => '-03:00',
116 'Greenwich Standard Time' => '',
117 'GTB Standard Time' => '+02:00',
118 'Hawaiian Standard Time' => '-10:00',
119 'India Standard Time' => '+05:30',
120 'Israel Standard Time' => '+02:00',
121 'Jordan Standard Time' => '+02:00',
122 'Korea Standard Time' => '+09:00',
123 'Mauritius Standard Time' => '+04:00',
124 'Middle East Standard Time' => '+02:00',
125 'Montevideo Standard Time' => '-03:00',
126 'Morocco Standard Time' => '',
127 'Myanmar Standard Time' => '+06:30',
128 'Namibia Standard Time' => '+01:00',
129 'Nepal Standard Time' => '+05:45',
130 'New Zealand Standard Time' => '+12:00',
131 'Pacific SA Standard Time' => '-03:00',
132 'Pacific Standard Time' => '-08:00',
133 'Pakistan Standard Time' => '+05:00',
134 'Paraguay Standard Time' => '-04:00',
135 'Romance Standard Time' => '+01:00',
136 'Russian Standard Time' => '+03:00',
137 'SA Eastern Standard Time' => '-03:00',
138 'SA Pacific Standard Time' => '-05:00',
139 'SA Western Standard Time' => '-04:00',
140 'Samoa Standard Time' => '+13:00',
141 'SE Asia Standard Time' => '+07:00',
142 'Singapore Standard Time' => '+08:00',
143 'South Africa Standard Time' => '+02:00',
144 'Sri Lanka Standard Time' => '+05:30',
145 'Syria Standard Time' => '+02:00',
146 'Taipei Standard Time' => '+08:00',
147 'Tokyo Standard Time' => '+09:00',
148 'Tonga Standard Time' => '+13:00',
149 'Turkey Standard Time' => '+02:00',
150 'Ulaanbaatar Standard Time' => '+08:00',
151 'UTC' => '',
152 'UTC-02' => '-02:00',
153 'UTC-11' => '-11:00',
154 'UTC+12' => '+12:00',
155 'Venezuela Standard Time' => '-04:30',
156 'W. Central Africa Standard Time' => '+01:00',
157 'W. Europe Standard Time' => '+01:00',
158 'West Asia Standard Time' => '+05:00',
159 'West Pacific Standard Time' => '+10:00',
160 ];
161
162 /**
163 * Add other timezone map to specific PHP timezone
164 *
165 * @param string $otherTz
166 * @param string $phpTz
167 * @throws InvalidArgumentException
168 */
169 public static function addOtherTzMapToPhpTz( $otherTz, $phpTz )
170 {
171 DateTimeZoneFactory::assertDateTimeZone( $phpTz );
172 self::$otherTzToPhpTz[$otherTz] = $phpTz;
173 }
174
175 /**
176 * @var array 7 MS timezones to PHP timezones
177 */
178 public static $otherTzToPhpTz = [
179 'Afghanistan Standard Time' => 'Asia/Kabul',
180 'Fiji Standard Time' => 'Pacific/Fiji',
181 // also in 'UTC+12', below
182 'Myanmar Standard Time' => 'Asia/Yangon',
183 'New Zealand Standard Time' => 'Pacific/Auckland',
184 'UTC-02' => 'America/Noronha',
185 // also America/Godthab - Greenland
186 // America/Miquelon - Saint Pierre and Miquelon
187 // Atlantic/South_Georgia - South Georgia and the South Sandwich Islands
188 'UTC-11' => 'Pacific/Pago_Pago',
189 // also Pacific/Niue, Pacific/Midway
190 'UTC+12' => 'Pacific/Auckland',
191 // also Antarctica/McMurdo, Asia/Anadyr, Asia/Kamchatka,
192 // Pacific/Fiji, Pacific/Funafuti, Pacific/Kwajalein, Pacific/Majuro,
193 // Pacific/Nauru, Pacific/Tarawa, Pacific/Wake, Pacific/Wallis
194 ];
195
196 /**
197 * @var array
198 */
199 private $inputiCal = [];
200
201 /**
202 * @var string
203 */
204 private $outputiCal = null;
205
206 /**
207 * @var array
208 */
209 private $vtimezoneRows = [];
210
211 /**
212 * @var array
213 */
214 private $otherTzPhpRelations = [];
215
216 /**
217 * Class constructor
218 *
219 * @param string|array $inputiCal strict rfc2445 formatted calendar
220 * @param array $otherTzPhpRelations [ other => phpTz ]
221 * @throws InvalidArgumentException
222 */
223 public function __construct( $inputiCal = null, array $otherTzPhpRelations = [] )
224 {
225 if( ! empty( $inputiCal )) {
226 $this->setInputiCal( $inputiCal );
227 }
228 $this->addOtherTzPhpRelations( self::$otherTzToPhpTz );
229 foreach( $otherTzPhpRelations as $otherTz => $phpTz ) {
230 $this->addOtherTzPhpRelation( $otherTz, $phpTz );
231 }
232 }
233
234 /**
235 * Class factory method
236 *
237 * @param string|array $inputiCal strict rfc2445 formatted calendar
238 * @param array $otherTzPhpRelations [ other => phpTz ]
239 * @return static
240 * @throws InvalidArgumentException
241 */
242 public static function factory( $inputiCal = null, array $otherTzPhpRelations = [] )
243 {
244 return new self( $inputiCal, $otherTzPhpRelations );
245 }
246
247
248 /**
249 * Short static all-in-one method
250 *
251 * @param string|array $inputiCal strict rfc2445 formatted calendar
252 * @param array $otherTzPhpRelations [ other => phpTz ]
253 * @return string
254 * @throws Exception
255 * @throws InvalidArgumentException
256 */
257 public static function process( $inputiCal, array $otherTzPhpRelations = [] )
258 {
259 return self::factory( $inputiCal, $otherTzPhpRelations )
260 ->processCalendar()
261 ->getOutputiCal();
262 }
263
264
265 /**
266 * @param string|array $inputiCal strict rfc2445 formatted calendar
267 * @return static
268 * @throws Exception
269 * @throws InvalidArgumentException
270 */
271 public function processCalendar( $inputiCal = null )
272 {
273 $FMTERR = 'Calendar content required!';
274 if( ! empty( $inputiCal )) {
275 $this->setInputiCal( $inputiCal );
276 }
277 if( ! $this->isInputiCalSet()) {
278 throw new InvalidArgumentException( $FMTERR );
279 }
280 $vtSwitch = false;
281 foreach( $this->getInputiCal() as $lix => $row ) {
282 if( StringFactory::startsWith( $row, self::$BEGINVTIMEZONE )) {
283 $this->setVtimezoneRow( $row );
284 $vtSwitch = true;
285 continue;
286 }
287 if( StringFactory::startsWith( $row, self::$ENDVTIMEZONE )) {
288 $this->setVtimezoneRow( $row );
289 $this->processVtimezone();
290 $this->setVtimezoneRows( [] );
291 $vtSwitch = false;
292 continue;
293 }
294 if( $vtSwitch ) {
295 $this->setVtimezoneRow( $row );
296 continue;
297 }
298 if( StringFactory::startsWith( $row, self::$BEGIN ) ||
299 StringFactory::startsWith( $row, self::$END )) {
300 $this->setOutputiCalRow( $row );
301 continue;
302 }
303 /* split property name and opt.params and value */
304 list( $propName, $row2 ) = StringFactory::getPropName( $row );
305 if( ! Util::isPropInList( $propName, self::$TZIDPROPS )) {
306 $this->setOutputiCalRow( $row );
307 continue;
308 }
309 /* Now we have only properties with propAttr TZID */
310 /* separate attributes from value */
311 list( $value, $propAttr ) = self::splitContent( $row2 );
312 if( ! isset( $propAttr[Vcalendar::TZID] )) {
313 $this->setOutputiCalRow( $row );
314 continue;
315 }
316 $this->processDtProp( $propName, $value, $propAttr );
317 } // end foreach
318 return $this;
319 }
320
321 /**
322 * Process VTIMEZONE properties
323 *
324 * NO UTC here !! ??
325 * @throws InvalidArgumentException
326 * @throws RuntimeException
327 */
328 private function processVtimezone()
329 {
330 $currTzId = null; // empty if Vtimezone TZID is found else troublesome one
331 $currTzIdFound = false; // true if PHP Vtimezone TZID found
332 $stdSwitch = $dlghtSwitch = false; // process STANDARD/DAYLIGHT or not
333 $stdArr = $dlghtArr = []; // TZOFFSETTO values (in STANDARD/DAYLIGHT)
334 foreach( $this->getVtimezoneRows() as $lix => $row ) {
335 switch( true ) {
336 case ( StringFactory::startsWith( $row, self::$BEGINVTIMEZONE )) :
337 $this->setOutputiCalRow( $row );
338 continue 2;
339 break;
340 case ( StringFactory::startsWith( $row, self::$ENDVTIMEZONE )) :
341 if( ! empty( $currTzId )) {
342 $this->processCurrTzId( $currTzId, $stdArr, $dlghtArr );
343 }
344 $this->setOutputiCalRow( $row );
345 $currTzId = null;
346 $currTzIdFound = false;
347 $stdSwitch = $dlghtSwitch = false; // process STANDARD/DAYLIGHT or not
348 $stdArr = $dlghtArr = []; // TZOFFSETTO values (in STANDARD/DAYLIGHT)
349 continue 2;
350 break;
351 case ( StringFactory::startsWith( $row, self::$BEGINSTANDARD )) :
352 $this->setOutputiCalRow( $row );
353 $stdSwitch = true;
354 continue 2;
355 break;
356 case ( StringFactory::startsWith( $row, self::$ENDSTANDARD )) :
357 $this->setOutputiCalRow( $row );
358 $stdSwitch = false;
359 continue 2;
360 break;
361 case ( StringFactory::startsWith( $row, self::$BEGINDAYLIGHT )) :
362 $this->setOutputiCalRow( $row );
363 $dlghtSwitch = true;
364 continue 2;
365 break;
366 case ( StringFactory::startsWith( $row, self::$ENDDAYLIGHT )) :
367 $this->setOutputiCalRow( $row );
368 $dlghtSwitch = false;
369 continue 2;
370 break;
371 case $currTzIdFound : // Vtimezone TZID is found, write whatever row it is
372 $this->setOutputiCalRow( $row );
373 continue 2;
374 break;
375 default :
376 break; // now we go on with property rows
377 } // end switch
378 /* split property name and opt.params and value */
379 list( $propName, $row2 ) = StringFactory::getPropName( $row );
380 if( Vcalendar::TZOFFSETTO == $propName ) { // save offset if...
381 if( $stdSwitch ) {
382 $stdArr[] = StringFactory::afterLast( Util::$COLON, $row2 );
383 }
384 elseif( $dlghtSwitch ) {
385 $dlghtArr[] = StringFactory::afterLast( Util::$COLON, $row2 );
386 }
387 }
388 if( Vcalendar::TZID != $propName ) { // skip all but Vtimezone TZID
389 $this->setOutputiCalRow( $row );
390 continue;
391 }
392 /* separate attributes from value */
393 list( $value, $propAttr ) = StringFactory::splitContent( $row2 );
394 $currTzId = $value;
395 $valueNew = null;
396 switch( true ) {
397 case ( $this->hasOtherTzPHPtzMap( $value )) :
398 $valueNew = $this->getOtherTzPhpRelations( $value );
399 break;
400 case ( isset( self::$MStimezoneToOffset[$value] )) :
401 $msTzOffset = self::$MStimezoneToOffset[$value];
402 if( empty( $msTzOffset )) {
403 $valueNew = Vcalendar::UTC;
404 }
405 else {
406 $valueNew = self::getTimeZoneNameFromOffset( $msTzOffset, false );
407 } // $valueNew is null on notFound
408 break;
409 default :
410 try {
411 DateTimeZoneFactory::assertDateTimeZone( $value );
412 $currTzId = null;
413 $currTzIdFound = true; // NO process of STANDARD/DAYLIGHT offset
414 }
415 catch( InvalidArgumentException $e ) {
416 $valueNew = null; // DO process of STANDARD/DAYLIGHT offset
417 }
418 break;
419 } // end switch
420 if( ! empty( $valueNew )) {
421 $this->addOtherTzPhpRelation( $currTzId, $valueNew, false );
422 $this->setOutputiCalRowElements( $propName, $valueNew, $propAttr );
423 $currTzId = null;
424 $currTzIdFound = true; // NO process of STANDARD/DAYLIGHT
425 }
426 else {
427 $this->setOutputiCalRow( $row ); // DO process of STANDARD/DAYLIGHT
428 }
429 } // end foreach
430 }
431
432 /**
433 * Find currTzId replacement using stdArr+dlghtArr offsets
434 *
435 * @param string $currTzId
436 * @param array $stdArr
437 * @param array $dlghtArr
438 * @throws RuntimeException
439 */
440 private function processCurrTzId( $currTzId, array $stdArr, array $dlghtArr )
441 {
442 static $ERR = 'Timezone \'%s\' (offset std %s, dlght %s) don\'t match any PHP timezone';
443 $stdTzs = $dlghtTzs = [];
444 foreach( $stdArr as $offset ) {
445 $stdTzs = self::getTimezoneListFromOffset( $offset, 0 ); // standard
446 }
447 foreach( $dlghtArr as $offset ) {
448 $dlghtTzs = self::getTimezoneListFromOffset( $offset, 1 ); // daylight
449 }
450 foreach( $dlghtTzs as $tz => $cnt ) {
451 if( isset( $stdTzs[$tz] )) {
452 $dlghtTzs[$tz] += $stdTzs[$tz];
453 }
454 }
455 foreach( $stdTzs as $tz => $cnt ) {
456 if( ! isset( $dlghtTzs[$tz] )) {
457 $dlghtTzs[$tz] = $cnt;
458 }
459 }
460 if( empty( $dlghtTzs )) {
461 throw new RuntimeException(
462 sprintf(
463 $ERR,
464 $currTzId,
465 implode( Util::$COMMA, $stdArr ),
466 implode( Util::$COMMA, $dlghtArr )
467 )
468 );
469 }
470 arsort( $dlghtTzs ); // reverse sort on number of hits
471 reset( $dlghtTzs );
472 $tzidNew = key( $dlghtTzs );
473 $this->replaceTzidInOutputiCal( $currTzId, $tzidNew );
474 $this->addOtherTzPhpRelation( $currTzId, $tzidNew, false );
475 }
476
477 /**
478 * Process component properties with propAttr TZID
479 *
480 * @param string $propName
481 * @param string $value
482 * @param array $propAttr
483 * @throws Exception
484 * @throws InvalidArgumentException
485 * @throws RuntimeException
486 * @todo properties RDATE, EXDATE
487 */
488 private function processDtProp( $propName, $value, array $propAttr )
489 {
490 $tzId = $propAttr[Vcalendar::TZID];
491 switch( true ) {
492 case ( $this->hasOtherTzPHPtzMap( $tzId ) ) :
493 $propAttr[Vcalendar::TZID] = $this->getOtherTzPhpRelations( $tzId );
494 break;
495 case ( isset( self::$MStimezoneToOffset[$tzId] ) &&
496 empty( self::$MStimezoneToOffset[$tzId] )) :
497 $this->addOtherTzPhpRelation( $tzId, Vcalendar::UTC, false );
498 $propAttr[Vcalendar::TZID] = Vcalendar::UTC;
499 break;
500 default : /* check and (opt) alter timezones */
501 $this->processDatePropsTZIDattribute( $propName, $value, $propAttr );
502 return;
503 } // end switch
504 self::checkTzidForUTC( $value, $propAttr );
505 $this->setOutputiCalRowElements( $propName, $value, $propAttr );
506 }
507
508 /**
509 * If in array, alter date-properties attribute TZID fixed. PHP-check (all) timezones, throws exception on error
510 *
511 * @param string $propName
512 * @param string $value
513 * @param array $propAttr
514 * @throws InvalidArgumentException
515 * @throws RuntimeException
516 */
517 private function processDatePropsTZIDattribute( $propName, $value, $propAttr )
518 {
519 $tzId = $tzIdIn = $propAttr[Vcalendar::TZID];
520 if( isset( self::$MStimezoneToOffset[$tzIdIn] )) {
521 $tzId = self::getTimeZoneNameFromOffset(
522 self::$MStimezoneToOffset[$tzIdIn],
523 true
524 );
525 $this->addOtherTzPhpRelation( $tzIdIn, $tzId, false );
526 $propAttr[Vcalendar::TZID] = $tzId;
527 self::checkTzidForUTC( $value, $propAttr );
528 }
529 else {
530 DateTimeZoneFactory::assertDateTimeZone( $tzId );
531 }
532 $this->setOutputiCalRowElements( $propName, $value, $propAttr );
533 }
534
535 /**
536 * Return array( value, propAttr ) from property row
537 *
538 * @param string $row2
539 * @return array ( value, propAttr )
540 */
541 private static function splitContent( $row2 )
542 {
543 /* separate attributes from value */
544 list( $value, $propAttr ) = StringFactory::splitContent( $row2 );
545 /* fix splitContent UTC 'bug' */
546 self::fixUTCx( $row2, $value, $propAttr );
547 return [ $value, $propAttr ];
548 }
549
550 /**
551 * Return (first found) timezone from offset, search on standard time (ie dst=0) first
552 *
553 * From DateTimeZoneFactory
554 * @param string $offset
555 * @param bool $throwException
556 * @return string tzName
557 * @throws RuntimeException on NOT found
558 * @since 2.27.14 - 2019-02-26
559 */
560 private static function getTimeZoneNameFromOffset( $offset, $throwException = true )
561 {
562 static $ERR = 'Offset \'%s\' (%+d seconds) don\'t match any PHP timezone';
563 $seconds = DateTimeZoneFactory::offsetToSeconds( $offset );
564 $res = timezone_name_from_abbr( Util::$SP0, $seconds, 0 );
565 if( false !== $res ) { // is NO dst
566 return $res;
567 }
568 $res = timezone_name_from_abbr( Util::$SP0, $seconds );
569 if( false !== $res ) { // ignores dst
570 return $res;
571 }
572 $res = timezone_name_from_abbr( Util::$SP0, $seconds, 1 );
573 if( false !== $res ) { // is dst
574 return $res;
575 }
576 if( $throwException ) {
577 throw new RuntimeException( sprintf( $ERR, $offset, $seconds ));
578 }
579 else {
580 return null;
581 }
582 }
583
584 /**
585 * Returns (array) timezones that match offset ( and dst)
586 *
587 * @see https://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155
588 * @see https://www.php.net/manual/en/datetimezone.listabbreviations.php#114161
589 * @param string $offset
590 * @param int $dst
591 * @return array
592 * @throws RuntimeException
593 */
594 private static function getTimezoneListFromOffset( $offset, $dst )
595 {
596 static $DST = 'dst';
597 static $OFFSET = 'offset';
598 static $TIMEZONEID = 'timezone_id';
599 static $FMTERR = 'Can\'t get offset from timezone %s';
600 $seconds = DateTimeZoneFactory::offsetToSeconds( $offset );
601 $output = [];
602 foreach( timezone_abbreviations_list() as $tzAbbrList ) {
603 foreach( $tzAbbrList as $tzAbbrCity ) {
604 if(((bool) $tzAbbrCity[$DST] !== (bool) $dst ) ||
605 empty( strlen( $tzAbbrCity[$TIMEZONEID] )) ||
606 ( $tzAbbrCity[$OFFSET] != $seconds )) {
607 continue;
608 }
609 $dateTimeOffsetNow = 0;
610 try {
611 $date = new DateTime(
612 null,
613 new DateTimeZone( $tzAbbrCity[$TIMEZONEID] )
614 );
615 $dateTimeOffsetNow = $date->getOffset();
616 }
617 catch( Exception $e ) {
618 throw new RuntimeException(
619 sprintf( $FMTERR, $tzAbbrCity[$TIMEZONEID] )
620 );
621 }
622 if( $seconds == $dateTimeOffsetNow ) {
623 $tzId = $tzAbbrCity[$TIMEZONEID];
624 if( isset( $output[$tzId] )) {
625 $output[$tzId] += 1;
626 }
627 else {
628 $output[$tzId] = 1;
629 }
630 } // end if
631 } // end foreach
632 } // end foreach
633 return $output;
634 }
635
636 /**
637 * Suffix value with 'Z'and remove propAttr TZID, IF propAttr TZID = UTC
638 *
639 * @param string $value
640 * @param array $propAttr
641 */
642 private static function checkTzidForUTC( & $value, & $propAttr )
643 {
644 if( ! DateTimeZoneFactory::isUTCtimeZone( $propAttr[Vcalendar::TZID] )) {
645 return;
646 }
647 unset( $propAttr[Vcalendar::TZID] );
648 $values = explode( Util::$COMMA, $value );
649 foreach( array_keys( $values ) as $x ) {
650 if( ParameterFactory::isParamsValueSet(
651 [ Util::$LCparams => $propAttr ],
652 Vcalendar::PERIOD
653 )) { // RDATE
654 $thePeriods = explode( Util::$SLASH, $values[$x] );
655 $thePeriods[0] .= Vcalendar::Z;
656 if( ! DateIntervalFactory::isStringAndDuration( $thePeriods[1] )) {
657 $thePeriods[1] .= Vcalendar::Z;
658 }
659 $values[$x] = implode( Util::$SLASH, $thePeriods );
660 }
661 else {
662 $values[$x] .= Vcalendar::Z;
663 }
664 }
665 $value = implode( Util::$COMMA, $values );
666 }
667
668 /**
669 * Fix StringFactory::splitContent UTC* bug for MS list UTC-related timezones
670 *
671 * Note, here $propAttr[Vcalendar::TZID] exists
672 * @param string $row2
673 * @param string $value
674 * @param array $propAttr
675 */
676 private static function fixUTCx( $row2, & $value, & $propAttr )
677 {
678 static $UTZx = [ 'UTC-02', 'UTC-11', 'UTC+12' ];
679 foreach( $UTZx as $theUTC ) {
680 if( false === strpos( $row2, $theUTC )) {
681 continue;
682 }
683 if( false !== strpos( $propAttr[Vcalendar::TZID], Util::$COLON )) {
684 $propAttr[Vcalendar::TZID] =
685 StringFactory::beforeLast(
686 Util::$COLON,
687 $propAttr[Vcalendar::TZID]
688 );
689 }
690 if( false !== strpos( $value, Util::$COLON )) {
691 $value = StringFactory::afterLast( Util::$COLON, $row2 );
692 }
693 break;
694 } // end foreach
695 }
696
697 /** ***********************************************************************
698 * Getters and setters etc
699 */
700
701 /**
702 * @return array
703 */
704 public function getInputiCal()
705 {
706 return $this->inputiCal;
707 }
708
709 /**
710 * @return bool
711 */
712 public function isInputiCalSet()
713 {
714 return ( ! empty( $this->inputiCal ));
715 }
716
717 /**
718 * @param string|array $inputiCal
719 * @return static
720 * @throws UnexpectedValueException
721 */
722 public function setInputiCal( $inputiCal )
723 {
724 /* get rows to parse */
725 $rows = StringFactory::conformParseInput( $inputiCal );
726 /* concatenate property values spread over several rows */
727 $this->inputiCal = StringFactory::concatRows( $rows );
728 /* Initiate output */
729 $this->setVtimezoneRows( [] );
730 return $this;
731 }
732
733
734 /**
735 * @return array
736 */
737 private function getVtimezoneRows()
738 {
739 return $this->vtimezoneRows;
740 }
741
742 /**
743 * @param string $vtimezoneRow
744 * @return static
745 */
746 private function setVtimezoneRow( $vtimezoneRow )
747 {
748 $this->vtimezoneRows[] = $vtimezoneRow;
749 return $this;
750 }
751
752 /**
753 * @param array $vtimezoneRows
754 * @return static
755 */
756 private function setVtimezoneRows( array $vtimezoneRows = [] )
757 {
758 $this->vtimezoneRows = $vtimezoneRows;
759 return $this;
760 }
761
762
763 /**
764 * @return string
765 */
766 public function getOutputiCal()
767 {
768 return $this->outputiCal;
769 }
770
771 /**
772 * Replace tz in outputiCal
773 *
774 * @param string $tzidOld
775 * @param string $tzidNew
776 * @return static
777 */
778 private function replaceTzidInOutputiCal( $tzidOld, $tzidNew )
779 {
780 $this->outputiCal = str_replace( $tzidOld, $tzidNew, $this->outputiCal );
781 return $this;
782 }
783
784 /**
785 * Append outputiCal from row
786 *
787 * @param string $row
788 * @return static
789 */
790 private function setOutputiCalRow( $row )
791 {
792 $this->outputiCal .= StringFactory::size75( $row );
793 return $this;
794 }
795
796 /**
797 * Append outputiCal row, built from propName, value, propAttr
798 *
799 * @param string $propName
800 * @param string $value
801 * @param array $propAttr
802 * @return static
803 */
804 private function setOutputiCalRowElements( $propName, $value, $propAttr )
805 {
806 $params = ParameterFactory::createParams( $propAttr );
807 $this->outputiCal .= StringFactory::createElement( $propName, $params, $value );
808 return $this;
809 }
810
811
812 /**
813 * @param string $otherTz
814 * @return string|bool|array bool false on key not found
815 */
816 public function getOtherTzPhpRelations( $otherTz = null )
817 {
818 if( ! empty( $otherTz )) {
819 return $this->hasOtherTzPHPtzMap( $otherTz )
820 ? $this->otherTzPhpRelations[$otherTz]
821 : false;
822 }
823 return $this->otherTzPhpRelations;
824 }
825
826 /**
827 * @param string $otherTzKey
828 * @return bool
829 */
830 public function hasOtherTzPHPtzMap( $otherTzKey )
831 {
832 return ( isset( $this->otherTzPhpRelations[$otherTzKey] ));
833 }
834
835 /**
836 * @param string $otherTzKey
837 * @param string $phpTz
838 * @param bool $doTzAssert
839 * @throws InvalidArgumentException
840 * @return static
841 */
842 public function addOtherTzPhpRelation( $otherTzKey, $phpTz, $doTzAssert = true )
843 {
844 if( $doTzAssert ) {
845 DateTimeZoneFactory::assertDateTimeZone( $phpTz );
846 }
847 $this->otherTzPhpRelations[$otherTzKey] = $phpTz;
848 return $this;
849 }
850
851 /**
852 * @param array $otherTzPhpRelations
853 * @return static
854 */
855 private function addOtherTzPhpRelations( array $otherTzPhpRelations )
856 {
857 $this->otherTzPhpRelations = $otherTzPhpRelations;
858 return $this;
859 }
860
861
862 /**
863 * @var array iCal component non-UTC date-property collection
864 */
865 private static $TZIDPROPS = [
866 Vcalendar::DTSTART,
867 Vcalendar::DTEND,
868 Vcalendar::DUE,
869 Vcalendar::RECURRENCE_ID,
870 Vcalendar::EXDATE,
871 Vcalendar::RDATE
872 ];
873
874 /**
875 * @var string
876 */
877 private static $BEGIN = 'BEGIN';
878 private static $BEGINVTIMEZONE = 'BEGIN:VTIMEZONE';
879 private static $BEGINSTANDARD = 'BEGIN:STANDARD';
880 private static $BEGINDAYLIGHT = 'BEGIN:DAYLIGHT';
881 private static $END = 'END';
882 private static $ENDVTIMEZONE = 'END:VTIMEZONE';
883 private static $ENDSTANDARD = 'END:STANDARD';
884 private static $ENDDAYLIGHT = 'END:DAYLIGHT';
885 }
886