Add upstream plugins

Signed-off-by: Adrian Nöthlich <git@promasu.tech>
This commit is contained in:
2019-10-25 22:42:20 +02:00
parent 5d3c2ec184
commit 290736650a
1186 changed files with 302577 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
<?php
/**
* Library function for massive time conversion operations.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Converter {
/**
* @var Ai1ec_Registry_Object Instance of objects registry.
*/
protected $_registry = null;
/**
* Get reference of object registry.
*
* @param Ai1ec_Registry_Object $registry Injected objects registry.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_registry = $registry;
}
/**
* Change timezone of times provided.
*
* @param array $input List of time entries to convert.
* @param string $source_tz Timezone to convert from.
* @param string $target_tz Timezone to convert to.
* @param string $format Format of target time entries.
*
* @return array List of converted times.
*/
public function change_timezone(
array $input,
$source_tz,
$target_tz = 'UTC',
$format = 'U'
) {
$output = array();
foreach ( $input as $time ) {
try {
$time_object = $this->_registry->get(
'date.time',
$input,
$source_tz
);
$output[] = $time_object->format( $format, $target_tz );
unset( $time_object );
} catch ( Ai1ec_Date_Exception $exception ) {
// ignore
}
}
return $output;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Wrapper for `DateTimeZone` to extend it with convenient methods
*
* @author Justas Butkus <justas@butkus.lt>
* @since 2013.03.06
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Utility.Time
*/
class Ai1ec_Date_Date_Time_Zone extends DateTimeZone {
/**
* Map of transitions details for given timestamp
* {@see DateTimeZone::getTransitions()} for representation of details.
* Return a map of prev(ious), curr(ent) and next transitions for
* a given timestamp.
*
* @NOTICE: if we start accepting PHP 5.3 - update getTransitions
* usage, to add offsets.
*
* @param int $timestamp UNIX timestamp (UTC0) for which to find transitions
*
* @return array Map of prev|curr|next transitions
*/
public function getDetailedTransitions( $timestamp ) {
$transition_list = $this->getTransitions();
$output = array(
'prev' => NULL,
'curr' => NULL,
'next' => NULL,
);
$previous = $current = NULL;
foreach ( $transition_list as $transition ) {
if (
NULL !== $previous &&
$timestamp >= $current['ts'] &&
$timestamp < $transition['ts']
) {
$output['prev'] = $previous;
$output['curr'] = $current;
$output['next'] = $transition;
break;
}
$previous = $current;
$current = $transition;
}
unset( $previous, $current, $transition_list, $transition );
return $output;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Base exception for all date/time operation failures.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date.exception
*/
class Ai1ec_Date_Exception extends Ai1ec_Exception {
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Exception to be thrown when timezone operation fails.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Timezone_Exception extends Ai1ec_Date_Exception {
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Legacy Time utility.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Time_Utility {
/**
* @var Ai1ec_Registry_Object
*/
static protected $_registry;
/**
* @param Ai1ec_Registry_Object $registry
*/
static public function set_registry( Ai1ec_Registry_Object $registry ) {
self::$_registry = $registry;
}
/**
* Legacy function needed for theme compatibility
*
* @param string $format
* @param int $timestamp
* @param bool $is_gmt
*/
static public function date_i18n(
$format,
$timestamp = false,
$is_gmt = true
) {
$timezone = ( $is_gmt ) ? 'UTC' : 'sys.default';
return self::$_registry->get( 'date.time', $timestamp, $timezone )
->format_i18n( $format );
}
}

View File

@@ -0,0 +1,279 @@
<?php
/**
* Wrap library calls to date subsystem.
*
* Meant to increase performance and work around known bugs in environment.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_System extends Ai1ec_Base {
/**
* @var array List of local time (key '0') and GMT time (key '1').
*/
protected $_current_time = array();
/**
* @var Ai1ec_Cache_Memory
*/
protected $_gmtdates;
/**
* Initiate current time list.
*
* @param Ai1ec_Registry_Object $registry
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$gmt_time = ( version_compare( PHP_VERSION, '5.1.0' ) >= 0 )
? time()
: gmmktime();
$requestTime = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
$this->_current_time = array(
$requestTime,
$gmt_time,
);
$this->_gmtdates = $registry->get( 'cache.memory' );
}
/**
* Get current time UNIX timestamp.
*
* Uses in-memory value, instead of re-calling `time()` / `gmmktime()`.
*
* @param bool $is_gmt Set to true to get GMT timestamp.
*
* @return int Current time UNIX timestamp
*/
public function current_time( $is_gmt = false ) {
return $this->_current_time[(int)( (bool)$is_gmt )];
}
/**
* Returns the associative array of date patterns supported by the plugin.
*
* Currently the formats are:
* array(
* 'def' => 'd/m/yyyy',
* 'us' => 'm/d/yyyy',
* 'iso' => 'yyyy-m-d',
* 'dot' => 'm.d.yyyy',
* );
*
* 'd' or 'dd' represent the day, 'm' or 'mm' represent the month, and 'yy'
* or 'yyyy' represent the year.
*
* @return array List of supported date patterns.
*/
public function get_date_patterns() {
return array(
'def' => 'd/m/yyyy',
'us' => 'm/d/yyyy',
'iso' => 'yyyy-m-d',
'dot' => 'm.d.yyyy',
);
}
/**
* Get acceptable date format.
*
* Returns the date pattern (in the form 'd-m-yyyy', for example) associated
* with the provided key, used by plugin settings. Simply a static map as
* follows:
*
* @param string $key Key for the date format.
*
* @return string Associated date format pattern.
*/
public function get_date_pattern_by_key( $key = 'def' ) {
$patterns = $this->get_date_patterns();
if ( ! isset( $patterns[$key] ) ) {
return (string)current( $patterns );
}
return $patterns[$key];
}
/**
* Format timestamp into URL safe, user selected representation.
*
* Returns a formatted date given a timestamp, based on the given date
* format, with any '/' characters replaced with URL-friendly '-'
* characters.
*
* @see Ai1ec_Date_System::get_date_patterns() for supported date formats.
*
* @param int $timestamp UNIX timestamp representing a date.
* @param string $pattern Key of date pattern (@see
* self::get_date_format_patter()) to
* format date with
*
* @return string Formatted date string.
*/
public function format_date_for_url( $timestamp, $pattern = 'def' ) {
$date = $this->format_date( $timestamp, $pattern );
$date = str_replace( '/', '-', $date );
return $date;
}
/**
* Similar to {@see format_date_for_url} just using new DateTime interface.
*
* @param Ai1ec_Date_Time $datetime Instance of datetime to format.
* @param string $pattern Target format to use.
*
* @return string Formatted datetime string.
*/
public function format_datetime_for_url(
Ai1ec_Date_Time $datetime,
$pattern = 'def'
) {
$date = $datetime->format( $this->get_date_format_patter( $pattern ) );
return str_replace( '/', '-', $date );
}
/**
* Returns the date formatted with new pattern from a given date and old pattern.
*
* @see self::get_date_patterns() for supported date formats.
*
* @param string $date Formatted date string
* @param string $old_pattern Key of old date pattern (@see
* self::get_date_format_patter())
* @param string $new_pattern Key of new date pattern (@see
* self::get_date_format_patter())
* @return string Formatted date string with new pattern
*/
public function convert_date_format( $date, $old_pattern, $new_pattern ) {
// Convert old date to timestamp
$timeArray = date_parse_from_format( $this->get_date_format_patter( $old_pattern ), $date );
$timestamp = mktime(
$timeArray['hour'], $timeArray['minute'], $timeArray['second'],
$timeArray['month'], $timeArray['day'], $timeArray['year']
);
// Convert to new date pattern
return $this->format_date( $timestamp, $new_pattern );
}
/**
* Returns a formatted date given a timestamp, based on the given date format.
*
* @see self::get_date_patterns() for supported date formats.
*
* @param int $timestamp UNIX timestamp representing a date (in GMT)
* @param string $pattern Key of date pattern (@see
* self::get_date_format_patter()) to
* format date with
* @return string Formatted date string
*/
public function format_date( $timestamp, $pattern = 'def' ) {
return gmdate( $this->get_date_format_patter( $pattern ), $timestamp );
}
public function get_date_format_patter( $requested ) {
$pattern = $this->get_date_pattern_by_key( $requested );
$pattern = str_replace(
array( 'dd', 'd', 'mm', 'm', 'yyyy', 'yy' ),
array( 'd', 'j', 'm', 'n', 'Y', 'y' ),
$pattern
);
return $pattern;
}
/**
* Returns human-readable version of the GMT offset.
*
* @param string $timezone_name Olsen Timezone name [optional=null]
*
* @return string GMT offset expression
*/
public function get_gmt_offset_expr( $timezone_name = null ) {
$timezone = $this->get_gmt_offset( $timezone_name );
$offset_h = (int)( $timezone / 60 );
$offset_m = absint( $timezone - $offset_h * 60 );
$timezone = sprintf(
Ai1ec_I18n::__( 'GMT%+d:%02d' ),
$offset_h,
$offset_m
);
return $timezone;
}
/**
* Get current GMT offset in seconds.
*
* @param string $timezone_name Olsen Timezone name [optional=null]
*
* @return int Offset from GMT in seconds.
*/
public function get_gmt_offset( $timezone_name = null ) {
if ( null === $timezone_name ) {
$timezone_name = 'sys.default';
}
$current = $this->_registry->get(
'date.time',
'now',
$timezone_name
);
return $current->get_gmt_offset();
}
/**
* gmgetdate method
*
* Get date/time information in GMT
*
* @param int $timestamp Timestamp at which information shall be evaluated
*
* @return array Associative array of information related to the timestamp
*/
public function gmgetdate( $timestamp = NULL ) {
if ( NULL === $timestamp ) {
$timestamp = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
}
if ( NULL === ( $date = $this->_gmtdates->get( $timestamp ) ) ) {
$particles = explode(
',',
gmdate( 's,i,G,j,w,n,Y,z,l,F,U', $timestamp )
);
$date = array_combine(
array(
'seconds',
'minutes',
'hours',
'mday',
'wday',
'mon',
'year',
'yday',
'weekday',
'month',
0
),
$particles
);
$this->_gmtdates->set( $timestamp, $date );
}
return $date;
}
/**
* Returns current rounded time as unix integer.
*
* @param int $shift Shift value.
*
* @return int Unix timestamp.
*/
public function get_current_rounded_time( $shift = 11 ) {
return $this->current_time() >> $shift << $shift;
}
}

View File

@@ -0,0 +1,216 @@
<?php
/**
* Time and date internationalization management library
*
* @author Timely Network Inc
* @since 2012.10.09
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Lib.Utility
*/
class Ai1ec_Time_I18n_Utility extends Ai1ec_Base {
/**
* @var char Separator to wrap unique keys and avoid collisions
* EOT is used instead of NUL, as NUL is used by `date()`
* functions family as guard and causes memory leaks.
*/
protected $_separator = "\004";
/**
* @var array Map of keys, used by date methods
*/
protected $_keys = array();
/**
* @var array Map of keys for substition
*/
protected $_skeys = array();
/**
* @var string Format to use when calling `date_i18n()`
*/
protected $_format = NULL;
/**
* @var Ai1ec_Memory_Utility Parsed time entries
*/
protected $_memory = NULL;
/**
* @var Ai1ec_Memory_Utility Parsed format entries
*/
protected $_transf = NULL;
/**
* Constructor
*
* Initialize internal memory objects and date keys.
*
* @param Ai1ec_Memory_Utility $memory Optionally inject memory to use
*
* @return void Constructor does not return
*/
public function __construct(
Ai1ec_Registry_Object $registry,
Ai1ec_Cache_Memory $memory = null
) {
parent::__construct( $registry );
if ( NULL === $memory ) {
$memory = $this->_registry->get( 'cache.memory', 120 ); // 30 * 4
}
$this->_memory = $memory;
$this->_transf = $this->_registry->get( 'cache.memory' );
$this->_keys = $this->_initialize_keys();
$this->_skeys = $this->_initialize_keys(
$this->_separator,
$this->_separator
);
$this->_format = implode( $this->_separator, $this->_keys );
}
/**
* format method
*
* Convenient wrapper for `date_i18n()`, which caches both faster format
* version and response for {$timestamp} and {$is_gmt} combination.
*
* @param string $format Format string to output timestamp in
* @param int $timestamp UNIX timestamp to output in given format
* @param bool $is_gmt Set to true, to treat {$timestamp} as GMT
*
* @return string Formatted date-time entry
*/
public function format( $format, $timestamp = false, $is_gmt = false ) {
$time_elements = $this->parse( $timestamp, $is_gmt );
$local_format = $this->_safe_format( $format );
return str_replace( $this->_skeys, $time_elements, $local_format );
}
/**
* parse method
*
* Parse given timestamp into I18n date/time values map.
*
* @param int $timestamp Timestamp to parse
* @param bool $is_gmt Set to true, to treat value as present in GMT
*
* @return array Map of date format keys and corresponding time values
*/
public function parse( $timestamp = false, $is_gmt = false ) {
$timestamp = (int)$timestamp;
if ( $timestamp <= 0 ) {
$timestamp = $this->_registry->get( 'date.system' )->current_time();
}
$cache_key = $timestamp . "\0" . $is_gmt;
if ( NULL === ( $record = $this->_memory->get( $cache_key ) ) ) {
$record = array_combine(
$this->_keys,
explode(
$this->_separator,
date_i18n( $this->_format, $timestamp, $is_gmt )
)
);
$this->_memory->set( $cache_key, $record );
}
return $record;
}
/**
* _safe_format method
*
* Prepare safe format value, to use in substitutions.
* In prepared string special values are wrapped by {$_separator} to allow
* fast replacement methods, using binary search.
*
* @param string $format Given format to polish
*
* @return string Modified format, with special keys wrapped in bin fields
*/
protected function _safe_format( $format ) {
if ( NULL === ( $safe = $this->_transf->get( $format ) ) ) {
$safe = '';
$state = 0;
$separator = $this->_separator;
$length = strlen( $format );
for ( $index = 0; $index < $length; $index++ ) {
if ( $state > 0 ) {
--$state;
}
$current = $format{$index};
if ( 0 === $state ) {
if ( '\\' === $current ) {
$state = 2;
} elseif ( isset( $this->_keys[$current] ) ) {
$current = $separator . $current . $separator;
}
}
if ( 2 !== $state ) {
$safe .= $current;
}
}
$this->_transf->set( $format, $safe );
}
return $safe;
}
/**
* _initialize_keys method
*
* Prepare list of keys, used by date functions.
* Optionally wrap values (keys are the same, always).
*
* @param string $prepend Prefix to date key
* @param string $append Suffix to date key
*
* @return array Map of date keys
*/
protected function _initialize_keys( $prepend = '', $append = '' ) {
$keys = array(
'd',
'D',
'j',
'l',
'N',
'S',
'w',
'z',
'W',
'F',
'm',
'M',
'n',
't',
'L',
'o',
'Y',
'y',
'a',
'A',
'B',
'g',
'G',
'h',
'H',
'i',
's',
'u',
'e',
'I',
'O',
'P',
'T',
'Z',
'c',
'r',
'U',
);
$map = array();
foreach ( $keys as $key ) {
$map[$key] = $prepend . $key . $append;
}
return $map;
}
}

View File

@@ -0,0 +1,423 @@
<?php
/**
* Time entity.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Time {
/**
* @var Ai1ec_Registry_Object Instance of objects registry.
*/
protected $_registry = null;
/**
* @var DateTime Instance of date time object used to perform manipulations.
*/
protected $_date_time = null;
/**
* @var string Olsen name of preferred timezone to use if none is requested.
*/
protected $_preferred_timezone = null;
/**
* @var bool Set to true when `no value` is set.
*/
protected $_is_empty = false;
/**
* Initialize local date entity.
*
* @param Ai1ec_Registry_Object $registry Objects registry instance.
* @param string $time For details {@see self::format}.
* @param string $timezone For details {@see self::format}.
*
* @return void
*/
public function __construct(
Ai1ec_Registry_Object $registry,
$time = 'now',
$timezone = 'UTC'
) {
$this->_registry = $registry;
$this->set_date_time( $time, $timezone );
}
/**
* Since clone is shallow, we need to clone the DateTime object
*/
public function __clone() {
$this->_date_time = clone $this->_date_time;
}
/**
* Return formatted date in desired timezone.
*
* NOTICE: consider optimizing by storing multiple copies of `DateTime` for
* each requested timezone, or some of them, as of now timezone is changed
* back and forth every time when formatting is called for.
*
* @param string $format Desired format as accepted by {@see date}.
* @param string $timezone Valid timezone identifier. Defaults to current.
*
* @return string Formatted date time.
*
* @throws Ai1ec_Date_Timezone_Exception If timezone is not recognized.
*/
public function format( $format = 'U', $timezone = null ) {
if ( $this->_is_empty ) {
return null;
}
if ( 'U' === $format ) { // performance cut
return $this->_date_time->format( 'U' );
}
$timezone = $this->get_default_format_timezone( $timezone );
$last_tz = $this->get_timezone();
$this->set_timezone( $timezone );
$formatted = $this->_date_time->format( $format );
$this->set_timezone( $last_tz );
return $formatted;
}
/**
* Format date time to i18n representation.
*
* @param string $format Target I18n format.
* @param string $timezone Valid timezone identifier. Defaults to current.
*
* @return string Formatted time.
*/
public function format_i18n( $format, $timezone = null ) {
$parser = $this->_registry->get( 'parser.date' );
$parsed = $parser->get_format( $format );
$inflected = $this->format( $parsed, $timezone );
$formatted = $parser->squeeze( $inflected );
return $formatted;
}
/**
* Commodity method to format to UTC.
*
* @param string $format Target format, defaults to UNIX timestamp.
*
* @return string Formatted datetime string.
*/
public function format_to_gmt( $format = 'U' ) {
return $this->format( $format, 'UTC' );
}
/**
* Create JavaScript ready date/time information string.
*
* @param bool $event_timezone Set to true to format in event timezone.
*
* @return string JavaScript date/time string.
*/
public function format_to_javascript( $event_timezone = false ) {
$event_timezone = ( $event_timezone )
? $this->get_timezone()
: null;
return $this->format( 'Y-m-d\TH:i:s', $event_timezone );
}
/**
* Get timezone to use when format doesn't have one.
*
* Precedence:
* 1. Timezone supplied for formatting;
* 2. Objects preferred timezone;
* 3. Default systems timezone.
*
* @var string $timezone Requested formatting timezone.
*
* @return string Olsen timezone name to use.
*/
public function get_default_format_timezone( $timezone = null ) {
if ( null !== $timezone ) {
return $timezone;
}
if ( null !== $this->_preferred_timezone ) {
return $this->_preferred_timezone;
}
return $this->_registry->get( 'date.timezone' )
->get_default_timezone();
}
/**
* Offset from GMT in minutes.
*
* @return int Signed integer - offset.
*/
public function get_gmt_offset() {
return $this->_date_time->getOffset() / 60;
}
/**
* Returns timezone offset as human readable GMT string.
*
* @return string
*/
public function get_gmt_offset_as_text() {
$offset = $this->_date_time->getOffset();
$offsetHours = $offset / 3600;
$offset = $offset % 3600;
$offsetMinutes = abs( $offset ) / 60;
return sprintf( '(GMT%+03d:%02d)', $offsetHours, $offsetMinutes );
}
/**
* Set preferred timezone to use when format is called without any.
*
* @param DateTimeZone $timezone Preferred timezone instance.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_preferred_timezone( $timezone ) {
if ( $timezone instanceof DateTimeZone ) {
$timezone = $timezone->getName();
}
$this->_preferred_timezone = (string)$timezone;
return $this;
}
/**
* Change timezone of stored entity.
*
* @param string $timezone Valid timezone identifier.
*
* @return Ai1ec_Date Instance of self for chaining.
*
* @throws Ai1ec_Date_Timezone_Exception If timezone is not recognized.
*/
public function set_timezone( $timezone = 'UTC' ) {
$date_time_tz = ( $timezone instanceof DateTimeZone )
? $timezone
: $this->_registry->get( 'date.timezone' )->get( $timezone );
$this->_date_time->setTimezone( $date_time_tz );
return $this;
}
/**
* Get timezone associated with current object.
*
* @return string|null Valid PHP timezone string or null on error.
*/
public function get_timezone() {
$timezone = $this->_date_time->getTimezone();
if ( false === $timezone ) {
return null;
}
return $timezone->getName();
}
/**
* Get difference in seconds between to dates.
*
* In PHP versions post 5.3.0 the {@see DateTimeImmutable::diff()} is
* used. In earlier versions the difference between two timestamps is
* being checked.
*
* @param Ai1ec_Date_Time $comparable Other date time entity.
*
* @return int Number of seconds between two dates.
*/
public function diff_sec( Ai1ec_Date_Time $comparable, $timezone = null ) {
// NOTICE: `$this->_is_empty` is not touched here intentionally
// because there is no meaningful difference to `empty` value.
// It is left to be handled at upper level - you are not likely to
// reach situation where you compare something against empty value.
if ( version_compare( PHP_VERSION, '5.3.0' ) < 0 ) {
$difference = $this->_date_time->format( 'U' ) -
$comparable->_date_time->format( 'U' );
if ( $difference < 0 ) {
$difference *= -1;
}
return $difference;
}
$difference = $this->_date_time->diff( $comparable->_date_time, true );
return (
$difference->days * 86400 +
$difference->h * 3600 +
$difference->i * 60 +
$difference->s
);
}
/**
* Adjust only date fragment of entity.
*
* @param int $year Year of the date.
* @param int $month Month of the date.
* @param int $day Day of the date.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_date( $year, $month, $day ) {
$this->_date_time->setDate( $year, $month, $day );
$this->_is_empty = false;
return $this;
}
/**
* Adjust only time fragment of entity.
*
* @param int $hour Hour of the time.
* @param int $minute Minute of the time.
* @param int $second Second of the time.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_time( $hour, $minute = 0, $second = 0 ) {
$this->_date_time->setTime( $hour, $minute, $second );
$this->_is_empty = false;
return $this;
}
/**
* Adjust day part of date time entity.
*
* @param int $quantifier Day adjustment quantifier.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function adjust_day( $quantifier ) {
// NOTICE: `$this->_is_empty` is not touched here, because if you
// start adjusting value it's likely not empty by then.
$this->adjust( $quantifier, 'day' );
return $this;
}
/**
* Adjust day part of date time entity.
*
* @param int $quantifier Day adjustment quantifier.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function adjust_month( $quantifier ) {
$this->adjust( $quantifier, 'month' );
return $this;
}
/**
* Change/initiate stored date time entity.
*
* NOTICE: time specifiers falling in range 0..2048 will be treated
* as a UNIX timestamp, to full format specification, thus ignoring
* any value passed for timezone.
*
* @param string $time Valid (PHP-parseable) date/time identifier.
* @param string $timezone Valid timezone identifier.
*
* @return Ai1ec_Date Instance of self for chaining.
*/
public function set_date_time( $time = 'now', $timezone = 'UTC' ) {
if ( $time instanceof self ) {
$this->_is_empty = $time->_is_empty;
$this->_date_time = clone $time->_date_time;
$this->_preferred_timezone = $time->_preferred_timezone;
if ( 'UTC' !== $timezone && $timezone ) {
$this->set_timezone( $timezone );
}
return $this;
}
$this->assert_utc_timezone();
$date_time_tz = $this->_registry->get( 'date.timezone' )
->get( $timezone );
$reset_tz = false;
$this->_is_empty = false;
if ( null === $time ) {
$this->_is_empty = true;
$time = '@' . ~PHP_INT_MAX;
$reset_tz = true;
} else if ( $this->is_timestamp( $time ) ) {
$time = '@' . $time; // treat as UNIX timestamp
$reset_tz = true; // store intended TZ
}
// PHP <= 5.3.5 compatible
$this->_date_time = new DateTime( $time, $date_time_tz );
if ( $reset_tz ) {
$this->set_timezone( $date_time_tz );
}
return $this;
}
/**
* Check if value should be treated as a UNIX timestamp.
*
* @param string $time Provided time value.
*
* @return bool True if seems like UNIX timestamp.
*/
public function is_timestamp( $time ) {
// '20001231T001559Z'
if ( isset( $time{8} ) && 'T' === $time{8} ) {
return false;
}
if ( (string)(int)$time !== (string)$time ) {
return false;
}
// 1000..2459 are treated as hours, 2460..9999 - as years
if ( $time > 999 && $time < 2460 ) {
return false;
}
return true;
}
/**
* Assert that current timezone is UTC.
*
* @return bool Success.
*/
public function assert_utc_timezone() {
$default = (string)date_default_timezone_get();
$success = true;
if ( 'UTC' !== $default ) {
// issue admin notice
$success = date_default_timezone_set( 'UTC' );
}
return $success;
}
/**
* Magic method for compatibility.
*
* @return string ISO-8601 formatted date-time.
*/
public function __toString() {
return $this->format( 'c' );
}
/**
* Modifies the DateTime object
*
* @param int $quantifieruantifier
* @param string $longname
*/
public function adjust( $quantifier, $longname ) {
$quantifier = (int)$quantifier;
if ( $quantifier > 0 && '+' !== $quantifier{0} ) {
$quantifier = '+' . $quantifier;
}
$modifier = $quantifier . ' ' . $longname;
$this->_date_time->modify( $modifier );
return $this;
}
/**
* Explicitly check if value (date) is empty.
*
* @return bool Emptiness
*/
public function is_empty() {
return $this->_is_empty;
}
}

View File

@@ -0,0 +1,617 @@
<?php
/**
* Timezones manipulation object.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Timezone extends Ai1ec_Base {
/**
* @var Ai1ec_Cache_Interface In-memory storage for timezone objects.
*/
protected $_cache = null;
/**
* @var array Map of timezone names and their Olson TZ counterparts.
*/
protected $_zones = array(
'+00:00' => 'UTC',
'Z' => 'UTC',
'AUS Central Standard Time' => 'Australia/Darwin',
'AUS Eastern Standard Time' => 'Australia/Sydney',
'Acre' => 'America/Rio_Branco',
'Afghanistan' => 'Asia/Kabul',
'Afghanistan Standard Time' => 'Asia/Kabul',
'Africa_Central' => 'Africa/Maputo',
'Africa_Eastern' => 'Africa/Nairobi',
'Africa_FarWestern' => 'Africa/El_Aaiun',
'Africa_Southern' => 'Africa/Johannesburg',
'Africa_Western' => 'Africa/Lagos',
'Aktyubinsk' => 'Asia/Aqtobe',
'Alaska' => 'America/Juneau',
'Alaska_Hawaii' => 'America/Anchorage',
'Alaskan Standard Time' => 'America/Anchorage',
'Almaty' => 'Asia/Almaty',
'Amazon' => 'America/Manaus',
'America_Central' => 'America/Chicago',
'America_Eastern' => 'America/New_York',
'America_Mountain' => 'America/Denver',
'America_Pacific' => 'America/Los_Angeles',
'Anadyr' => 'Asia/Anadyr',
'Aqtau' => 'Asia/Aqtau',
'Aqtobe' => 'Asia/Aqtobe',
'Arab Standard Time' => 'Asia/Riyadh',
'Arabian' => 'Asia/Riyadh',
'Arabian Standard Time' => 'Asia/Dubai',
'Arabic Standard Time' => 'Asia/Baghdad',
'Argentina' => 'America/Buenos_Aires',
'Argentina Standard Time' => 'America/Buenos_Aires',
'Argentina_Western' => 'America/Mendoza',
'Armenia' => 'Asia/Yerevan',
'Armenian Standard Time' => 'Asia/Yerevan',
'Ashkhabad' => 'Asia/Ashgabat',
'Atlantic' => 'America/Halifax',
'Atlantic Standard Time' => 'America/Halifax',
'Australia_Central' => 'Australia/Adelaide',
'Australia_CentralWestern' => 'Australia/Eucla',
'Australia_Eastern' => 'Australia/Sydney',
'Australia_Western' => 'Australia/Perth',
'Azerbaijan' => 'Asia/Baku',
'Azerbaijan Standard Time' => 'Asia/Baku',
'Azores' => 'Atlantic/Azores',
'Azores Standard Time' => 'Atlantic/Azores',
'Baku' => 'Asia/Baku',
'Bangladesh' => 'Asia/Dhaka',
'Bering' => 'America/Adak',
'Bhutan' => 'Asia/Thimphu',
'Bolivia' => 'America/La_Paz',
'Borneo' => 'Asia/Kuching',
'Brasilia' => 'America/Sao_Paulo',
'British' => 'Europe/London',
'Brunei' => 'Asia/Brunei',
'Canada Central Standard Time' => 'America/Regina',
'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
'Cape_Verde' => 'Atlantic/Cape_Verde',
'Caucasus Standard Time' => 'Asia/Yerevan',
'Cen. Australia Standard Time' => 'Australia/Adelaide',
'Central America Standard Time' => 'America/Guatemala',
'Central Asia Standard Time' => 'Asia/Dhaka',
'Central Brazilian Standard Time' => 'America/Manaus',
'Central Europe Standard Time' => 'Europe/Budapest',
'Central European Standard Time' => 'Europe/Warsaw',
'Central Pacific Standard Time' => 'Pacific/Guadalcanal',
'Central Standard Time' => 'America/Chicago',
'Central Standard Time (Mexico)' => 'America/Mexico_City',
'Chamorro' => 'Pacific/Saipan',
'Changbai' => 'Asia/Harbin',
'Chatham' => 'Pacific/Chatham',
'Chile' => 'America/Santiago',
'China' => 'Asia/Shanghai',
'China Standard Time' => 'Asia/Shanghai',
'Choibalsan' => 'Asia/Choibalsan',
'Christmas' => 'Indian/Christmas',
'Cocos' => 'Indian/Cocos',
'Colombia' => 'America/Bogota',
'Cook' => 'Pacific/Rarotonga',
'Cuba' => 'America/Havana',
'Dacca' => 'Asia/Dhaka',
'Dateline Standard Time' => 'Etc/GMT+12',
'Davis' => 'Antarctica/Davis',
'Dominican' => 'America/Santo_Domingo',
'DumontDUrville' => 'Antarctica/DumontDUrville',
'Dushanbe' => 'Asia/Dushanbe',
'Dutch_Guiana' => 'America/Paramaribo',
'E. Africa Standard Time' => 'Africa/Nairobi',
'E. Australia Standard Time' => 'Australia/Brisbane',
'E. Europe Standard Time' => 'Europe/Minsk',
'E. South America Standard Time' => 'America/Sao_Paulo',
'East_Timor' => 'Asia/Dili',
'Easter' => 'Pacific/Easter',
'Eastern Standard Time' => 'America/New_York',
'Ecuador' => 'America/Guayaquil',
'Egypt Standard Time' => 'Africa/Cairo',
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
'Europe_Central' => 'Europe/Paris',
'Europe_Eastern' => 'Europe/Bucharest',
'Europe_Western' => 'Atlantic/Canary',
'FLE Standard Time' => 'Europe/Kiev',
'Falkland' => 'Atlantic/Stanley',
'Fiji' => 'Pacific/Fiji',
'Fiji Standard Time' => 'Pacific/Fiji',
'French_Guiana' => 'America/Cayenne',
'French_Southern' => 'Indian/Kerguelen',
'Frunze' => 'Asia/Bishkek',
'GMT' => 'UTC', // seems better than 'Atlantic/Reykjavik'
'GMT Standard Time' => 'Europe/London',
'GTB Standard Time' => 'Europe/Istanbul',
'Galapagos' => 'Pacific/Galapagos',
'Gambier' => 'Pacific/Gambier',
'Georgia' => 'Asia/Tbilisi',
'Georgian Standard Time' => 'Etc/GMT-3',
'Gilbert_Islands' => 'Pacific/Tarawa',
'Goose_Bay' => 'America/Goose_Bay',
'Greenland Standard Time' => 'America/Godthab',
'Greenland_Central' => 'America/Scoresbysund',
'Greenland_Eastern' => 'America/Scoresbysund',
'Greenland_Western' => 'America/Godthab',
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
'Guam' => 'Pacific/Guam',
'Gulf' => 'Asia/Dubai',
'Guyana' => 'America/Guyana',
'Hawaii_Aleutian' => 'Pacific/Honolulu',
'Hawaiian Standard Time' => 'Pacific/Honolulu',
'Hong_Kong' => 'Asia/Hong_Kong',
'Hovd' => 'Asia/Hovd',
'India' => 'Asia/Calcutta',
'India Standard Time' => 'Asia/Calcutta',
'Indian_Ocean' => 'Indian/Chagos',
'Indochina' => 'Asia/Saigon',
'Indonesia_Central' => 'Asia/Makassar',
'Indonesia_Eastern' => 'Asia/Jayapura',
'Indonesia_Western' => 'Asia/Jakarta',
'Iran' => 'Asia/Tehran',
'Iran Standard Time' => 'Asia/Tehran',
'Irish' => 'Europe/Dublin',
'Irkutsk' => 'Asia/Irkutsk',
'Israel' => 'Asia/Jerusalem',
'Israel Standard Time' => 'Asia/Jerusalem',
'Japan' => 'Asia/Tokyo',
'Jordan Standard Time' => 'Asia/Amman',
'Kamchatka' => 'Asia/Kamchatka',
'Karachi' => 'Asia/Karachi',
'Kashgar' => 'Asia/Kashgar',
'Kazakhstan_Eastern' => 'Asia/Almaty',
'Kazakhstan_Western' => 'Asia/Aqtobe',
'Kizilorda' => 'Asia/Qyzylorda',
'Korea' => 'Asia/Seoul',
'Korea Standard Time' => 'Asia/Seoul',
'Kosrae' => 'Pacific/Kosrae',
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
'Kuybyshev' => 'Europe/Samara',
'Kwajalein' => 'Pacific/Kwajalein',
'Kyrgystan' => 'Asia/Bishkek',
'Lanka' => 'Asia/Colombo',
'Liberia' => 'Africa/Monrovia',
'Line_Islands' => 'Pacific/Kiritimati',
'Long_Shu' => 'Asia/Chongqing',
'Lord_Howe' => 'Australia/Lord_Howe',
'Macau' => 'Asia/Macau',
'Magadan' => 'Asia/Magadan',
'Malaya' => 'Asia/Kuala_Lumpur',
'Malaysia' => 'Asia/Kuching',
'Maldives' => 'Indian/Maldives',
'Marquesas' => 'Pacific/Marquesas',
'Marshall_Islands' => 'Pacific/Majuro',
'Mauritius' => 'Indian/Mauritius',
'Mauritius Standard Time' => 'Indian/Mauritius',
'Mawson' => 'Antarctica/Mawson',
'Mexico Standard Time' => 'America/Mexico_City',
'Mexico Standard Time 2' => 'America/Chihuahua',
'Mid-Atlantic Standard Time' => 'Atlantic/South_Georgia',
'Middle East Standard Time' => 'Asia/Beirut',
'Mongolia' => 'Asia/Ulaanbaatar',
'Montevideo Standard Time' => 'America/Montevideo',
'Morocco Standard Time' => 'Africa/Casablanca',
'Moscow' => 'Europe/Moscow',
'Mountain Standard Time' => 'America/Denver',
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
'Myanmar' => 'Asia/Rangoon',
'Myanmar Standard Time' => 'Asia/Rangoon',
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
'Namibia Standard Time' => 'Africa/Windhoek',
'Nauru' => 'Pacific/Nauru',
'Nepal' => 'Asia/Katmandu',
'Nepal Standard Time' => 'Asia/Katmandu',
'New Zealand Standard Time' => 'Pacific/Auckland',
'New_Caledonia' => 'Pacific/Noumea',
'New_Zealand' => 'Pacific/Auckland',
'Newfoundland' => 'America/St_Johns',
'Newfoundland Standard Time' => 'America/St_Johns',
'Niue' => 'Pacific/Niue',
'Norfolk' => 'Pacific/Norfolk',
'Noronha' => 'America/Noronha',
'North Asia East Standard Time' => 'Asia/Irkutsk',
'North Asia Standard Time' => 'Asia/Krasnoyarsk',
'North_Mariana' => 'Pacific/Saipan',
'Novosibirsk' => 'Asia/Novosibirsk',
'Omsk' => 'Asia/Omsk',
'Oral' => 'Asia/Oral',
'Pacific SA Standard Time' => 'America/Santiago',
'Pacific Standard Time' => 'America/Los_Angeles',
'Pacific Standard Time (Mexico)' => 'America/Tijuana',
'Pakistan' => 'Asia/Karachi',
'Pakistan Standard Time' => 'Asia/Karachi',
'Palau' => 'Pacific/Palau',
'Papua_New_Guinea' => 'Pacific/Port_Moresby',
'Paraguay' => 'America/Asuncion',
'Peru' => 'America/Lima',
'Philippines' => 'Asia/Manila',
'Phoenix_Islands' => 'Pacific/Enderbury',
'Pierre_Miquelon' => 'America/Miquelon',
'Pitcairn' => 'Pacific/Pitcairn',
'Ponape' => 'Pacific/Ponape',
'Qyzylorda' => 'Asia/Qyzylorda',
'Reunion' => 'Indian/Reunion',
'Romance Standard Time' => 'Europe/Paris',
'Rothera' => 'Antarctica/Rothera',
'Russian Standard Time' => 'Europe/Moscow',
'SA Eastern Standard Time' => 'Etc/GMT+3',
'SA Pacific Standard Time' => 'America/Bogota',
'SA Western Standard Time' => 'America/La_Paz',
'SE Asia Standard Time' => 'Asia/Bangkok',
'Sakhalin' => 'Asia/Sakhalin',
'Samara' => 'Europe/Samara',
'Samarkand' => 'Asia/Samarkand',
'Samoa' => 'Pacific/Apia',
'Samoa Standard Time' => 'Pacific/Apia',
'Seychelles' => 'Indian/Mahe',
'Shevchenko' => 'Asia/Aqtau',
'Singapore' => 'Asia/Singapore',
'Singapore Standard Time' => 'Asia/Singapore',
'Solomon' => 'Pacific/Guadalcanal',
'South Africa Standard Time' => 'Africa/Johannesburg',
'South_Georgia' => 'Atlantic/South_Georgia',
'Sri Lanka Standard Time' => 'Asia/Colombo',
'Suriname' => 'America/Paramaribo',
'Sverdlovsk' => 'Asia/Yekaterinburg',
'Syowa' => 'Antarctica/Syowa',
'Tahiti' => 'Pacific/Tahiti',
'Taipei' => 'Asia/Taipei',
'Taipei Standard Time' => 'Asia/Taipei',
'Tajikistan' => 'Asia/Dushanbe',
'Tashkent' => 'Asia/Tashkent',
'Tasmania Standard Time' => 'Australia/Hobart',
'Tbilisi' => 'Asia/Tbilisi',
'Tokelau' => 'Pacific/Fakaofo',
'Tokyo Standard Time' => 'Asia/Tokyo',
'Tonga' => 'Pacific/Tongatapu',
'Tonga Standard Time' => 'Pacific/Tongatapu',
'Truk' => 'Pacific/Truk',
'Turkey' => 'Europe/Istanbul',
'Turkmenistan' => 'Asia/Ashgabat',
'Tuvalu' => 'Pacific/Funafuti',
'US/Eastern' => 'America/New_York',
'US Eastern Standard Time' => 'Etc/GMT+5',
'US Mountain Standard Time' => 'America/Phoenix',
'Uralsk' => 'Asia/Oral',
'Uruguay' => 'America/Montevideo',
'Urumqi' => 'Asia/Urumqi',
'Uzbekistan' => 'Asia/Tashkent',
'Vanuatu' => 'Pacific/Efate',
'Venezuela' => 'America/Caracas',
'Venezuela Standard Time' => 'America/Caracas',
'Vladivostok' => 'Asia/Vladivostok',
'Vladivostok Standard Time' => 'Asia/Vladivostok',
'Volgograd' => 'Europe/Volgograd',
'Vostok' => 'Antarctica/Vostok',
'W. Australia Standard Time' => 'Australia/Perth',
'W. Central Africa Standard Time' => 'Africa/Lagos',
'W. Europe Standard Time' => 'Europe/Berlin',
'Wake' => 'Pacific/Wake',
'Wallis' => 'Pacific/Wallis',
'West Asia Standard Time' => 'Asia/Tashkent',
'West Pacific Standard Time' => 'Pacific/Port_Moresby',
'Yakutsk' => 'Asia/Yakutsk',
'Yakutsk Standard Time' => 'Asia/Yakutsk',
'Yekaterinburg' => 'Asia/Yekaterinburg',
'Yerevan' => 'Asia/Yerevan',
'Yukon' => 'America/Yakutat',
);
/**
* @var array Map of timezones acceptable by DateTimeZone but not strtotime.
*/
protected $_invalid_legacy = array(
'US/Eastern' => true,
);
/**
* @var array|bool List of system identifiers or false if none available.
*/
protected $_identifiers = false;
/**
* Initialize local cache and identifiers.
*
* @param Ai1ec_Registry_Object $registry Registry to use.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$this->_cache = $this->_registry->get( 'cache.memory' );
$this->_init_identifiers();
}
/**
* Get default timezone to use in input/output.
*
* Approach is as follows:
* - check user profile for timezone preference;
* - if user has no preference - check site for timezone selection;
* - if site has no selection - raise notice and use 'UTC'.
*
* @return string Olson timezone string identifier.
*/
public function get_default_timezone() {
static $default_timezone = null;
if ( null === $default_timezone ) {
$candidates = array();
$candidates[] = (string)$this->_registry->get( 'model.meta-user' )
->get_current( 'ai1ec_timezone' );
$candidates[] = (string)$this->_registry->get( 'model.option' )
->get( 'timezone_string' );
$candidates[] = (string)$this->_registry->get( 'model.option' )
->get( 'gmt_offset' );
$candidates = array_filter( $candidates, 'strlen' );
foreach ( $candidates as $timezone ) {
$timezone = $this->get_name( $timezone );
if ( false !== $timezone ) {
$default_timezone = $timezone;
break;
}
}
if ( null === $default_timezone ) {
$default_timezone = 'UTC';
$this->_registry->get( 'notification.admin' )->store(
sprintf(
Ai1ec_I18n::__(
'Please select site timezone in %s <em>Timezone</em> dropdown menu.'
),
'<a href="' . ai1ec_admin_url( 'options-general.php' ) .
'">' . Ai1ec_I18n::__( 'Settings' ) . '</a>'
),
'error'
);
}
}
return $default_timezone;
}
/**
* Attempt to decode GMT offset to some Olson timezone.
*
* @param float $zone GMT offset.
*
* @return string Valid Olson timezone name (UTC is last resort).
*/
public function decode_gmt_timezone( $zone ) {
$auto_zone = timezone_name_from_abbr( null, $zone * 3600, true );
if ( false !== $auto_zone ) {
return $auto_zone;
}
$auto_zone = timezone_name_from_abbr(
null,
( (int) $zone ) * 3600,
true
);
if ( false !== $auto_zone ) {
return $auto_zone;
}
$this->_registry->get( 'notification.admin' )->store(
sprintf(
Ai1ec_I18n::__(
'Timezone "UTC%+d" is not recognized. Please %suse valid%s timezone name, until then events will be created in UTC timezone.'
),
$zone,
'<a href="' . ai1ec_admin_url( 'options-general.php' ) . '">',
'</a>'
),
'error'
);
return 'UTC';
}
/**
* Get valid timezone name from input.
*
* @param string $zone Name to check/parse.
*
* @return string Timezone name to use
*/
public function get_name( $zone ) {
if ( is_numeric( $zone ) ) {
$decoded_zone = $this->decode_gmt_timezone( $zone );
if ( 'UTC' !== $decoded_zone ) {
$message = sprintf(
Ai1ec_I18n::__(
'Selected timezone "UTC%+d" will be treated as %s.'
),
$zone,
$decoded_zone
);
$this->_registry->get( 'notification.admin' )
->store( $message );
}
$zone = $decoded_zone;
}
if ( false === $this->_identifiers ) {
return $zone; // anything should do, as zones are not supported
}
if ( ! isset( $this->_identifiers[$zone] ) ) {
$zone = $this->_olson_lookup( $zone );
$valid_legacy = false;
try {
new DateTimeZone( $zone ); // throw away instantly
$valid_legacy = true;
} catch ( Exception $excpt ) {
$valid_legacy = false;
}
if ( ! $valid_legacy || isset( $this->_invalid_legacy[$zone] ) ) {
return $this->guess_zone( $zone );
}
$this->_identifiers[$zone] = $zone;
unset( $valid_legacy );
}
return $zone;
}
/**
* Quick map look-up to discard zones that have limited recognition.
*
* @param string $zone Name of timezone to lookup.
*
* @return string Timezone name to use. Might be the same as $zone.
*/
protected function _olson_lookup( $zone ) {
if ( isset( $this->_zones[$zone] ) ) {
return $this->_zones[$zone];
}
return $zone;
}
/**
* Check if timezone is set in wp_option
*
*/
public function is_timezone_not_set() {
$timezone = $this->_registry->get( 'model.option' )
->get( 'timezone_string' );
return empty( $timezone );
}
/**
* Render options for select in settings
*
* @return array
*/
public function get_timezones( $only_zones = false ) {
$zones = DateTimeZone::listIdentifiers();
if (
empty( $zones )
) {
return array();
}
if ( ! $only_zones ) {
$manual = __( 'Manual Offset', AI1EC_PLUGIN_NAME );
$options = array();
$options[$manual][] = array(
'text' => __( 'Choose your timezone', AI1EC_PLUGIN_NAME ),
'value' => '',
'args' => array(
'selected' => 'selected'
)
);
}
foreach ( $zones as $zone ) {
$exploded_zone = explode( '/', $zone );
if ( ! isset( $exploded_zone[1] ) && ! $only_zones ) {
$exploded_zone[1] = $exploded_zone[0];
$exploded_zone[0] = $manual;
}
$optgroup = $exploded_zone[0];
unset( $exploded_zone[0] );
$options[$optgroup][] = array(
'text' => implode( '/', $exploded_zone ),
'value' => $zone,
);
}
return $options;
}
/**
* Guess valid timezone identifier from arbitrary input.
*
* @param string $meta_name Arbitrary input.
*
* @return string|bool Parsed timezone name or false if none found.
*/
public function guess_zone( $meta_name ) {
if ( isset( $this->_zones[$meta_name] ) ) {
return $this->_zones[$meta_name];
}
$name_variants = array(
strtr( $meta_name, ' ', '_' ),
strtr( $meta_name, '_', ' ' ),
);
if ( false !== ( $parenthesis_pos = strpos( $meta_name, '(' ) ) ) {
foreach ( $name_variants as $name ) {
$name_variants[] = substr( $name, 0, $parenthesis_pos - 1 );
}
}
foreach ( $name_variants as $name ) {
if ( isset( $this->_zones[$name] ) ) {
// cache to avoid future lookups and return
$this->_zones[$meta_name] = $this->_zones[$name];
return $this->_zones[$name];
}
}
if (
isset( $meta_name{0} ) &&
'(' === $meta_name{0} &&
$closing_pos = strpos( $meta_name, ')' )
) {
$meta_name = trim( substr( $meta_name, $closing_pos + 1 ) );
return $this->guess_zone( $meta_name );
}
if (
false === strpos( $meta_name, ' Standard ' ) &&
false !== ( $time_pos = strpos( $meta_name, ' Time' ) )
) {
$meta_name = substr( $meta_name, 0, $time_pos ) .
' Standard' . substr( $meta_name, $time_pos );
return $this->guess_zone( $meta_name );
}
return false;
}
/**
* Get timezone object instance.
*
* @param string $timezone Name of timezone to get instance for.
*
* @return DateTimeZone Instance of timezone object.
*
* @throws Ai1ec_Date_Timezone_Exception If an error occurs.
*/
public function get( $timezone ) {
if ( 'sys.default' === $timezone ) {
$timezone = $this->get_default_timezone();
}
$name = $this->get_name( $timezone );
if ( ! $name ) {
$name = $this->get_name( $this->get_default_timezone() );
}
$zone = $this->_cache->get( $name, null );
if ( null === $zone ) {
$exception = null;
try {
$zone = new DateTimeZone( $name );
} catch ( Exception $invalid_tz ) {
$exception = $invalid_tz;
}
if ( null !== $exception ) {
throw new Ai1ec_Date_Timezone_Exception( $exception->getMessage() );
}
$this->_cache->set( $name, $zone );
}
return $zone;
}
/**
* Add system identifiers to object registry.
*
* @return bool Success
*/
protected function _init_identifiers() {
$identifiers = DateTimeZone::listIdentifiers();
if ( ! $identifiers ) {
return false;
}
$mapped = array();
foreach ( $identifiers as $zone ) {
$zone = (string)$zone;
$mapped[$zone] = true;
$this->_zones[$zone] = $zone;
}
unset( $identifiers, $zone );
$this->_identifiers = $mapped;
return true;
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* Validation utility library
*
* @author Timely Network Inc
* @since 2012.08.21
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Lib.Utility
*/
class Ai1ec_Validation_Utility {
/**
* Check if the date supplied is valid. It validates $date in the format given
* by $pattern, which matches one of the supported date patterns.
*
* @param string $date Date string to validate
* @param  string $pattern Key of date pattern (@see
* self::get_date_patterns()) to
* match date string against
* @return boolean
*/
static public function validate_date( $date, $pattern = 'def' ) {
$result = self::validate_date_and_return_parsed_date( $date, $pattern );
if( $result === false ) {
return false;
}
return true;
}
/**
* Check if the date supplied is valid. It validates date in the format given
* by $pattern, which matches one of the supported date patterns.
*
* @param string $date Date string to parse
* @param string $pattern Key of date pattern (@see
* self::get_date_patterns()) to
* match date string against
* @return array|boolean An array with the parsed date or false if the date
* is not valid.
*/
static public function validate_date_and_return_parsed_date(
$date, $pattern = 'def'
) {
$pattern = self::_get_pattern_regexp( $pattern );
if ( preg_match( $pattern, $date, $matches ) ) {
if ( checkdate( $matches['m'], $matches['d'], $matches['y'] ) ) {
return array(
'month' => $matches['m'],
'day' => $matches['d'],
'year' => $matches['y'],
);
}
}
return false;
}
/**
* Convert input into a valid ISO date.
*
* @param string $date Date to convert to ISO.
* @param string $pattern Format used to store it.
*
* @return string|bool Re-formatted date or false on failure.
*/
static public function format_as_iso( $date, $pattern = 'def' ) {
$regexp = self::_get_pattern_regexp( $pattern );
if ( ! preg_match( $regexp, $date, $matches ) ) {
return false;
}
return sprintf(
'%04d-%02d-%02d',
$matches['y'],
$matches['m'],
$matches['d']
);
}
/**
* Create regexp with named groups to match positional elements.
*
* @param string $pattern Pattern to convert.
*
* @return string Regular expression pattern.
*/
static protected function _get_pattern_regexp( $pattern ) {
$pattern = self::get_date_pattern_by_key( $pattern );
$pattern = preg_quote( $pattern, '/' );
$pattern = str_replace(
array( 'dd', 'd', 'mm', 'm', 'yyyy', 'yy' ),
array( '(?P<d>\d{2})', '(?P<d>\d{1,2})', '(?P<m>\d{2})', '(?P<m>\d{1,2})', '(?P<y>\d{4})', '(?P<y>\d{2})' ),
$pattern
);
// Accept hyphens and dots in place of forward slashes (for URLs).
$pattern = str_replace( '\/', '[\/\-\.]', $pattern );
return '#^' . $pattern . '$#';
}
/**
* Check if the string or integer is a valid timestamp.
*
* @see http://stackoverflow.com/questions/2524680/check-whether-the-string-is-a-unix-timestamp
* @param string|int $timestamp
* @return boolean
*/
static public function is_valid_time_stamp( $timestamp ) {
return
(
is_int( $timestamp ) ||
( (string)(int)$timestamp ) === (string)$timestamp
)
&& ( $timestamp <= PHP_INT_MAX )
&& ( $timestamp >= 0 /*~ PHP_INT_MAX*/ );
// do not allow negative timestamps until this is widely accepted
}
/**
* Returns the associative array of date patterns supported by the plugin,
* currently:
* array(
* 'def' => 'd/m/yyyy',
* 'us' => 'm/d/yyyy',
* 'iso' => 'yyyy-m-d',
* 'dot' => 'm.d.yyyy',
* );
*
* 'd' or 'dd' represent the day, 'm' or 'mm' represent the month, and 'yy'
* or 'yyyy' represent the year.
*
* @return array Supported date patterns
*/
static public function get_date_patterns() {
return array(
'def' => 'd/m/yyyy',
'us' => 'm/d/yyyy',
'iso' => 'yyyy-m-d',
'dot' => 'm.d.yyyy',
);
}
/**
* Returns the date pattern (in the form 'd-m-yyyy', for example) associated
* with the provided key, used by plugin settings. Simply a static map as
* follows:
*
* @param string $key Key for the date format
* @return string Associated date format pattern
*/
static public function get_date_pattern_by_key( $key = 'def' ) {
$patterns = self::get_date_patterns();
return $patterns[$key];
}
}