PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 2.9.19.4
Pods – Custom Content Types and Fields v2.9.19.4
2.7.31.4 2.8.23.5 2.9.19.5 3.0.10.5 3.1.4.3 3.2.8.4 3.3.9.2 2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / tribe-common / src / Tribe / Timezones.php
pods / tribe-common / src / Tribe Last commit date
Admin 2 weeks ago Ajax 2 weeks ago Asset 2 weeks ago Context 2 weeks ago Customizer 2 weeks ago Debug_Bar 2 weeks ago Dialog 2 weeks ago Documentation 2 weeks ago Duplicate 2 weeks ago Editor 2 weeks ago Image 2 weeks ago JSON_LD 2 weeks ago Languages 2 weeks ago Log 2 weeks ago Meta 2 weeks ago Models 2 weeks ago PUE 2 weeks ago Process 2 weeks ago Promoter 2 weeks ago REST 2 weeks ago Repository 2 weeks ago Service_Providers 2 weeks ago Shortcode 2 weeks ago Support 2 weeks ago Tabbed_View 2 weeks ago Tooltip 2 weeks ago Traits 2 weeks ago Utils 2 weeks ago Validator 2 weeks ago Widget 2 weeks ago Abstract_Deactivation.php 2 weeks ago Abstract_Plugin_Register.php 2 weeks ago App_Shop.php 2 weeks ago Assets.php 2 weeks ago Assets_Pipeline.php 2 weeks ago Autoloader.php 2 weeks ago Cache.php 2 weeks ago Cache_Listener.php 2 weeks ago Changelog_Reader.php 2 weeks ago Container.php 2 weeks ago Context.php 2 weeks ago Cost_Utils.php 2 weeks ago Credits.php 2 weeks ago Customizer.php 2 weeks ago DB_Lock.php 2 weeks ago Data.php 2 weeks ago Date_Utils.php 2 weeks ago Db.php 2 weeks ago Debug.php 2 weeks ago Dependency.php 2 weeks ago Deprecation.php 2 weeks ago Editor.php 2 weeks ago Error.php 2 weeks ago Exception.php 2 weeks ago Extension.php 2 weeks ago Extension_Loader.php 2 weeks ago Feature_Detection.php 2 weeks ago Field.php 2 weeks ago Field_Conditional.php 2 weeks ago Freemius.php 2 weeks ago Log.php 2 weeks ago Main.php 2 weeks ago Notices.php 2 weeks ago Plugin_Meta_Links.php 2 weeks ago Plugins.php 2 weeks ago Plugins_API.php 2 weeks ago Post_History.php 2 weeks ago Post_Transient.php 2 weeks ago Promise.php 2 weeks ago Repository.php 2 weeks ago Rewrite.php 2 weeks ago Settings.php 2 weeks ago Settings_Manager.php 2 weeks ago Settings_Tab.php 2 weeks ago Simple_Table.php 2 weeks ago Support.php 2 weeks ago Tabbed_View.php 2 weeks ago Template.php 2 weeks ago Template_Factory.php 2 weeks ago Template_Part_Cache.php 2 weeks ago Templates.php 2 weeks ago Terms.php 2 weeks ago Timezones.php 2 weeks ago Tracker.php 2 weeks ago Updater.php 2 weeks ago Validate.php 2 weeks ago View_Helpers.php 2 weeks ago
Timezones.php
626 lines
1 <?php
2
3 /**
4 * Helpers for handling timezone based event datetimes.
5 *
6 * In our timezone logic, the term "local" refers to the locality of an event
7 * rather than the local WordPress timezone.
8 */
9 class Tribe__Timezones {
10 const SITE_TIMEZONE = 'site';
11 const EVENT_TIMEZONE = 'event';
12
13 /**
14 * Container for reusable DateTimeZone objects.
15 *
16 * @var array
17 */
18 protected static $timezones = [];
19
20 public static function init() {
21 self::invalidate_caches();
22 }
23
24 /**
25 * Clear any cached timezone-related values when appropriate.
26 *
27 * Currently we are concerned only with the site timezone abbreviation.
28 */
29 protected static function invalidate_caches() {
30 add_filter( 'pre_update_option_gmt_offset', [ __CLASS__, 'clear_site_timezone_abbr' ] );
31 add_filter( 'pre_update_option_timezone_string', [ __CLASS__, 'clear_site_timezone_abbr' ] );
32 }
33
34 /**
35 * Wipe the cached site timezone abbreviation, if set.
36 *
37 * @param mixed $option_val (passed through without modification)
38 *
39 * @return mixed
40 */
41 public static function clear_site_timezone_abbr( $option_val ) {
42 delete_transient( 'tribe_events_wp_timezone_abbr' );
43 return $option_val;
44 }
45
46 /**
47 * Returns the current site-wide timezone string abbreviation, if it can be
48 * determined or falls back on the full timezone string/offset text.
49 *
50 * @param string $date
51 *
52 * @return string
53 */
54 public static function wp_timezone_abbr( $date ) {
55 $timezone_string = self::wp_timezone_string();
56 $abbr = self::abbr( $date, $timezone_string );
57
58 return empty( $abbr ) ? $timezone_string : $abbr;
59 }
60
61 /**
62 * Returns the current site-wide timezone string.
63 *
64 * Based on the core WP code found in wp-admin/options-general.php.
65 *
66 * @return string
67 */
68 public static function wp_timezone_string() {
69 $current_offset = get_option( 'gmt_offset' );
70 $tzstring = get_option( 'timezone_string' );
71
72 // Return the timezone string if already set
73 if ( ! empty( $tzstring ) ) {
74 return $tzstring;
75 }
76
77 // Otherwise return the UTC offset
78 if ( 0 == $current_offset ) {
79 return 'UTC+0';
80 } elseif ( $current_offset < 0 ) {
81 return 'UTC' . $current_offset;
82 }
83
84 return 'UTC+' . $current_offset;
85 }
86
87 /**
88 * Attempts to provide the correct timezone abbreviation for the provided timezone string
89 * on the date given (and so should account for daylight saving time, etc).
90 *
91 * @param string|DateTime|DateTimeImmutable $date The date string representation or object.
92 * @param string|DateTimeZone $timezone_string The timezone string or object.
93 *
94 * @return string
95 */
96 public static function abbr( $date, $timezone_string ) {
97 try {
98 $timezone_object = $timezone_string instanceof DateTimeZone
99 ? $timezone_string
100 : new DateTimeZone( $timezone_string );
101 $date_time = $date instanceof DateTime
102 || ( class_exists( 'DateTimeImmutable' ) && $date instanceof DateTimeImmutable )
103 ? $date
104 : Tribe__Date_Utils::build_date_object( $date, $timezone_object );
105
106 $abbr = $date_time->format( 'T' );
107
108 // If PHP date "T" format is a -03 or +03, it's a bugged abbreviation, we can find it manually.
109 if ( 0 === strpos( $abbr, '-' ) || 0 === strpos( $abbr, '+' ) ) {
110 $abbreviations = timezone_abbreviations_list();
111
112 foreach ( $abbreviations as $abbreviation => $timezones ) {
113 foreach ( $timezones as $timezone ) {
114 if ( $timezone['timezone_id'] === $timezone_string ) {
115 return strtoupper( $abbreviation );
116 }
117 }
118 }
119 }
120 } catch ( Exception $e ) {
121 $abbr = '';
122 }
123
124 return $abbr;
125 }
126
127 /**
128 * Helper function to retrieve the timezone string for a given UTC offset
129 *
130 * This is a close copy of WooCommerce's wc_timezone_string() method
131 *
132 * @param string $offset UTC offset
133 *
134 * @return string
135 */
136 public static function generate_timezone_string_from_utc_offset( $offset ) {
137 if ( ! self::is_utc_offset( $offset ) ) {
138 return $offset;
139 }
140
141 // ensure we have the minutes on the offset
142 if ( ! strpos( $offset, ':' ) ) {
143 $offset .= ':00';
144 }
145
146 $offset = str_replace( 'UTC', '', $offset );
147
148 list( $hours, $minutes ) = explode( ':', $offset );
149 $seconds = $hours * 60 * 60 + $minutes * 60;
150
151 // attempt to guess the timezone string from the UTC offset
152 $timezone = timezone_name_from_abbr( '', $seconds, 0 );
153
154 if ( false === $timezone ) {
155 $is_dst = (bool) date( 'I' );
156
157 foreach ( timezone_abbreviations_list() as $abbr ) {
158 foreach ( $abbr as $city ) {
159 if (
160 (bool) $city['dst'] === $is_dst
161 && intval( $city['offset'] ) === intval( $seconds )
162 && $city['timezone_id']
163 ) {
164 return $city['timezone_id'];
165 }
166 }
167 }
168
169 // fallback to UTC
170 return 'UTC';
171 }
172
173 return $timezone;
174 }
175
176 /**
177 * Tests to see if the timezone string is a UTC offset, ie "UTC+2".
178 *
179 * @param string $timezone
180 *
181 * @return bool
182 */
183 public static function is_utc_offset( $timezone ) {
184 $timezone = trim( $timezone );
185 return ( 0 === strpos( $timezone, 'UTC' ) && strlen( $timezone ) > 3 );
186 }
187
188 /**
189 * Returns a DateTimeZone object matching the representation in $tzstring where
190 * possible, or else representing UTC (or, in the worst case, false).
191 *
192 * If optional parameter $with_fallback is true, which is the default, then in
193 * the event it cannot find/create the desired timezone it will try to return the
194 * UTC DateTimeZone before bailing.
195 *
196 * @param string $tzstring
197 * @param bool $with_fallback = true
198 *
199 * @return DateTimeZone|false
200 */
201 public static function get_timezone( $tzstring, $with_fallback = true ) {
202 if ( isset( self::$timezones[ $tzstring ] ) ) {
203 return self::$timezones[ $tzstring ];
204 }
205
206 try {
207 self::$timezones[ $tzstring ] = new DateTimeZone( $tzstring );
208 return self::$timezones[ $tzstring ];
209 }
210 catch ( Exception $e ) {
211 if ( $with_fallback ) {
212 return self::get_timezone( 'UTC', true );
213 }
214 }
215
216 return false;
217 }
218
219 /**
220 * Confirms if the current timezone mode matches the $possible_mode.
221 *
222 * @param string $possible_mode
223 *
224 * @return bool
225 */
226 public static function is_mode( $possible_mode ) {
227 return $possible_mode === self::mode();
228 }
229
230 /**
231 * Returns a string representing the timezone/offset currently desired for
232 * the display of dates and times.
233 *
234 * @return string
235 */
236 public static function mode() {
237 $mode = self::EVENT_TIMEZONE;
238
239 if ( 'site' === tribe_get_option( 'tribe_events_timezone_mode' ) ) {
240 $mode = self::SITE_TIMEZONE;
241 }
242
243 return apply_filters( 'tribe_events_current_display_timezone', $mode );
244 }
245
246 /**
247 * Tries to convert the provided $datetime to UTC from the timezone represented by $tzstring.
248 *
249 * Though the usual range of formats are allowed, $datetime ordinarily ought to be something
250 * like the "Y-m-d H:i:s" format (ie, no timezone information). If it itself contains timezone
251 * data, the results may be unexpected.
252 *
253 * In those cases where the conversion fails to take place, the $datetime string will be
254 * returned untouched.
255 *
256 * @param string $datetime
257 * @param string $tzstring
258 * @param string $format The optional format of the resulting date, defaults to
259 * `Tribe__Date_Utils::DBDATETIMEFORMAT`.
260 *
261 * @return string
262 */
263 public static function to_utc( $datetime, $tzstring, $format = null ) {
264 if ( self::is_utc_offset( $tzstring ) ) {
265 return self::apply_offset( $datetime, $tzstring, true );
266 }
267
268 $local = self::get_timezone( $tzstring );
269 $utc = self::get_timezone( 'UTC' );
270
271 $new_datetime = date_create( $datetime, $local );
272
273 if ( $new_datetime ) {
274 $new_datetime->setTimezone( $utc );
275 $format = ! empty( $format ) ? $format : Tribe__Date_Utils::DBDATETIMEFORMAT;
276
277 return $new_datetime->format( $format );
278 }
279
280 // Fallback to the unmodified datetime if there was a failure during conversion
281 return $datetime;
282 }
283
284 /**
285 * Tries to convert the provided $datetime to the timezone represented by $tzstring.
286 *
287 * This is the sister function of self::to_utc() - please review the docs for that method
288 * for more information.
289 *
290 * @param string $datetime
291 * @param string $tzstring
292 *
293 * @return string
294 */
295 public static function to_tz( $datetime, $tzstring ) {
296
297 if ( self::is_utc_offset( $tzstring ) ) {
298
299 return self::apply_offset( $datetime, $tzstring );
300 }
301
302 $local = self::get_timezone( $tzstring );
303 $utc = self::get_timezone( 'UTC' );
304
305 $new_datetime = date_create( $datetime, $utc );
306
307 if ( $new_datetime && $new_datetime->setTimezone( $local ) ) {
308 return $new_datetime->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
309 }
310
311 // Fallback to the unmodified datetime if there was a failure during conversion
312 return $datetime;
313 }
314
315 /**
316 * Localizes a date or timestamp using WordPress timezone and returns it in the specified format.
317 *
318 * @param string $format The format the date shouuld be formatted to.
319 * @param string|int $date The date UNIX timestamp or `strtotime` parseable string.
320 * @param string $timezone An optional timezone string identifying the timezone the date shoudl be localized
321 * to; defaults to the WordPress installation timezone (if available) or to the system
322 * timezone.
323 *
324 * @return string|bool The parsed date in the specified format and localized to the system or specified
325 * timezone, or `false` if the specified date is not a valid date string or timestamp
326 * or the specified timezone is not a valid timezone string.
327 */
328 public static function localize_date( $format = null, $date = null, $timezone = null ) {
329 if ( empty( $timezone ) ) {
330 $timezone = self::wp_timezone_string();
331 }
332
333 $timezone = self::generate_timezone_string_from_utc_offset( $timezone );
334
335 try {
336 $timezone_object = new DateTimeZone( $timezone );
337
338 if ( Tribe__Date_Utils::is_timestamp( $date ) ) {
339 $date = new DateTime( "@{$date}" );
340 } else {
341 $date = new DateTime( $date );
342 }
343 } catch ( Exception $e ) {
344 return false;
345 }
346
347 $date->setTimezone( $timezone_object );
348
349 return $date->format( $format );
350 }
351
352 /**
353 * Converts a date string or timestamp to a destination timezone.
354 *
355 * @param string|int $date Either a string parseable by the `strtotime` function or a UNIX timestamp.
356 * @param string $from_timezone The timezone of the source date.
357 * @param string $to_timezone The timezone the destination date should use.
358 * @param string $format The format that should be used for the destination date.
359 *
360 * @return string The formatted and converted date.
361 */
362 public static function convert_date_from_timezone( $date, $from_timezone, $to_timezone, $format ) {
363 if ( ! Tribe__Date_Utils::is_timestamp( $date ) ) {
364 $from_date = new DateTime( $date, new DateTimeZone( $from_timezone ) );
365 $timestamp = $from_date->format( 'U' );
366 } else {
367 $timestamp = $date;
368 }
369
370 $to_date = new DateTime( "@{$timestamp}", new DateTimeZone( $to_timezone ) );
371
372 return $to_date->format( $format );
373 }
374
375 /**
376 * Whether the candidate timezone is a valid PHP timezone or a supported UTC offset.
377 *
378 * @param string $candidate
379 *
380 * @return bool
381 */
382 public static function is_valid_timezone( $candidate ) {
383 if ( self::is_utc_offset( $candidate ) ) {
384 return true;
385 }
386 try {
387 new DateTimeZone( $candidate );
388 } catch ( Exception $e ) {
389 return false;
390 }
391
392 return true;
393 }
394
395 /**
396 * Given a string in the form "UTC+2.5" returns the corresponding DateTimeZone object.
397 *
398 * If this is not possible or if $utc_offset_string does not match the expected pattern,
399 * boolean false is returned.
400 *
401 * @todo revise to eliminate all of these: maybe_get_tz_name, apply_offset, timezone_from_utc_offset, and adjust_timestamp
402 *
403 * @since 4.6.3
404 *
405 * @param string $utc_offset_string
406 *
407 * @return DateTimeZone | bool
408 */
409 public static function timezone_from_utc_offset( $utc_offset_string ) {
410 // Test for strings looking like "UTC-2" or "UTC+5.25" etc
411 if ( ! preg_match( '/^UTC[+-][0-9.]{1,4}$/', $utc_offset_string ) ) {
412 return false;
413 }
414
415 // Breakdown into polarity, hours and minutes
416 $parts = explode( '.', substr( $utc_offset_string, 4 ) );
417 $hours = (int) $parts[ 0 ];
418 $fraction = isset( $parts[ 1 ] ) ? '0.' . (int) $parts[ 1 ] : 0;
419 $minutes = $fraction * 60;
420 $polarity = substr( $utc_offset_string, 3, 1 );
421
422 // Reassemble in the form +/-hhmm (ie "-0200" or "+0930")
423 $utc_offset = sprintf( $polarity . "%'.02d%'.02d", $hours, $minutes );
424
425 if ( '+0000' === $utc_offset || '-0000' === $utc_offset ) {
426 $utc_offset = 'UTC';
427 }
428
429 // Use this to build a new DateTimeZone
430 try {
431 return new DateTimeZone( $utc_offset );
432 } catch ( Exception $e ) {
433 return false;
434 }
435 }
436
437 /**
438 * Applies an time offset to the specified date time.
439 *
440 * @todo revise to eliminate all of these: maybe_get_tz_name, apply_offset, timezone_from_utc_offset, and adjust_timestamp
441 *
442 * @param string $datetime The date and time string in a valid date format.
443 * @param int|string $offset (string or numeric offset)
444 * @param bool $invert = false Whether the offset should be added (`true`) or
445 * subtracted (`false`); signum operations carry over so
446 * `-(-23) = +23`.
447 *
448 * @return string
449 */
450 public static function apply_offset( $datetime, $offset, $invert = false ) {
451 // Normalize
452 $offset = strtolower( trim( $offset ) );
453
454 // Strip any leading "utc" text if set
455 if ( 0 === strpos( $offset, 'utc' ) ) {
456 $offset = substr( $offset, 3 );
457 }
458
459 // It's possible no adjustment will be needed
460 if ( 0 === (int) $offset ) {
461 return $datetime;
462 }
463
464 // if the offset contains fractions like :15, :30 or :45 convert them
465 $supported_offsets = [
466 '/:15$/' => '.25',
467 '/:30$/' => '.5',
468 '/:45$/' => '.75',
469 ];
470 $offset = preg_replace( array_keys( $supported_offsets ), array_values( $supported_offsets ), $offset );
471
472 // Convert the offset to minutes for easier handling of fractional offsets
473 $offset = (int) ( $offset * 60 );
474
475 // Invert the offset? Useful for stripping an offset that has already been applied
476 if ( $invert ) {
477 $offset *= - 1;
478 }
479
480 if ( $offset > 0 ) {
481 $offset = '+' . $offset;
482 }
483
484 $offset = $offset . ' minutes';
485
486 $offset_datetime = date_create( $datetime );
487
488 if ( $offset_datetime && $offset_datetime->modify( $offset ) ) {
489 return $offset_datetime->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
490 }
491
492 return $datetime;
493 }
494
495 /**
496 * Try to figure out the Timezone name base on offset
497 *
498 * @since 4.0.7
499 *
500 * @todo revise to eliminate all of these: maybe_get_tz_name, apply_offset, timezone_from_utc_offset, and adjust_timestamp
501 *
502 * @param string|int|float $timezone The timezone
503 *
504 * @return string The Guessed Timezone String
505 */
506 public static function maybe_get_tz_name( $timezone ) {
507 if ( ! self::is_utc_offset( $timezone ) && ! is_numeric( $timezone ) ) {
508 return $timezone;
509 }
510
511 if ( ! is_numeric( $timezone ) ) {
512 $offset = str_replace( 'utc', '', trim( strtolower( $timezone ) ) );
513 } else {
514 $offset = $timezone;
515 }
516
517
518 // try to get timezone from gmt_offset, respecting daylight savings
519 $timezone = timezone_name_from_abbr( null, $offset * 3600, true );
520
521 // if that didn't work, maybe they don't have daylight savings
522 if ( false === $timezone ) {
523 $timezone = timezone_name_from_abbr( null, $offset * 3600, false );
524 }
525
526 // and if THAT didn't work, round the gmt_offset down and then try to get the timezone respecting daylight savings
527 if ( false === $timezone ) {
528 $timezone = timezone_name_from_abbr( null, (int) $offset * 3600, true );
529 }
530
531 // lastly if that didn't work, round the gmt_offset down and maybe that TZ doesn't do daylight savings
532 if ( false === $timezone ) {
533 $timezone = timezone_name_from_abbr( null, (int) $offset * 3600, false );
534 }
535
536 return $timezone;
537 }
538
539 /**
540 * Accepts a unix timestamp and adjusts it so that when it is used to constitute
541 * a new datetime string, that string reflects the designated timezone.
542 *
543 * @todo revise to eliminate all of these: maybe_get_tz_name, apply_offset, timezone_from_utc_offset, and adjust_timestamp
544 *
545 * @deprecated 4.7.12
546 *
547 * @param string $unix_timestamp
548 * @param string $tzstring
549 *
550 * @return string
551 */
552 public static function adjust_timestamp( $unix_timestamp, $tzstring ) {
553 try {
554 $local = self::get_timezone( $tzstring );
555
556 $datetime = date_create_from_format( 'U', $unix_timestamp )->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
557
558 // We prefer format('U') to getTimestamp() here due to our requirement for compatibility with PHP 5.2
559 return date_create_from_format( 'Y-m-d H:i:s', $datetime, $local )->format( 'U' );
560 }
561 catch( Exception $e ) {
562 return $unix_timestamp;
563 }
564 }
565
566 /**
567 * Returns a valid timezone object built from the passed timezone or from the
568 * site one if a timezone in not passed.
569 *
570 * @since 4.9.5
571 *
572 * @param string|null|DateTimeZone $timezone A DateTimeZone object, a timezone string
573 * or `null` to build an object using the site one.
574 *
575 * @return DateTimeZone The built DateTimeZone object.
576 */
577 public static function build_timezone_object( $timezone = null ) {
578 if ( $timezone instanceof DateTimeZone ) {
579 return $timezone;
580 }
581
582 /** @var Tribe__Cache $cache */
583 $cache = tribe('cache');
584
585 if ( is_string( $timezone ) && $cached = $cache[ __METHOD__ . $timezone ] ) {
586 return clone $cached;
587 }
588
589 $timezone = null === $timezone ? self::wp_timezone_string() : $timezone;
590
591 try {
592 $object = new DateTimeZone( self::get_valid_timezone( $timezone ) );
593 } catch ( Exception $e ) {
594 return new DateTimeZone( 'UTC' );
595 }
596
597 if ( is_string( $timezone ) ) {
598 $cache[ __METHOD__ . $timezone ] = $object;
599 }
600
601 return $object;
602 }
603
604 /**
605 * Parses the timezone string to validate or convert it into a valid one.
606 *
607 * @since 4.9.5
608 *
609 * @param string|\DateTimeZone $timezone_candidate The timezone string candidate.
610 *
611 * @return string The validated timezone string or a valid timezone string alternative.
612 */
613 public static function get_valid_timezone( $timezone_candidate ) {
614 if ( $timezone_candidate instanceof DateTimeZone ) {
615 return $timezone_candidate->getName();
616 }
617
618 $timezone_string = preg_replace( '/[+-]0$/', '', $timezone_candidate );
619 $timezone_string = self::is_utc_offset( $timezone_string )
620 ? self::generate_timezone_string_from_utc_offset( $timezone_string )
621 : $timezone_string;
622
623 return $timezone_string;
624 }
625 }
626