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