Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 8 Mar 2013 */ namespace IPS; /* To prevent PHP errors (extending class does not exist) revealing path */ use DateInterval; use DateTimeZone; use Exception; use UnexpectedValueException; use function count; use function defined; use function in_array; use function substr; if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Date/Time Class */ class DateTime extends \DateTime { /** * Create from timestamp * * @param int$ timestamp UNIX Timestamp * @param bool $bypassTimezone Ignore timezone (useful for things like rfc1123() which forces to GMT anyways) * @return DateTime */ public static function ts( int $timestamp, bool $bypassTimezone=FALSE ): DateTime { $obj = new static; $obj->setTimestamp( $timestamp ); if ( !$bypassTimezone AND Dispatcher::hasInstance() and Member::loggedIn()->timezone ) { if( in_array( Member::loggedIn()->timezone, static::getTimezoneIdentifiers() ) ) { try { $obj->setTimezone( new DateTimeZone( Member::loggedIn()->timezone ) ); } catch ( Exception $e ) { } } } return $obj; } /** * @brief Cached timezone identifiers */ protected static ?array $timeZoneIdentifiers = NULL; /** * Get the valid time zone identifiers * * @note Abstracted to implement caching * @return array */ public static function getTimezoneIdentifiers(): array { if( static::$timeZoneIdentifiers === NULL ) { static::$timeZoneIdentifiers = DateTimeZone::listIdentifiers(); } return static::$timeZoneIdentifiers; } /** * Helper method to fix Argentinian Timezones https://bugs.webkit.org/show_bug.cgi?id=218542 * * @param string|null $timezone * @return string|null */ public static function getFixedTimezone( ?string $timezone=NULL ) :? string { if ( in_array( $timezone, [ 'America/Buenos_Aires', 'America/Catamarca', 'America/Cordoba', 'America/Jujuy', 'America/La_Rioja', 'America/Mendoza', 'America/Rio_Gallegos', 'America/Salta', 'America/San_Juan', 'America/San_Luis', 'America/Tucuman', 'America/Ushuaia' ] ) ) { return 'America/Argentina/' . substr( $timezone, 8 ); } /* Chromium reports the wrong timezones for these locations - https://bugs.chromium.org/p/chromium/issues/detail?id=580195 */ return match ( $timezone ) { 'Asia/Calcutta' => 'Asia/Kolkata', 'Asia/Katmandu' => 'Asia/Kathmandu', default => $timezone, }; } /** * Create New * * @return DateTime */ public static function create(): DateTime { return new static; } /** * Format a DateInterval showing only the relevant pieces. * * @param DateInterval $diff The interval * @param int $restrictParts The maximum number of "pieces" to return. Restricts "1 year, 1 month, 1 day, 1 hour, 1 minute, 1 second" to just "1 year, 1 month". Pass 0 to not reduce. * @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member * @return string */ public static function formatInterval( DateInterval $diff, int $restrictParts=2, Lang|Member $memberOrLanguage=NULL ): string { $language = static::determineLanguage( $memberOrLanguage ); /* Figure out what pieces we have. Note that we are letting the language manager perform the formatting to implement better pluralization. */ $format = array(); if( $diff->y !== 0 ) { $format[] = $language->addToStack( 'f_years', FALSE, array( 'pluralize' => array( $diff->y ) ) ); } if( $diff->m !== 0 ) { $format[] = $language->addToStack( 'f_months', FALSE, array( 'pluralize' => array( $diff->m ) ) ); } if( $diff->d !== 0 ) { $format[] = $language->addToStack( 'f_days', FALSE, array( 'pluralize' => array( $diff->d ) ) ); } if( $diff->h !== 0 ) { $format[] = $language->addToStack( 'f_hours', FALSE, array( 'pluralize' => array( $diff->h ) ) ); } if( $diff->i !== 0 ) { $format[] = $language->addToStack( 'f_minutes', FALSE, array( 'pluralize' => array( $diff->i ) ) ); } /* If we don't have anything but seconds, return "less than a minute ago" */ if( !count($format) ) { if( $diff->s !== 0 ) { return $language->addToStack('less_than_a_minute'); } } else if( $diff->s !== 0 ) { $format[] = $language->addToStack( 'f_seconds', FALSE, array( 'pluralize' => array( $diff->s ) ) ); } /* If we are still here, reduce the number of items in the $format array as appropriate */ if( $restrictParts > 0 ) { $useOnly = array(); $haveUsed = 0; foreach( $format as $period ) { $useOnly[] = $period; $haveUsed++; if( $haveUsed >= $restrictParts ) { break; } } $format = $useOnly; } return $language->formatList( $format ); } /** * Format the date and time according to the user's locale * * @return string * @note We cast to a string explicitly because in some environments (e.g. Windows) boolean FALSE can be returned from strftime if conversion specifiers are not valid */ public function __toString() { return static::strftime( '%x ' . static::localeTimeFormat(), $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), Member::loggedIn()->language() ); } /** * Get HTML output * * @param bool $capitalize TRUE if by itself, FALSE if in the middle of a sentence * @param bool $short Whether or not to use the short form * @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member * @param bool $useTitle Whether to add a title attribute to the