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
This commit is contained in:
Tim Starling 2024-07-11 16:28:32 +10:00
parent b492ad2a0d
commit 06f07b0000
6 changed files with 156 additions and 11 deletions

View file

@ -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' ],

View file

@ -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.",

View file

@ -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}}",

View file

@ -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' ] );

View file

@ -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 {
'</strong>';
}
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 '<strong class="error">' .
wfMessage( 'pfunc_timef_bad_format' ) .
'</strong>';
}
$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"

View file

@ -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)
</p>
!! end
!! test
#timef: one parameter
!! wikitext
{{#timef:15 January 2001}}
!! html
<p>00:00, 15 January 2001
</p>
!! end
!! test
#timef: date only
!! wikitext
{{#timef:15 January 2001|date}}
!! html
<p>15 January 2001
</p>
!! end
!! test
#timef: time only
!! wikitext
{{#timef:15 January 2001|time}}
!! html
<p>00:00
</p>
!! end
!! test
#timef: "pretty" format
!! wikitext
{{#timef:15 January 2001|pretty}}
!! html
<p>15 January
</p>
!! end
!! test
#timef: Japanese target language
!! options
language=ja
!! wikitext
{{#timef:15 January 2001}}
!! html
<p>2001年1月15日 (月) 00:00
</p>
!! end
!! test
#timef: ja parameter
!! wikitext
{{#timef:15 January 2001|both|ja}}
!! html
<p>2001年1月15日 (月) 00:00
</p>
!! end
!! test
#timefl: one parameter (can't actually customise timezone)
!! wikitext
{{#timefl:15 January 2001}}
!! html
<p>00:00, 15 January 2001
</p>
!! end
!! test
#titleparts
!! wikitext