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 / VtimezonePopulateFactory.php

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

430 lines 16.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 DateTimeInterface;
34 use Exception;
35 use InvalidArgumentException;
36 use Kigkonsult\Icalcreator\Vcalendar;
37
38 use function array_keys;
39 use function count;
40 use function date;
41 use function end;
42 use function is_array;
43 use function reset;
44 use function sprintf;
45
46 /**
47 * VtimezonePopulateFactory class, iCalcreator instance Vtimezone populate class
48 *
49 * Result when timezone is 'Europe/Stockholm' and no from/to arguments
50 * BEGIN:VTIMEZONE
51 * TZID:Europe/Stockholm
52 * BEGIN:STANDARD
53 * DTSTART:20101031T020000
54 * TZOFFSETFROM:+0200
55 * TZOFFSETTO:+0100
56 * TZNAME:CET
57 * END:STANDARD
58 * BEGIN:DAYLIGHT
59 * DTSTART:20100328T030000
60 * TZOFFSETFROM:+0100
61 * TZOFFSETTO:+0200
62 * TZNAME:CEST
63 * END:DAYLIGHT
64 * END:VTIMEZONE
65 *
66 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
67 * @since 2.29.16 - 2020-01-25
68 *
69 * Contributors :
70 * Yitzchok Lavi <icalcreator@onebigsystem.com>
71 * jpirkey
72 *
73 */
74 class VtimezonePopulateFactory
75 {
76 /*
77 * @var string for populate method (and descendents)
78 * @static
79 */
80 private static $ABBR = 'abbr';
81 private static $AT = '@';
82 private static $ISDST = 'isdst';
83 private static $OFFSET = 'offset';
84 private static $SECONDS = 'seconds';
85 private static $TIME = 'time';
86 private static $TS = 'ts';
87 private static $YMD = 'Ymd';
88
89 /**
90 * Return calendar with timezone and standard/daylight components
91 *
92 * @param Vcalendar $calendar iCalcreator calendar instance
93 * @param string $timezone valid timezone acceptable by PHP5 DateTimeZone
94 * @param array $xProp *[x-propName => x-propValue]
95 * @param DateTimeInterface|int $start .. or unix timestamp
96 * @param DateTimeInterface|int $end .. or unix timestamp
97 * @return Vcalendar
98 * @throws Exception
99 * @throws InvalidArgumentException
100 * @static
101 * @since 2.29.16 - 2020-01-25
102 */
103 public static function process(
104 Vcalendar $calendar,
105 $timezone = null,
106 $xProp = [],
107 $start = null,
108 $end = null
109 ) {
110 $timezone = self::getTimezone( $calendar, $timezone, $xProp );
111 $foundTrans = [];
112 if( ! DateTimeZoneFactory::isUTCtimeZone( $timezone )) {
113 list( $start, $end ) =
114 self::ensureStartAndEnd( $calendar, $timezone, $start, $end );
115 $foundTrans = self::findTransitions( $timezone, $start, $end );
116 }
117 while( false !== $calendar->deleteComponent( Vcalendar::VTIMEZONE )) {
118 continue;
119 }
120 $timezoneComp = $calendar->newVtimezone();
121 $timezoneComp->setTzid( $timezone );
122 if( ! empty( $xProp )) {
123 foreach( (array) $xProp as $xPropName => $xPropValue ) {
124 if( StringFactory::isXprefixed( $xPropName )) {
125 $timezoneComp->setXprop( $xPropName, $xPropValue );
126 }
127 }
128 } // end if
129 foreach( $foundTrans as $tix => $trans ) {
130 // create standard/daylight subcomponents
131 $subComp = ( true !== $trans[self::$ISDST] )
132 ? $timezoneComp->newStandard()
133 : $timezoneComp->newDaylight();
134 $subComp->setDtstart( $trans[self::$TIME] );
135 if( ! empty( $trans[self::$ABBR] )) {
136 $subComp->setTzname( $trans[self::$ABBR] );
137 }
138 if( isset( $trans[Vcalendar::TZOFFSETFROM] )) {
139 $subComp->setTzoffsetfrom(
140 DateTimeZoneFactory::secondsToOffset( $trans[Vcalendar::TZOFFSETFROM] )
141 );
142 }
143 $subComp->setTzoffsetto(
144 DateTimeZoneFactory::secondsToOffset( $trans[self::$OFFSET] )
145 );
146 if( isset( $trans[Vcalendar::RDATE] )) {
147 foreach( $trans[Vcalendar::RDATE] as $rDate ) {
148 // single RDATEs, each with single date
149 $subComp->setRdate( $rDate );
150 }
151 }
152 } // end foreach
153 return $calendar;
154 }
155
156 /**
157 * Return timezone from 1. tz arg, 2. xProps arg 3. calendar/vtimezone X-prop X_WR_TIMEZONE/X-LIC-LOCATION, 4. UTC
158 *
159 * @param Vcalendar $calendar iCalcreator calendar instance
160 * @param string $timezone valid timezone acceptable by PHP5 DateTimeZone
161 * @param array $xProp *[x-propName => x-propValue]
162 * @return string
163 * @static
164 * @since 2.29.22 - 2019-08-26
165 */
166 private static function getTimezone(
167 Vcalendar $calendar,
168 $timezone = null,
169 $xProp = []
170 ) {
171 switch( true ) {
172 case ( ! empty( $timezone )) :
173 break;
174 case Util::issetAndNotEmpty( $xProp, Vcalendar::X_WR_TIMEZONE ) :
175 $timezone = $xProp[Vcalendar::X_WR_TIMEZONE];
176 break;
177 case Util::issetAndNotEmpty( $xProp, Vcalendar::X_LIC_LOCATION ) :
178 $timezone = $xProp[Vcalendar::X_LIC_LOCATION];
179 break;
180 case ( false !==
181 ( $xProp = $calendar->getXprop( Vcalendar::X_WR_TIMEZONE ))) :
182 $timezone = $xProp[1];
183 break;
184 case ( false !==
185 ( $comp = $calendar->getComponent( Vcalendar::VTIMEZONE ))) :
186 $calendar->reset();
187 if( false !== ( $xProp = $comp->getXprop( Vcalendar::X_LIC_LOCATION ))) {
188 $timezone = $xProp[1];
189 break;
190 }
191 // fall through
192 default :
193 return Vcalendar::UTC;
194 } // end switch
195 DateTimeZoneFactory::assertDateTimeZone( $timezone );
196 return $timezone;
197 }
198
199 /**
200 * Return valid (ymd-)from/tom
201 *
202 * @param Vcalendar $calendar
203 * @param string $timezone valid timezone acceptable by PHP5 DateTimeZone
204 * @param DateTimeInterface|int $start .. or unix timestamp
205 * @param DateTimeInterface|int $end .. or unix timestamp
206 * @return array
207 * @throws InvalidArgumentException
208 * @throws Exception
209 * @static
210 * @since 2.27.15 - 2019-03-21
211 */
212 private static function ensureStartAndEnd(
213 Vcalendar $calendar,
214 $timezone,
215 $start = null,
216 $end = null
217 ) {
218 static $NUMBEROFDAYSBEFORE = 365;
219 static $FMTBEFORE = '-%d days';
220 static $NUMBEROFDAYSAFTER = 548;
221 static $FMTAFTER = '+%d days';
222 static $ERRMSG = 'Date are not in order: %d - %d';
223 switch( true ) {
224 case empty( $start ) :
225 break;
226 case ( $start instanceof DateTimeInterface ) :
227 $start = $start->getTimestamp();
228 break;
229 default :
230 Util::assertInteger( $start, __METHOD__ );
231 break;
232 } // end switch
233 switch( true ) {
234 case empty( $end ) :
235 break;
236 case ( $end instanceof DateTimeInterface ) :
237 $end = $end->getTimestamp();
238 break;
239 default :
240 Util::assertInteger( $end, __METHOD__ );
241 break;
242 } // end switch
243 switch( true ) {
244 case ( ! empty( $start ) && ! empty( $end )) :
245 break;
246 case ( ! empty( $start )) : // set to = +18 month (i.e 548 days)
247 $end = $start + ( 3600 * 24 * $NUMBEROFDAYSAFTER );
248 break;
249 case ( ! empty( $end )) : // set from = -12 month (i.e 365 days)
250 $start = $end - ( 3600 * 24 * $NUMBEROFDAYSBEFORE );
251 break;
252 default :
253 $dtstarts = array_keys( $calendar->getProperty( Vcalendar::DTSTART ));
254 switch( true ) {
255 case ( empty( $dtstarts )) :
256 $start = DateTimeFactory::factory( null, $timezone );
257 $end = ( clone $start );
258 break;
259 case ( 1 == count( $dtstarts )) :
260 $start = DateTimeFactory::factory(
261 reset( $dtstarts ),
262 $timezone
263 );
264 $end = ( clone $start );
265 break;
266 default :
267 $start = DateTimeFactory::factory(
268 reset( $dtstarts ),
269 $timezone
270 );
271 $end = DateTimeFactory::factory(
272 end( $dtstarts ),
273 $timezone
274 );
275 break;
276 } // end switch
277 $start = $start->modify( sprintf( $FMTBEFORE, $NUMBEROFDAYSBEFORE ))
278 ->getTimestamp();
279 $end = $end->modify( sprintf( $FMTAFTER, $NUMBEROFDAYSAFTER ))
280 ->getTimestamp();
281 break;
282 } // end switch
283 if( $start > $end ) {
284 throw new InvalidArgumentException( sprintf( $ERRMSG, $start, $end ));
285 }
286 return [ $start, $end ];
287 }
288
289 /**
290 * Return (prep'd) datetimezone transitions
291 *
292 * @param string $timezone
293 * @param int $start
294 * @param int $end
295 * @return array
296 * @throws InvalidArgumentException
297 * @throws Exception
298 * @static
299 * @since 2.27.15 - 2019-02-23
300 */
301 private static function findTransitions( $timezone, $start, $end )
302 {
303 static $Y = 'Y';
304 $foundTrans = [];
305 $prevOffsetFrom = 0;
306 $stdIx = $dlghtIx = -1;
307 $backupTrans = false;
308 $dateFromYmd = DateTimeFactory::setDateTimeTimeZone(
309 DateTimeFactory::factory( self::$AT . $start ), $timezone )
310 ->format( DateTimeFactory::$Ymd );
311 $dateToYmd = DateTimeFactory::setDateTimeTimeZone(
312 DateTimeFactory::factory( self::$AT . $end ), $timezone )
313 ->format( DateTimeFactory::$Ymd );
314 // extend search-args to assure we start/end at daylight shifts
315 $start -= ( 3600 * 24 * 275 );
316 $end += ( 3600 * 24 * 185 );
317 $transitions =
318 DateTimeZoneFactory::getDateTimeZoneTransitions( $timezone, $start, $end );
319 // all transitions in date-time order!!
320 foreach( $transitions as $tix => $trans ) {
321 if( 0 > (int) date( $Y, $trans[self::$TS] )) {
322 // skip negative year... but save offset
323 $prevOffsetFrom = $trans[self::$OFFSET];
324 // previous trans offset will be 'next' trans offsetFrom
325 continue;
326 } // end if
327 $transDate = DateTimeFactory::factory( self::$AT . $trans[self::$TS] );
328 $transDateYmd = $transDate->format( self::$YMD );
329 if( $transDateYmd < $dateFromYmd ) {
330 // previous trans offset will be 'next' trans offsetFrom
331 $prevOffsetFrom = $trans[self::$OFFSET];
332 // we save it in case we don't find any match
333 $backupTrans = $trans;
334 $backupTrans[Vcalendar::TZOFFSETFROM] =
335 ( 0 < $tix ) ? $transitions[$tix - 1][self::$OFFSET] : 0;
336 continue;
337 } // end if
338 if(( $transDateYmd > $dateToYmd ) && ( -1 < ( $stdIx + $dlghtIx ))) {
339 // loop always (?) breaks here with, at least, one standard/daylight
340 break;
341 }
342 if( ! empty( $prevOffsetFrom ) || ( 0 == $prevOffsetFrom )) {
343 // set previous offsetto as offsetFrom
344 $trans[Vcalendar::TZOFFSETFROM] = $prevOffsetFrom;
345 // convert utc time to local time
346 $transDate->modify( $trans[Vcalendar::TZOFFSETFROM] . self::$SECONDS );
347 $trans[self::$TIME] = $transDate;
348 } // end if
349 $prevOffsetFrom = $trans[self::$OFFSET];
350 if( true !== $trans[self::$ISDST] ) {
351 // standard timezone, build RDATEs (in date order)
352 if(( -1 < $stdIx ) &&
353 self::matchTrans( $foundTrans[$stdIx], $trans )) {
354 $foundTrans[$stdIx][Vcalendar::RDATE][] = clone $trans[self::$TIME];
355 continue;
356 }
357 $stdIx = $tix;
358 } // end standard timezone
359 else {
360 // daylight timezone, build RDATEs (in date order)
361 if(( -1 < $dlghtIx ) &&
362 self::matchTrans( $foundTrans[$dlghtIx], $trans )) {
363 $foundTrans[$dlghtIx][Vcalendar::RDATE][] = clone $trans[self::$TIME];
364 continue;
365 }
366 $dlghtIx = $tix;
367 } // end daylight timezone
368 $foundTrans[$tix] = $trans;
369 } // end foreach( $transitions as $tix => $trans )
370 if( empty( $foundTrans )) {
371 $foundTrans[0] = self::buildTrans( $backupTrans, $timezone );
372 }
373 return $foundTrans;
374 }
375
376 /**
377 * return bool true if foundTrans matches trans
378 *
379 * @param array $foundTrans
380 * @param array $trans
381 * @return bool
382 * @static
383 * @since 2.27.15 - 2019-02-23
384 */
385 private static function matchTrans( array $foundTrans, array $trans )
386 {
387 return
388 (( isset( $foundTrans[Vcalendar::TZOFFSETFROM] )) &&
389 ( $foundTrans[self::$ABBR] == $trans[self::$ABBR] ) &&
390 ( $foundTrans[Vcalendar::TZOFFSETFROM]
391 == $trans[Vcalendar::TZOFFSETFROM] ) &&
392 ( $foundTrans[self::$OFFSET] == $trans[self::$OFFSET] )
393 );
394 }
395
396 /**
397 * return (array build 'found'-trans
398 *
399 * @param array|bool $backupTrans
400 * @param string $timezone
401 * @return array
402 * @throws InvalidArgumentException
403 * @throws Exception
404 * @static
405 * @since 2.27.15 - 2019-02-23
406 */
407 private static function buildTrans( $backupTrans, $timezone )
408 {
409 static $NOW = 'now';
410 if( is_array( $backupTrans )) {
411 // we use the last transition (i.e. before startdate) for the tz info
412 $prevDate = DateTimeFactory::factory( self::$AT . $backupTrans[self::$TS] );
413 // convert utc date to 'local' date
414 $prevDate->modify( $backupTrans[Vcalendar::TZOFFSETFROM] . self::$SECONDS );
415 $backupTrans[self::$TIME] = $prevDate;
416 } // end if( $backupTrans )
417 else {
418 // or we use the timezone identifier to BUILD the standard tz info (?)
419 $prevDate = DateTimeFactory::factory( $NOW, $timezone );
420 $backupTrans = [
421 self::$TIME => $prevDate,
422 self::$OFFSET => $prevDate->format( Vcalendar::Z ),
423 Vcalendar::TZOFFSETFROM => $prevDate->format( Vcalendar::Z ),
424 self::$ISDST => false,
425 ];
426 }
427 return $backupTrans;
428 }
429 }
430