From 66b5a3ac3f00a051afcd925b6e4edcc9c95c8cc3 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 8 Dec 2024 13:18:36 -0700 Subject: [PATCH 1/5] Workaround for a PHP date parsing oddity in SMF\Time::__construct() Signed-off-by: Jon Stovell --- Sources/Time.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Sources/Time.php b/Sources/Time.php index 5dbfc02d8e..ea8a735050 100644 --- a/Sources/Time.php +++ b/Sources/Time.php @@ -187,12 +187,24 @@ public function __construct(string $datetime = 'now', \DateTimeZone|string|null unset($timezone); } - parent::__construct($datetime, $timezone ?? self::$user_tz); - - // If $datetime was a Unix timestamp, force the time zone to be the one we were told to use. - // Honestly, it's a mystery why the \DateTime class doesn't do this itself already... - if (str_starts_with($datetime, '@')) { + if ( + // If $datetime was a Unix timestamp, set the time zone to the one + // we were told to use. Honestly, it's a mystery why the \DateTime + // class doesn't do this itself already... + str_starts_with($datetime, '@') + // In some versions of PHP, unexpected results may be produced if + // $datetime contains the special 'now' or 'ago' keywords and also + // contains a time zone ID string (e.g. 'now Europe/Paris'), but + // that time zone ID string is different than the one in $timezone. + // In order to avoid problems, we use two steps when the 'now' or + // 'ago' keywords are present. + || str_contains($datetime, 'now') + || str_contains($datetime, 'ago') + ) { + parent::__construct($datetime); $this->setTimezone($timezone ?? self::$user_tz); + } else { + parent::__construct($datetime, $timezone ?? self::$user_tz); } } From 6a8cff670ebdb9260a9478cc3edb30d4785293e4 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 9 Dec 2024 13:55:08 -0700 Subject: [PATCH 2/5] Improves input sanitization in SMF\Time::__construct() Signed-off-by: Jon Stovell --- Sources/Time.php | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/Sources/Time.php b/Sources/Time.php index ea8a735050..c2de222bf1 100644 --- a/Sources/Time.php +++ b/Sources/Time.php @@ -151,6 +151,13 @@ class Time extends \DateTime implements \ArrayAccess */ protected static array $today; + /** + * @var string + * + * Regular expression to match all keywords recognized by PHP's date parser. + */ + protected static string $parsable_words_regex; + /**************** * Public methods ****************/ @@ -187,6 +194,8 @@ public function __construct(string $datetime = 'now', \DateTimeZone|string|null unset($timezone); } + $datetime = self::sanitize($datetime); + if ( // If $datetime was a Unix timestamp, set the time zone to the one // we were told to use. Honestly, it's a mystery why the \DateTime @@ -1010,6 +1019,62 @@ public static function isStrftimeFormat(string $format): bool return (bool) preg_match('/' . self::REGEX_STRFTIME . '/', $format); } + /** + * Removes text that the date parser wouldn't recognize. + * + * @param string $datetime A date/time string that needs to be parsed. + * @return string Sanitized version of $datetime. + */ + public static function sanitize(string $datetime): string + { + self::setParsableWordsRegex(); + + // Remove HTML. + $datetime = strip_tags($datetime); + + // Parsing fails when AM/PM is not separated from the time by a space. + $datetime = preg_replace_callback_array( + [ + '/(\s\d?\d)([ap]\.?m\.?)/i' => fn ($matches) => $matches[1] . ':00 ' . $matches[2], + '/(:\d\d)([ap]\.?m\.?)/i' => fn ($matches) => $matches[1] . ' ' . $matches[2], + ], + $datetime, + ); + + // Protect the parsable strings. + $placeholders = []; + + $datetime = preg_replace_callback( + [ + '~(GMT)?[+\-](0?\d|1[0-2]):?([0-5]\d)~i', + '~\b' . self::$parsable_words_regex . '\b~iu', + '~[ap]\.?m\.?~i', + '~\d+(st|nd|rd|th)~i', + '~(\b|d+)[TW]\d+~i', + '~[.:+\-/@]~', + '~\d+~', + ], + function ($matches) use (&$placeholders) { + $char = mb_chr(0xE000 + count($placeholders)); + $placeholders[$char] = $matches[0]; + + return $char; + }, + $datetime, + ); + + // Remove unparsable strings. + $datetime = preg_replace('~[^\s' . implode('', array_keys($placeholders)) . ']~u', '', $datetime); + + // Restore the parsable strings. + $datetime = strtr($datetime, $placeholders); + + // Clean up white space. + $datetime = trim(Utils::normalizeSpaces($datetime, true, true, ['collapse_hspace' => true, 'replace_tabs' => true, 'no_breaks' => true])); + + return $datetime; + } + /** * Backward compatibility wrapper for the format method. * @@ -1298,6 +1363,44 @@ protected static function datetimePartialFormat(string $type, string $format): s return self::$formats[$orig_format][$type]; } + + /** + * Builds a regex to match words that the date parser recognizes and saves + * it in self::$parsable_words_regex. + */ + protected static function setParsableWordsRegex(): void + { + self::$parsable_words_regex = self::$parsable_words_regex ?? Utils::buildRegex( + array_merge( + // Time zone abbreviations. + array_filter(array_keys(\DateTimeZone::listAbbreviations()), fn ($a) => !is_numeric($a)), + // Time zone identifiers. + \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), + // Recognized key words. + [ + 'january', 'february', 'march', 'april', 'may', 'june', + 'july', 'august', 'september', 'october', 'november', + 'december', 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', + 'aug', 'sep', 'sept', 'oct', 'nov', 'dec', 'I', 'II', 'III', + 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', + 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', + 'friday', 'saturday', 'sun', 'mon', 'tue', 'wed', 'thu', + 'fri', 'sat', 'first', 'second', 'third', 'fourth', 'fifth', + 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', + 'twelfth', 'next', 'last', 'previous', 'this', 'ms', 'µs', + 'msec', 'millisecond', 'µsec', 'microsecond', 'usec', 'sec', + 'second', 'min', 'minute', 'hour', 'day', 'week', + 'fortnight', 'forthnight', 'month', 'year', 'msecs', + 'milliseconds', 'µsecs', 'microseconds', 'usecs', 'secs', + 'seconds', 'mins', 'minutes', 'hours', 'days', 'weeks', + 'fortnights', 'forthnights', 'months', 'years', 'yesterday', + 'midnight', 'today', 'now', 'noon', 'tomorrow', 'back', + 'front', 'of', 'ago', + ], + ), + '~', + ); + } } ?> \ No newline at end of file From 7d140d8dd461a10c97b98c6703bab617fada4838 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 19 Dec 2024 23:27:37 -0700 Subject: [PATCH 3/5] Adds support for 'tomorrow' as a relative date format Signed-off-by: Jon Stovell --- Languages/en_US/General.php | 1 + Languages/en_US/Help.php | 4 ++-- Languages/en_US/ManageSettings.php | 2 +- Sources/Time.php | 30 ++++++++++++++++++++---------- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Languages/en_US/General.php b/Languages/en_US/General.php index 945e8854f9..2fee8be1df 100644 --- a/Languages/en_US/General.php +++ b/Languages/en_US/General.php @@ -591,6 +591,7 @@ $txt['today'] = 'Today at '; $txt['yesterday'] = 'Yesterday at '; +$txt['tomorrow'] = 'Tomorrow at '; $txt['new_poll'] = 'New poll'; $txt['poll_question'] = 'Question'; $txt['poll_vote'] = 'Submit Vote'; diff --git a/Languages/en_US/Help.php b/Languages/en_US/Help.php index b63990d39f..b47100aa5d 100644 --- a/Languages/en_US/Help.php +++ b/Languages/en_US/Help.php @@ -228,7 +228,7 @@ $helptxt['titlesEnable'] = 'Switching Custom Titles on will allow members with the relevant permission to create a special title for themselves. This will be shown underneath the name.
For example:
Jeff
Cool Guy'; $helptxt['onlineEnable'] = 'This will show an image to indicate whether the member is online or offline'; -$helptxt['todayMod'] = 'This will show "Today" or "Yesterday" instead of the date.

