all-in-one-event-calendar/lib/date/time.php
2017-11-09 17:36:04 +01:00

423 lines
No EOL
13 KiB
PHP

<?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;
}
}