You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1362 lines
32 KiB

<?php
/**
* CodeIgniter
*
* An open source application development framework for PHP
*
* This content is released under the MIT License (MIT)
*
* Copyright (c) 2014-2019 British Columbia Institute of Technology
* Copyright (c) 2019 CodeIgniter Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2019 CodeIgniter Foundation
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 4.0.0
* @filesource
*/
namespace CodeIgniter\I18n;
use CodeIgniter\I18n\Exceptions\I18nException;
use IntlCalendar;
use Locale;
use DateTime;
use DateInterval;
use DateTimeZone;
use IntlDateFormatter;
/**
* Class Time
*
* A localized date/time package inspired
* by Nesbot/Carbon and CakePHP/Chronos.
*
* Requires the intl PHP extension.
*
* @package CodeIgniter\I18n
*/
class Time extends DateTime
{
/**
* @var string
*/
protected $timezone;
/**
* @var string
*/
protected $locale;
/**
* Format to use when displaying datetime through __toString
*
* @var string
*/
protected $toStringFormat = 'yyyy-MM-dd HH:mm:ss';
/**
* Used to check time string to determine if it is relative time or not....
*
* @var string
*/
protected static $relativePattern = '/this|next|last|tomorrow|yesterday|midnight|today|[+-]|first|last|ago/i';
/**
* @var \CodeIgniter\I18n\Time
*/
protected static $testNow;
//--------------------------------------------------------------------
// Constructors
//--------------------------------------------------------------------
/**
* Time constructor.
*
* @param string|null $time
* @param null $timezone
* @param string|null $locale
*
* @throws \Exception
*/
public function __construct(string $time = null, $timezone = null, string $locale = null)
{
// If no locale was provided, grab it from Locale (set by IncomingRequest for web requests)
$this->locale = ! empty($locale) ? $locale : Locale::getDefault();
// If a test instance has been provided, use it instead.
if (is_null($time) && static::$testNow instanceof Time)
{
if (empty($timezone))
{
$timezone = static::$testNow->getTimezone();
}
$time = static::$testNow->toDateTimeString();
}
$timezone = ! empty($timezone) ? $timezone : date_default_timezone_get();
$this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
if (! empty($time))
{
// If the time string was a relative string (i.e. 'next Tuesday')
// then we need to adjust the time going in so that we have a current
// timezone to work with.
if (is_string($time) && static::hasRelativeKeywords($time))
{
$instance = new DateTime('now', $this->timezone);
$instance->modify($time);
$time = $instance->format('Y-m-d H:i:s');
}
}
return parent::__construct($time, $this->timezone);
}
//--------------------------------------------------------------------
/**
* Returns a new Time instance with the timezone set.
*
* @param string|DateTimeZone|null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function now($timezone = null, string $locale = null)
{
return new Time(null, $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns a new Time instance while parsing a datetime string.
*
* Example:
* $time = Time::parse('first day of December 2008');
*
* @param string $datetime
* @param string|null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function parse(string $datetime, $timezone = null, string $locale = null)
{
return new Time($datetime, $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Return a new time with the time set to midnight.
*
* @param null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function today($timezone = null, string $locale = null)
{
return new Time(date('Y-m-d 00:00:00'), $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns an instance set to midnight yesterday morning.
*
* @param null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function yesterday($timezone = null, string $locale = null)
{
return new Time(date('Y-m-d 00:00:00', strtotime('-1 day')), $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns an instance set to midnight tomorrow morning.
*
* @param null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function tomorrow($timezone = null, string $locale = null)
{
return new Time(date('Y-m-d 00:00:00', strtotime('+1 day')), $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns a new instance based on the year, month and day. If any of those three
* are left empty, will default to the current value.
*
* @param integer|null $year
* @param integer|null $month
* @param integer|null $day
* @param null $timezone
* @param string $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function createFromDate(int $year = null, int $month = null, int $day = null, $timezone = null, string $locale = null)
{
return static::create($year, $month, $day, null, null, null, $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns a new instance with the date set to today, and the time set to the values passed in.
*
* @param integer|null $hour
* @param integer|null $minutes
* @param integer|null $seconds
* @param null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function createFromTime(int $hour = null, int $minutes = null, int $seconds = null, $timezone = null, string $locale = null)
{
return static::create(null, null, null, $hour, $minutes, $seconds, $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Returns a new instance with the date time values individually set.
*
* @param integer|null $year
* @param integer|null $month
* @param integer|null $day
* @param integer|null $hour
* @param integer|null $minutes
* @param integer|null $seconds
* @param null $timezone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function create(int $year = null, int $month = null, int $day = null, int $hour = null, int $minutes = null, int $seconds = null, $timezone = null, string $locale = null)
{
$year = is_null($year) ? date('Y') : $year;
$month = is_null($month) ? date('m') : $month;
$day = is_null($day) ? date('d') : $day;
$hour = empty($hour) ? 0 : $hour;
$minutes = empty($minutes) ? 0 : $minutes;
$seconds = empty($seconds) ? 0 : $seconds;
return new Time(date('Y-m-d H:i:s', strtotime("{$year}-{$month}-{$day} {$hour}:{$minutes}:{$seconds}")), $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Provides a replacement for DateTime's own createFromFormat function, that provides
* more flexible timeZone handling
*
* @param string $format
* @param string $datetime
* @param DateTimeZone|string $timeZone
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function createFromFormat($format, $datetime, $timeZone = null)
{
$date = parent::createFromFormat($format, $datetime);
return new Time($date->format('Y-m-d H:i:s'), $timeZone);
}
//--------------------------------------------------------------------
/**
* Returns a new instance with the datetime set based on the provided UNIX timestamp.
*
* @param integer $timestamp
* @param DateTimeZone $timeZone
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function createFromTimestamp(int $timestamp, $timeZone = null, string $locale = null)
{
return new Time(date('Y-m-d H:i:s', $timestamp), $timeZone, $locale);
}
//--------------------------------------------------------------------
/**
* Takes an instance of DateTime and returns an instance of Time with it's same values.
*
* @param \DateTime $dateTime
* @param string|null $locale
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public static function instance(DateTime $dateTime, string $locale = null)
{
$date = $dateTime->format('Y-m-d H:i:s');
$timezone = $dateTime->getTimezone();
return new Time($date, $timezone, $locale);
}
//--------------------------------------------------------------------
/**
* Converts the current instance to a mutable DateTime object.
*
* @return \DateTime
* @throws \Exception
*/
public function toDateTime()
{
$dateTime = new DateTime(null, $this->getTimezone());
$dateTime->setTimestamp(parent::getTimestamp());
return $dateTime;
}
//--------------------------------------------------------------------
// For Testing
//--------------------------------------------------------------------
/**
* Creates an instance of Time that will be returned during testing
* when calling 'Time::now' instead of the current time.
*
* @param \CodeIgniter\I18n\Time|string $datetime
* @param null $timezone
* @param string|null $locale
*
* @throws \Exception
*/
public static function setTestNow($datetime = null, $timezone = null, string $locale = null)
{
// Reset the test instance
if (is_null($datetime))
{
static::$testNow = null;
return;
}
// Convert to a Time instance
if (is_string($datetime))
{
$datetime = new Time($datetime, $timezone, $locale);
}
else if ($datetime instanceof DateTime && ! $datetime instanceof Time)
{
$datetime = new Time($datetime->format('Y-m-d H:i:s'), $timezone);
}
static::$testNow = $datetime;
}
//--------------------------------------------------------------------
/**
* Returns whether we have a testNow instance saved.
*
* @return boolean
*/
public static function hasTestNow(): bool
{
return ! is_null(static::$testNow);
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Getters
//--------------------------------------------------------------------
/**
* Returns the localized Year
*
* @return string
* @throws \Exception
*/
public function getYear(): string
{
return $this->toLocalizedString('Y');
}
//--------------------------------------------------------------------
/**
* Returns the localized Month
*
* @return string
* @throws \Exception
*/
public function getMonth(): string
{
return $this->toLocalizedString('M');
}
//--------------------------------------------------------------------
/**
* Return the localized day of the month.
*
* @return string
* @throws \Exception
*/
public function getDay(): string
{
return $this->toLocalizedString('d');
}
//--------------------------------------------------------------------
/**
* Return the localized hour (in 24-hour format).
*
* @return string
* @throws \Exception
*/
public function getHour(): string
{
return $this->toLocalizedString('H');
}
//--------------------------------------------------------------------
/**
* Return the localized minutes in the hour.
*
* @return string
* @throws \Exception
*/
public function getMinute(): string
{
return $this->toLocalizedString('m');
}
//--------------------------------------------------------------------
/**
* Return the localized seconds
*
* @return string
* @throws \Exception
*/
public function getSecond(): string
{
return $this->toLocalizedString('s');
}
//--------------------------------------------------------------------
/**
* Return the index of the day of the week
*
* @return string
* @throws \Exception
*/
public function getDayOfWeek(): string
{
return $this->toLocalizedString('c');
}
//--------------------------------------------------------------------
/**
* Return the index of the day of the year
*
* @return string
* @throws \Exception
*/
public function getDayOfYear(): string
{
return $this->toLocalizedString('D');
}
//--------------------------------------------------------------------
/**
* Return the index of the week in the month
*
* @return string
* @throws \Exception
*/
public function getWeekOfMonth(): string
{
return $this->toLocalizedString('W');
}
//--------------------------------------------------------------------
/**
* Return the index of the week in the year
*
* @return string
* @throws \Exception
*/
public function getWeekOfYear(): string
{
return $this->toLocalizedString('w');
}
//--------------------------------------------------------------------
/**
* Returns the age in years from the "current" date and 'now'
*
* @throws \Exception
*/
public function getAge()
{
$now = Time::now()->getTimestamp();
$time = $this->getTimestamp();
// future dates have no age
return max(0, date('Y', $now) - date('Y', $time));
}
//--------------------------------------------------------------------
/**
* Returns the number of the current quarter for the year.
*
* @return string
* @throws \Exception
*/
public function getQuarter(): string
{
return $this->toLocalizedString('Q');
}
//--------------------------------------------------------------------
/**
* Are we in daylight savings time currently?
*
* @return boolean
*/
public function getDst(): bool
{
// grab the transactions that would affect today
$start = strtotime('-1 year', $this->getTimestamp());
$end = strtotime('+2 year', $start);
$transitions = $this->timezone->getTransitions($start, $end);
$daylightSaving = false;
foreach ($transitions as $transition)
{
if ($transition['time'] > $this->format('U'))
{
$daylightSaving = (bool) $transition['isdst'] ?? $daylightSaving;
}
}
return $daylightSaving;
}
//--------------------------------------------------------------------
/**
* Returns boolean whether the passed timezone is the same as
* the local timezone.
*
* @return boolean
*/
public function getLocal(): bool
{
$local = date_default_timezone_get();
return $local === $this->timezone->getName();
}
//--------------------------------------------------------------------
/**
* Returns boolean whether object is in UTC.
*
* @return boolean
*/
public function getUtc(): bool
{
return $this->getOffset() === 0;
}
/**
* Returns the name of the current timezone.
*
* @return string
*/
public function getTimezoneName(): string
{
return $this->timezone->getName();
}
//--------------------------------------------------------------------
// Setters
//--------------------------------------------------------------------
/**
* Sets the current year for this instance.
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setYear($value)
{
return $this->setValue('year', $value);
}
/**
* Sets the month of the year.
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setMonth($value)
{
if (is_numeric($value) && $value < 1 || $value > 12)
{
throw I18nException::forInvalidMonth($value);
}
if (is_string($value) && ! is_numeric($value))
{
$value = date('m', strtotime("{$value} 1 2017"));
}
return $this->setValue('month', $value);
}
/**
* Sets the day of the month.
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setDay($value)
{
if ($value < 1 || $value > 31)
{
throw I18nException::forInvalidDay($value);
}
$date = $this->getYear() . '-' . $this->getMonth();
$lastDay = date('t', strtotime($date));
if ($value > $lastDay)
{
throw I18nException::forInvalidOverDay($lastDay, $value);
}
return $this->setValue('day', $value);
}
/**
* Sets the hour of the day (24 hour cycle)
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setHour($value)
{
if ($value < 0 || $value > 23)
{
throw I18nException::forInvalidHour($value);
}
return $this->setValue('hour', $value);
}
/**
* Sets the minute of the hour
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setMinute($value)
{
if ($value < 0 || $value > 59)
{
throw I18nException::forInvalidMinutes($value);
}
return $this->setValue('minute', $value);
}
/**
* Sets the second of the minute.
*
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setSecond($value)
{
if ($value < 0 || $value > 59)
{
throw I18nException::forInvalidSeconds($value);
}
return $this->setValue('second', $value);
}
/**
* Helper method to do the heavy lifting of the 'setX' methods.
*
* @param string $name
* @param $value
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
protected function setValue(string $name, $value)
{
list($year, $month, $day, $hour, $minute, $second) = explode('-', $this->format('Y-n-j-G-i-s'));
$$name = $value;
return Time::create($year, $month, $day, $hour, $minute, $second, $this->getTimezoneName(), $this->locale);
}
/**
* Returns a new instance with the revised timezone.
*
* @param \DateTimeZone $timezone
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setTimezone($timezone)
{
return Time::parse($this->toDateTimeString(), $timezone, $this->locale);
}
/**
* Returns a new instance with the date set to the new timestamp.
*
* @param integer $timestamp
*
* @return \CodeIgniter\I18n\Time
* @throws \Exception
*/
public function setTimestamp($timestamp)
{
$time = date('Y-m-d H:i:s', $timestamp);
return Time::parse($time, $this->timezone, $this->locale);
}
//--------------------------------------------------------------------
// Add/Subtract
//--------------------------------------------------------------------
/**
* Returns a new Time instance with $seconds added to the time.
*
* @param integer $seconds
*
* @return static
*/
public function addSeconds(int $seconds)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$seconds} seconds"));
}
/**
* Returns a new Time instance with $minutes added to the time.
*
* @param integer $minutes
*
* @return static
*/
public function addMinutes(int $minutes)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$minutes} minutes"));
}
/**
* Returns a new Time instance with $hours added to the time.
*
* @param integer $hours
*
* @return static
*/
public function addHours(int $hours)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$hours} hours"));
}
/**
* Returns a new Time instance with $days added to the time.
*
* @param integer $days
*
* @return static
*/
public function addDays(int $days)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$days} days"));
}
/**
* Returns a new Time instance with $months added to the time.
*
* @param integer $months
*
* @return static
*/
public function addMonths(int $months)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$months} months"));
}
/**
* Returns a new Time instance with $years added to the time.
*
* @param integer $years
*
* @return static
*/
public function addYears(int $years)
{
$time = clone($this);
return $time->add(DateInterval::createFromDateString("{$years} years"));
}
/**
* Returns a new Time instance with $seconds subtracted from the time.
*
* @param integer $seconds
*
* @return static
*/
public function subSeconds(int $seconds)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$seconds} seconds"));
}
/**
* Returns a new Time instance with $minutes subtracted from the time.
*
* @param integer $minutes
*
* @return static
*/
public function subMinutes(int $minutes)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$minutes} minutes"));
}
/**
* Returns a new Time instance with $hours subtracted from the time.
*
* @param integer $hours
*
* @return static
*/
public function subHours(int $hours)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$hours} hours"));
}
/**
* Returns a new Time instance with $days subtracted from the time.
*
* @param integer $days
*
* @return static
*/
public function subDays(int $days)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$days} days"));
}
/**
* Returns a new Time instance with $months subtracted from the time.
*
* @param integer $months
*
* @return static
*/
public function subMonths(int $months)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$months} months"));
}
/**
* Returns a new Time instance with $hours subtracted from the time.
*
* @param integer $years
*
* @return static
*/
public function subYears(int $years)
{
$time = clone($this);
return $time->sub(DateInterval::createFromDateString("{$years} years"));
}
//--------------------------------------------------------------------
// Formatters
//--------------------------------------------------------------------
/**
* Returns the localized value of the date in the format 'Y-m-d H:i:s'
*
* @throws \Exception
*/
public function toDateTimeString()
{
return $this->toLocalizedString('yyyy-MM-dd HH:mm:ss');
}
//--------------------------------------------------------------------
/**
* Returns a localized version of the date in Y-m-d format.
*
* @return string
* @throws \Exception
*/
public function toDateString()
{
return $this->toLocalizedString('yyyy-MM-dd');
}
//--------------------------------------------------------------------
/**
* Returns a localized version of the date in nicer date format:
*
* i.e. Apr 1, 2017
*
* @return string
* @throws \Exception
*/
public function toFormattedDateString()
{
return $this->toLocalizedString('MMM d, yyyy');
}
//--------------------------------------------------------------------
/**
* Returns a localized version of the time in nicer date format:
*
* i.e. 13:20:33
*
* @return string
* @throws \Exception
*/
public function toTimeString()
{
return $this->toLocalizedString('HH:mm:ss');
}
//--------------------------------------------------------------------
/**
* Returns the localized value of this instance in $format.
*
* @param string|null $format
*
* @return string|boolean
* @throws \Exception
*/
public function toLocalizedString(?string $format = null)
{
$format = $format ?? $this->toStringFormat;
return IntlDateFormatter::formatObject($this->toDateTime(), $format, $this->locale);
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Comparison
//--------------------------------------------------------------------
/**
* Determines if the datetime passed in is equal to the current instance.
* Equal in this case means that they represent the same moment in time,
* and are not required to be in the same timezone, as both times are
* converted to UTC and compared that way.
*
* @param Time|DateTime|string $testTime
* @param string|null $timezone
*
* @return boolean
* @throws \Exception
*/
public function equals($testTime, string $timezone = null): bool
{
$testTime = $this->getUTCObject($testTime, $timezone);
$ourTime = $this->toDateTime()
->setTimezone(new DateTimeZone('UTC'))
->format('Y-m-d H:i:s');
return $testTime->format('Y-m-d H:i:s') === $ourTime;
}
//--------------------------------------------------------------------
/**
* Ensures that the times are identical, taking timezone into account.
*
* @param Time|DateTime|string $testTime
* @param string|null $timezone
*
* @return boolean
* @throws \Exception
*/
public function sameAs($testTime, string $timezone = null): bool
{
if ($testTime instanceof DateTime)
{
$testTime = $testTime->format('Y-m-d H:i:s');
}
else if (is_string($testTime))
{
$timezone = $timezone ?: $this->timezone;
$timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
$testTime = new DateTime($testTime, $timezone);
$testTime = $testTime->format('Y-m-d H:i:s');
}
$ourTime = $this->toDateTimeString();
return $testTime === $ourTime;
}
//--------------------------------------------------------------------
/**
* Determines if the current instance's time is before $testTime,
* after converting to UTC.
*
* @param $testTime
* @param string|null $timezone
*
* @return boolean
* @throws \Exception
*/
public function isBefore($testTime, string $timezone = null): bool
{
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
$ourTime = $this->getTimestamp();
return $ourTime < $testTime;
}
//--------------------------------------------------------------------
/**
* Determines if the current instance's time is after $testTime,
* after converting in UTC.
*
* @param $testTime
* @param string|null $timezone
*
* @return boolean
* @throws \Exception
*/
public function isAfter($testTime, string $timezone = null): bool
{
$testTime = $this->getUTCObject($testTime, $timezone)->getTimestamp();
$ourTime = $this->getTimestamp();
return $ourTime > $testTime;
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Differences
//--------------------------------------------------------------------
/**
* Returns a text string that is easily readable that describes
* how long ago, or how long from now, a date is, like:
*
* - 3 weeks ago
* - in 4 days
* - 6 hours ago
*
* @return mixed
* @throws \Exception
*/
public function humanize()
{
$now = IntlCalendar::fromDateTime(Time::now($this->timezone)->toDateTimeString());
$time = $this->getCalendar()->getTime();
$years = $now->fieldDifference($time, IntlCalendar::FIELD_YEAR);
$months = $now->fieldDifference($time, IntlCalendar::FIELD_MONTH);
$days = $now->fieldDifference($time, IntlCalendar::FIELD_DAY_OF_YEAR);
$hours = $now->fieldDifference($time, IntlCalendar::FIELD_HOUR_OF_DAY);
$minutes = $now->fieldDifference($time, IntlCalendar::FIELD_MINUTE);
$phrase = null;
if ($years !== 0)
{
$phrase = lang('Time.years', [abs($years)]);
$before = $years < 0;
}
else if ($months !== 0)
{
$phrase = lang('Time.months', [abs($months)]);
$before = $months < 0;
}
else if ($days !== 0 && (abs($days) >= 7))
{
$weeks = ceil($days / 7);
$phrase = lang('Time.weeks', [abs($weeks)]);
$before = $days < 0;
}
else if ($days !== 0)
{
$before = $days < 0;
// Yesterday/Tomorrow special cases
if (abs($days) === 1)
{
return $before ? lang('Time.yesterday') : lang('Time.tomorrow');
}
$phrase = lang('Time.days', [abs($days)]);
}
else if ($hours !== 0)
{
// Display the actual time instead of a regular phrase.
return $this->format('g:i a');
}
else if ($minutes !== 0)
{
$phrase = lang('Time.minutes', [abs($minutes)]);
$before = $minutes < 0;
}
else
{
return lang('Time.now');
}
return $before ? lang('Time.ago', [$phrase]) : lang('Time.inFuture', [$phrase]);
}
/**
* @param $testTime
* @param string|null $timezone
*
* @return \CodeIgniter\I18n\TimeDifference
* @throws \Exception
*/
public function difference($testTime, string $timezone = null)
{
$testTime = $this->getUTCObject($testTime, $timezone);
$ourTime = $this->getUTCObject($this);
return new TimeDifference($ourTime, $testTime);
}
//--------------------------------------------------------------------
// Utilities
//--------------------------------------------------------------------
/**
* Returns a Time instance with the timezone converted to UTC.
*
* @param $time
* @param string|null $timezone
*
* @return \DateTime|static
* @throws \Exception
*/
public function getUTCObject($time, string $timezone = null)
{
if ($time instanceof Time)
{
$time = $time->toDateTime()
->setTimezone(new DateTimeZone('UTC'));
}
else if ($time instanceof DateTime)
{
$time = $time->setTimezone(new DateTimeZone('UTC'));
}
else if (is_string($time))
{
$timezone = $timezone ?: $this->timezone;
$timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
$time = new DateTime($time, $timezone);
$time = $time->setTimezone(new DateTimeZone('UTC'));
}
return $time;
}
//--------------------------------------------------------------------
/**
* Returns the IntlCalendar object used for this object,
* taking into account the locale, date, etc.
*
* Primarily used internally to provide the difference and comparison functions,
* but available for public consumption if they need it.
*
* @return \IntlCalendar
* @throws \Exception
*/
public function getCalendar()
{
return IntlCalendar::fromDateTime($this->toDateTimeString());
}
//--------------------------------------------------------------------
/**
* Check a time string to see if it includes a relative date (like 'next Tuesday').
*
* @param string $time
*
* @return boolean
*/
protected static function hasRelativeKeywords(string $time): bool
{
// skip common format with a '-' in it
if (preg_match('/[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/', $time) !== 1)
{
return preg_match(static::$relativePattern, $time) > 0;
}
return false;
}
//--------------------------------------------------------------------
/**
* Outputs a short format version of the datetime.
*
* @return string
* @throws \Exception
*/
public function __toString(): string
{
return IntlDateFormatter::formatObject($this->toDateTime(), $this->toStringFormat, $this->locale);
}
//--------------------------------------------------------------------
/**
* Allow for property-type access to any getX method...
*
* Note that we cannot use this for any of our setX methods,
* as they return new Time objects, but the __set ignores
* return values.
* See http://php.net/manual/en/language.oop5.overloading.php
*
* @param $name
*
* @return mixed
*/
public function __get($name)
{
$method = 'get' . ucfirst($name);
if (method_exists($this, $method))
{
return $this->$method();
}
return null;
}
//--------------------------------------------------------------------
/**
* Allow for property-type checking to any getX method...
*
* @param $name
*
* @return boolean
*/
public function __isset($name): bool
{
$method = 'get' . ucfirst($name);
return method_exists($this, $method);
}
}