+$helptxt['todayMod'] = 'This will show "Yesterday", "Today", or "Tomorrow" instead of the date.

Examples:

  • @@ -236,7 +236,7 @@ October 3, 2009 at 12:59:18 am
  • Only Today
    Today at 12:59:18 am
  • -
  • Today & Yesterday
    +
  • Yesterday, Today, & Tomorrow
    Yesterday at 09:36:55 pm
'; $helptxt['disableCustomPerPage'] = 'Check this setting to stop users from customizing the amount of messages and topics to display per page on the Message Index and Topic Display page respectively.'; diff --git a/Languages/en_US/ManageSettings.php b/Languages/en_US/ManageSettings.php index 79b6b866cf..99c0d491e0 100644 --- a/Languages/en_US/ManageSettings.php +++ b/Languages/en_US/ManageSettings.php @@ -96,7 +96,7 @@ $txt['todayMod'] = 'Enable shorthand date display'; $txt['today_disabled'] = 'Disabled'; $txt['today_only'] = 'Only Today'; -$txt['yesterday_today'] = 'Today & Yesterday'; +$txt['yesterday_today'] = 'Yesterday, Today, & Tomorrow'; $txt['onlineEnable'] = 'Show online/offline status in posts and PMs'; $txt['defaultMaxMembers'] = 'Members per page in member list'; $txt['timeLoadPageEnable'] = 'Display time taken to create every page'; diff --git a/Sources/Time.php b/Sources/Time.php index c2de222bf1..fd5c37c50a 100644 --- a/Sources/Time.php +++ b/Sources/Time.php @@ -485,28 +485,38 @@ public function format(?string $format = null, ?bool $relative = null, ?bool $st $format = strtr($format, self::FORMAT_SHORT_FORMS); } - // Today and Yesterday? - $prefix = ''; - - if ($relative && Config::$modSettings['todayMod'] >= 1) { + // Yesterday, today, or tomorrow? + if (!$relative) { + $prefix = ''; + } else { $tzid = date_format($this, 'e'); if (!isset(self::$today[$tzid])) { self::$today[$tzid] = strtotime('today ' . $tzid); } - // Tomorrow? We don't support the future. ;) - if ($this->getTimestamp() >= self::$today[$tzid] + 86400) { - $prefix = ''; + // The future. + if ($this->getTimestamp() >= self::$today[$tzid] + 172800) { + $relative_day = null; + } + // Tomorrow. + elseif ($this->getTimestamp() >= self::$today[$tzid] + 86400) { + $relative_day = Config::$modSettings['todayMod'] > 1 ? 'tomorrow' : null; } // Today. elseif ($this->getTimestamp() >= self::$today[$tzid]) { - $prefix = Lang::$txt['today'] ?? ''; + $relative_day = Config::$modSettings['todayMod'] >= 1 ? 'today' : null; } // Yesterday. - elseif (Config::$modSettings['todayMod'] > 1 && $this->getTimestamp() >= self::$today[$tzid] - 86400) { - $prefix = Lang::$txt['yesterday'] ?? ''; + elseif ($this->getTimestamp() >= self::$today[$tzid] - 86400) { + $relative_day = Config::$modSettings['todayMod'] > 1 ? 'yesterday' : null; } + // The past. + else { + $relative_day = null; + } + + $prefix = Lang::$txt[$relative_day] ?? ''; } $format = !empty($prefix) ? self::getTimeFormat($format) : $format; From 0deda752eb2db0777e4395b6e583cb7f66d14a14 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 8 Dec 2024 13:25:01 -0700 Subject: [PATCH 4/5] Moves Calendar::convertDateToEnglish() to Time::convertToEnglish() Signed-off-by: Jon Stovell --- Sources/Actions/Calendar.php | 52 ++-------------------- Sources/Subs-Compat.php | 10 ++--- Sources/Time.php | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 53 deletions(-) diff --git a/Sources/Actions/Calendar.php b/Sources/Actions/Calendar.php index 773b60664c..40b7ff6aa0 100644 --- a/Sources/Actions/Calendar.php +++ b/Sources/Actions/Calendar.php @@ -171,7 +171,7 @@ public function show(): void // Need a start date for all views if (!empty($_REQUEST['start_date'])) { - $start_parsed = date_parse(str_replace(',', '', self::convertDateToEnglish($_REQUEST['start_date']))); + $start_parsed = date_parse(str_replace(',', '', Time::convertToEnglish($_REQUEST['start_date']))); if (empty($start_parsed['error_count']) && empty($start_parsed['warning_count'])) { $_REQUEST['year'] = $start_parsed['year']; @@ -187,7 +187,7 @@ public function show(): void // Need an end date for the list view if (!empty($_REQUEST['end_date'])) { - $end_parsed = date_parse(str_replace(',', '', self::convertDateToEnglish($_REQUEST['end_date']))); + $end_parsed = date_parse(str_replace(',', '', Time::convertToEnglish($_REQUEST['end_date']))); if (empty($end_parsed['error_count']) && empty($end_parsed['warning_count'])) { $_REQUEST['end_year'] = $end_parsed['year']; @@ -1486,7 +1486,7 @@ public static function validateEventPost(): void if (!isset($_POST['deleteevent'])) { // The 2.1 way if (isset($_POST['start_date'])) { - $d = date_parse(str_replace(',', '', self::convertDateToEnglish($_POST['start_date']))); + $d = date_parse(str_replace(',', '', Time::convertToEnglish($_POST['start_date']))); if (!empty($d['error_count']) || !empty($d['warning_count'])) { ErrorHandler::fatalLang('invalid_date', false); @@ -1500,7 +1500,7 @@ public static function validateEventPost(): void ErrorHandler::fatalLang('event_month_missing', false); } } elseif (isset($_POST['start_datetime'])) { - $d = date_parse(str_replace(',', '', self::convertDateToEnglish($_POST['start_datetime']))); + $d = date_parse(str_replace(',', '', Time::convertToEnglish($_POST['start_datetime']))); if (!empty($d['error_count']) || !empty($d['warning_count'])) { ErrorHandler::fatalLang('invalid_date', false); @@ -1624,50 +1624,6 @@ public static function removeHolidays(array $holiday_ids): void } } - /** - * Helper function to convert date string to english - * so that date_parse can parse the date - * - * @param string $date A localized date string - * @return string English date string - */ - public static function convertDateToEnglish(string $date): string - { - if (User::$me->language == 'english') { - return $date; - } - - $replacements = array_combine(array_map('strtolower', Lang::$txt['months_titles']), [ - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December', - ]); - $replacements += array_combine(array_map('strtolower', Lang::$txt['months_short']), [ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', - ]); - $replacements += array_combine(array_map('strtolower', Lang::$txt['days']), [ - 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', - ]); - $replacements += array_combine(array_map('strtolower', Lang::$txt['days_short']), [ - 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', - ]); - // Find all possible variants of AM and PM for this language. - $replacements[strtolower(Lang::$txt['time_am'])] = 'AM'; - $replacements[strtolower(Lang::$txt['time_pm'])] = 'PM'; - - if (($am = Time::strftime('%p', strtotime('01:00:00'))) !== 'p' && $am !== false) { - $replacements[strtolower($am)] = 'AM'; - $replacements[strtolower(Time::strftime('%p', strtotime('23:00:00')))] = 'PM'; - } - - if (($am = Time::strftime('%P', strtotime('01:00:00'))) !== 'P' && $am !== false) { - $replacements[strtolower($am)] = 'AM'; - $replacements[strtolower(Time::strftime('%P', strtotime('23:00:00')))] = 'PM'; - } - - return strtr(strtolower($date), $replacements); - } - /****************** * Internal methods ******************/ diff --git a/Sources/Subs-Compat.php b/Sources/Subs-Compat.php index bd3151ba23..55037db963 100644 --- a/Sources/Subs-Compat.php +++ b/Sources/Subs-Compat.php @@ -1940,11 +1940,6 @@ function removeHolidays(array $holiday_ids): void Actions\Calendar::removeHolidays($holiday_ids); } - function convertDateToEnglish(string $date): string - { - return Actions\Calendar::convertDateToEnglish($date); - } - /** * Begin * Actions\CoppaForm @@ -4767,6 +4762,11 @@ function timeformat(int $log_time, bool|string $show_today = true, ?string $tzid return SMF\Time::timeformat($log_time, $show_today, $tzid); } + function convertDateToEnglish(string $date): string + { + return SMF\Time::convertToEnglish($date); + } + /** @deprecated since 2.1 */ function forum_time(bool $use_user_offset = true, ?int $timestamp = null): int { diff --git a/Sources/Time.php b/Sources/Time.php index fd5c37c50a..5a4ed6e093 100644 --- a/Sources/Time.php +++ b/Sources/Time.php @@ -1085,6 +1085,91 @@ function ($matches) use (&$placeholders) { return $datetime; } + /** + * Helper function to convert a date string to English so that date_parse() + * can parse it correctly. + * + * @param string $date A localized date string. + * @return string English date string. + */ + public static function convertToEnglish(string $date): string + { + self::setParsableWordsRegex(); + + // Preserve any existing parseable words, such as time zone identifiers. + $placeholders = []; + + $date = preg_replace_callback( + '~\b' . self::$parsable_words_regex . '\b~iu', + function ($matches) use (&$placeholders) { + $char = mb_chr(0xE000 + count($placeholders)); + $placeholders[$char] = $matches[0]; + + return $char; + }, + $date, + ); + + // Build an array of regular expressions to translate the current language strings to English. + $replacements = array_combine( + array_map(fn ($arg) => '~' . $arg . '~iu', Lang::$txt['months_titles']), + [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December', + ], + ); + + $replacements += array_combine( + array_map(fn ($arg) => '~' . $arg . '~iu', Lang::$txt['months_short']), + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + ); + + $replacements += array_combine( + array_map(fn ($arg) => '~' . $arg . '~iu', Lang::$txt['days']), + ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + ); + + $replacements += array_combine( + array_map(fn ($arg) => '~' . $arg . '~iu', Lang::$txt['days_short']), + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + ); + + // Find all possible variants of AM and PM for this language. + $replacements['~' . Lang::$txt['time_am'] . '~iu'] = 'AM'; + $replacements['~' . Lang::$txt['time_pm'] . '~iu'] = 'PM'; + + if (($am = self::strftime('%p', strtotime('01:00:00'))) !== 'p' && $am !== false) { + $replacements['~' . $am . '~iu'] = 'AM'; + $replacements['~' . self::strftime('%p', strtotime('23:00:00')) . '~iu'] = 'PM'; + } + + if (($am = self::strftime('%P', strtotime('01:00:00'))) !== 'P' && $am !== false) { + $replacements['~' . $am . '~iu'] = 'AM'; + $replacements['~' . self::strftime('%P', strtotime('23:00:00')) . '~iu'] = 'PM'; + } + + // Find this language's equivalents for today, yesterday, and tomorrow. + // In theory, it would be nice to do the same for other keywords used by + // PHP's date parser, but that would get very complicated very quickly. + foreach (['today', 'yesterday', 'tomorrow'] as $word) { + $translated_word = preg_replace('~\X*(\X*?)\X*~u', '$1', Lang::$txt[$word]); + $replacements['~\b' . $translated_word . '\b~iu'] = $word; + } + + // Wrap the replacement strings in closures. + foreach ($replacements as $pattern => $replacement) { + $replacements[$pattern] = fn ($matches) => $replacement; + } + + // Translate. + $date = preg_replace_callback_array($replacements, $date); + + // Restore the preserved words. + $date = strtr($date, $placeholders); + + return $date; + } + /** * Backward compatibility wrapper for the format method. * From 6716caceb31d1a5f125fd5610afe34fc3610fff9 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 11 Dec 2024 23:34:07 -0700 Subject: [PATCH 5/5] Tidies away some backward compatibility methods in SMF\Time Signed-off-by: Jon Stovell --- Sources/Actions/Admin/Bans.php | 2 +- Sources/Parsers/BBCodeParser.php | 2 +- Sources/Subs-Compat.php | 10 ++++-- Sources/Time.php | 54 ++++++++------------------------ 4 files changed, 23 insertions(+), 45 deletions(-) diff --git a/Sources/Actions/Admin/Bans.php b/Sources/Actions/Admin/Bans.php index 794a2d675f..5e843c9b8a 100644 --- a/Sources/Actions/Admin/Bans.php +++ b/Sources/Actions/Admin/Bans.php @@ -947,7 +947,7 @@ public function log(): void ], 'data' => [ 'function' => function ($rowData) { - return Time::timeformat($rowData['log_time']); + return Time::stringFromUnix($rowData['log_time']); }, ], 'sort' => [ diff --git a/Sources/Parsers/BBCodeParser.php b/Sources/Parsers/BBCodeParser.php index 1504001db2..0aebb55387 100644 --- a/Sources/Parsers/BBCodeParser.php +++ b/Sources/Parsers/BBCodeParser.php @@ -626,7 +626,7 @@ class BBCodeParser extends Parser 'parameters' => [ 'author' => ['match' => '([^<>]{1,192}?)'], 'link' => ['match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'], - 'date' => ['match' => '(\d+)', 'validate' => 'SMF\\Time::timeformat'], + 'date' => ['match' => '(\d+)', 'validate' => 'SMF\\Time::stringFromUnix'], ], 'before' => '
{txt_quote_from}: {author} {txt_search_on} {date}', 'after' => '
', diff --git a/Sources/Subs-Compat.php b/Sources/Subs-Compat.php index 55037db963..db6c26e415 100644 --- a/Sources/Subs-Compat.php +++ b/Sources/Subs-Compat.php @@ -4759,7 +4759,13 @@ function get_date_or_time_format(string $type = '', string $format = '', ?bool $ function timeformat(int $log_time, bool|string $show_today = true, ?string $tzid = null): string { - return SMF\Time::timeformat($log_time, $show_today, $tzid); + // For backward compatibility, replace empty values with the user's time + // zone and replace anything invalid with the forum's default time zone. + $tzid = empty($tzid) ? SMF\User::getTimezone() : (($tzid === 'forum' || @timezone_open((string) $tzid) === false) ? SMF\Config::$modSettings['default_timezone'] : $tzid); + + $date = new SMF\Time('@' . $log_time, $tzid); + + return is_bool($show_today) ? $date->format(null, $show_today) : $date->format($show_today); } function convertDateToEnglish(string $date): string @@ -4770,7 +4776,7 @@ function convertDateToEnglish(string $date): string /** @deprecated since 2.1 */ function forum_time(bool $use_user_offset = true, ?int $timestamp = null): int { - return SMF\Time::forumTime($use_user_offset, $timestamp); + return !isset($timestamp) ? time() : (int) $timestamp; } /** diff --git a/Sources/Time.php b/Sources/Time.php index 5a4ed6e093..0a937fbcb8 100644 --- a/Sources/Time.php +++ b/Sources/Time.php @@ -829,6 +829,19 @@ public static function gmstrftime(string $format, ?int $timestamp = null): strin return self::strftime($format, $timestamp, 'UTC'); } + /** + * Like self::strftime(), but always uses the current user's time zone and + * preferred time format. + * + * @param int|string|null $timestamp A Unix timestamp. + * If null or invalid, defaults to the current time. + * @return string A formatted time string. + */ + public static function stringFromUnix(int|string|null $timestamp = null): string + { + return self::create('@' . (is_numeric($timestamp) ? $timestamp : time()))->format(); + } + /** * Returns a strftime format or DateTime format for showing dates. * @@ -1170,47 +1183,6 @@ function ($matches) use (&$placeholders) { return $date; } - /** - * Backward compatibility wrapper for the format method. - * - * @param int|string $log_time A timestamp. - * @param bool|string $show_today Whether to show "Today"/"Yesterday" or - * just a date. If a string is specified, that is used to temporarily - * override the date format. - * @param string $tzid Time zone identifier string of the time zone to use. - * If empty, the user's time zone will be used. - * If set to a valid time zone identifier, that will be used. - * Otherwise, the value of Config::$modSettings['default_timezone'] will - * be used. - * @return string A formatted time string - */ - public static function timeformat(int|string $log_time, bool|string $show_today = true, ?string $tzid = null): string - { - $log_time = (int) $log_time; - - // For backward compatibility, replace empty values with the user's time - // zone and replace anything invalid with the forum's default time zone. - $tzid = empty($tzid) ? User::getTimezone() : (($tzid === 'forum' || @timezone_open((string) $tzid) === false) ? Config::$modSettings['default_timezone'] : $tzid); - - $date = new self('@' . $log_time); - $date->setTimezone(new \DateTimeZone($tzid)); - - return is_bool($show_today) ? $date->format(null, $show_today) : $date->format($show_today); - } - - /** - * Backward compatibility method. - * - * @deprecated since 2.1 - * @param bool $use_user_offset This parameter is deprecated and ignored. - * @param int $timestamp A timestamp (null to use current time). - * @return int Seconds since the Unix epoch. - */ - public static function forumTime(bool $use_user_offset = true, ?int $timestamp = null): int - { - return !isset($timestamp) ? time() : (int) $timestamp; - } - /************************* * Internal static methods *************************/