From 06f07b0000ed85989e37fc930de59a82a6a69d8f Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Thu, 11 Jul 2024 16:28:32 +1000 Subject: [PATCH] Add parser functions giving access to standard date/time formats Add parser functions #timef and #timefl, giving access to the standard date/time formats of any language. StubObject::unstub() is no longer required since the user language is unstubbed in ParserOptions::__construct(). Bug: T223772 Change-Id: If158f45aad67ff017f9cd15889915b226e03f50c --- ParserFunctions.i18n.magic.php | 6 +++ i18n/en.json | 1 + i18n/qqq.json | 1 + includes/Hooks.php | 2 + includes/ParserFunctions.php | 90 +++++++++++++++++++++++++++---- tests/parser/funcsParserTests.txt | 67 ++++++++++++++++++++++- 6 files changed, 156 insertions(+), 11 deletions(-) diff --git a/ParserFunctions.i18n.magic.php b/ParserFunctions.i18n.magic.php index 8032790a..2042f3c3 100644 --- a/ParserFunctions.i18n.magic.php +++ b/ParserFunctions.i18n.magic.php @@ -20,6 +20,12 @@ $magicWords['en'] = [ 'ifexist' => [ 0, 'ifexist' ], 'time' => [ 0, 'time' ], 'timel' => [ 0, 'timel' ], + 'timef' => [ 1, 'timef' ], + 'timefl' => [ 1, 'timefl' ], + 'timef-time' => [ 1, 'time' ], + 'timef-date' => [ 1, 'date' ], + 'timef-both' => [ 1, 'both' ], + 'timef-pretty' => [ 1, 'pretty' ], 'rel2abs' => [ 0, 'rel2abs' ], 'titleparts' => [ 0, 'titleparts' ], 'len' => [ 0, 'len' ], diff --git a/i18n/en.json b/i18n/en.json index f8bfc999..cc0b4816 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -7,6 +7,7 @@ "pfunc_time_too_long": "Error: Too many #time calls.", "pfunc_time_too_big": "Error: #time only supports years up to 9999.", "pfunc_time_too_small": "Error: #time only supports years from 0.", + "pfunc_timef_bad_format": "Error: invalid format for #timef", "pfunc_rel2abs_invalid_depth": "Error: Invalid depth in path: \"$1\" (tried to access a node above the root node).", "pfunc_expr_stack_exhausted": "Expression error: Stack exhausted.", "pfunc_expr_unexpected_number": "Expression error: Unexpected number.", diff --git a/i18n/qqq.json b/i18n/qqq.json index 67ff5e66..b7c2a9f5 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -15,6 +15,7 @@ "pfunc_time_too_long": "Used as error message.\n\nSee also:\n* {{msg-mw|Pfunc time error}}\n* {{msg-mw|Pfunc time too big}}\n* {{msg-mw|Pfunc time too small}}", "pfunc_time_too_big": "Used as error message.\n\nSee also:\n* {{msg-mw|Pfunc time error}}\n* {{msg-mw|Pfunc time too long}}\n* {{msg-mw|Pfunc time too small}}", "pfunc_time_too_small": "Used as error message.\n\nSee also:\n* {{msg-mw|Pfunc time error}}\n* {{msg-mw|Pfunc time too long}}\n* {{msg-mw|Pfunc time too big}}", + "pfunc_timef_bad_format": "Used as error message.", "pfunc_rel2abs_invalid_depth": "Used as error message. Parameters:\n* $1 - full path", "pfunc_expr_stack_exhausted": "Used as error message.\n{{Related|Pfunc expr}}", "pfunc_expr_unexpected_number": "Used as error message.\n{{Related|Pfunc expr}}", diff --git a/includes/Hooks.php b/includes/Hooks.php index 5b87948f..07926819 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -84,6 +84,8 @@ class Hooks implements $parser->setFunctionHook( 'iferror', [ $this->parserFunctions, 'iferror' ], Parser::SFH_OBJECT_ARGS ); $parser->setFunctionHook( 'time', [ $this->parserFunctions, 'time' ], Parser::SFH_OBJECT_ARGS ); $parser->setFunctionHook( 'timel', [ $this->parserFunctions, 'localTime' ], Parser::SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'timef', [ $this->parserFunctions, 'timef' ], Parser::SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'timefl', [ $this->parserFunctions, 'timefl' ], Parser::SFH_OBJECT_ARGS ); $parser->setFunctionHook( 'expr', [ $this->parserFunctions, 'expr' ] ); $parser->setFunctionHook( 'rel2abs', [ $this->parserFunctions, 'rel2abs' ] ); diff --git a/includes/ParserFunctions.php b/includes/ParserFunctions.php index 2185f6b2..47be1a15 100644 --- a/includes/ParserFunctions.php +++ b/includes/ParserFunctions.php @@ -19,7 +19,6 @@ use PPNode; use RepoGroup; use Sanitizer; use StringUtils; -use StubObject; use Title; use Wikimedia\RequestTimeout\TimeoutException; @@ -564,15 +563,8 @@ class ParserFunctions { ''; } - if ( $language !== '' && $this->languageNameUtils->isValidBuiltInCode( $language ) ) { - // use whatever language is passed as a parameter - $langObject = $this->languageFactory->getLanguage( $language ); - } else { - // use wiki's content language - $langObject = $parser->getTargetLanguage(); - // $ttl is passed by reference, which doesn't work right on stub objects - StubObject::unstub( $langObject ); - } + $langObject = $this->languageFactory->getLanguage( + $this->normalizeLangCode( $parser, $language ) ); $result = $langObject->sprintfDate( $format, $ts, $tz, $ttl ); } self::$mTimeCache[$format][$cacheKey][$language][$local] = [ $result, $ttl ]; @@ -582,6 +574,21 @@ class ParserFunctions { return $result; } + /** + * Convert an input string to a known language code for time formatting + * + * @param Parser $parser + * @param string $langCode + * @return string + */ + private function normalizeLangCode( Parser $parser, string $langCode ) { + if ( $langCode !== '' && $this->languageNameUtils->isKnownLanguageTag( $langCode ) ) { + return $langCode; + } else { + return $parser->getTargetLanguage()->getCode(); + } + } + /** * {{#time: format string }} * {{#time: format string | date/time object }} @@ -622,6 +629,69 @@ class ParserFunctions { return $this->timeCommon( $parser, $frame, $format, $date, $language, true ); } + /** + * Formatted time -- time with a symbolic rather than explicit format + * + * @param Parser $parser + * @param PPFrame $frame + * @param array $args + * @return string + */ + public function timef( Parser $parser, PPFrame $frame, array $args ) { + return $this->timefCommon( $parser, $frame, $args, false ); + } + + /** + * Formatted time -- time with a symbolic rather than explicit format + * Using the local timezone of the wiki. + * + * @param Parser $parser + * @param PPFrame $frame + * @param array $args + * @return string + */ + public function timefl( Parser $parser, PPFrame $frame, array $args ) { + return $this->timefCommon( $parser, $frame, $args, true ); + } + + /** + * Helper for timef and timefl + * + * @param Parser $parser + * @param PPFrame $frame + * @param array $args + * @param bool $local + * @return string + */ + private function timefCommon( Parser $parser, PPFrame $frame, array $args, $local ) { + $date = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : ''; + $inputType = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : ''; + + if ( $inputType !== '' ) { + $types = $parser->getMagicWordFactory()->newArray( [ + 'timef-time', + 'timef-date', + 'timef-both', + 'timef-pretty' + ] ); + $id = $types->matchStartToEnd( $inputType ); + if ( $id === false ) { + return '' . + wfMessage( 'pfunc_timef_bad_format' ) . + ''; + } + $type = str_replace( 'timef-', '', $id ); + } else { + $type = 'both'; + } + + $langCode = isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : ''; + $langCode = $this->normalizeLangCode( $parser, $langCode ); + $lang = $this->languageFactory->getLanguage( $langCode ); + $format = $lang->getDateFormatString( $type, 'default' ); + return $this->timeCommon( $parser, $frame, $format, $date, $langCode, $local ); + } + /** * Obtain a specified number of slash-separated parts of a title, * e.g. {{#titleparts:Hello/World|1}} => "Hello" diff --git a/tests/parser/funcsParserTests.txt b/tests/parser/funcsParserTests.txt index 7f525efa..9663ceea 100644 --- a/tests/parser/funcsParserTests.txt +++ b/tests/parser/funcsParserTests.txt @@ -97,7 +97,7 @@ Explicitly specified timezone: America/New_York (UTC-5) !! end !! test -Explicitely specified output language (Dutch) +Explicitly specified output language (Dutch) !! wikitext {{#time:d F Y|1988-02-28|nl}} !! html @@ -105,6 +105,71 @@ Explicitely specified output language (Dutch)

!! end +!! test +#timef: one parameter +!! wikitext +{{#timef:15 January 2001}} +!! html +

00:00, 15 January 2001 +

+!! end + +!! test +#timef: date only +!! wikitext +{{#timef:15 January 2001|date}} +!! html +

15 January 2001 +

+!! end + +!! test +#timef: time only +!! wikitext +{{#timef:15 January 2001|time}} +!! html +

00:00 +

+!! end + +!! test +#timef: "pretty" format +!! wikitext +{{#timef:15 January 2001|pretty}} +!! html +

15 January +

+!! end + +!! test +#timef: Japanese target language +!! options +language=ja +!! wikitext +{{#timef:15 January 2001}} +!! html +

2001年1月15日 (月) 00:00 +

+!! end + +!! test +#timef: ja parameter +!! wikitext +{{#timef:15 January 2001|both|ja}} +!! html +

2001年1月15日 (月) 00:00 +

+!! end + +!! test +#timefl: one parameter (can't actually customise timezone) +!! wikitext +{{#timefl:15 January 2001}} +!! html +

00:00, 15 January 2001 +

+!! end + !! test #titleparts !! wikitext