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