Skip to content

Commit 5ff6e3f

Browse files
jenkins-botGerrit Code Review
authored andcommitted
Merge "Introduce new showcalendar option for time formatters"
2 parents 36fedb4 + 1e2a15b commit 5ff6e3f

14 files changed

+570
-217
lines changed

lib/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"version-wikibase": "Wikibase",
5858
"wikibase-time-calendar-gregorian": "Gregorian",
5959
"wikibase-time-calendar-julian": "Julian",
60+
"wikibase-time-with-calendar": "$1 ($2)",
6061
"wikibase-time-precision-CE": "$1 CE",
6162
"wikibase-time-precision-Gannum": "$1 billion years CE",
6263
"wikibase-time-precision-Mannum": "$1 million years CE",

lib/i18n/qqq.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"version-wikibase": "Name of the Wikibase extension collection, used on [[Special:Version]]\n{{Identical|Wikibase}}",
7272
"wikibase-time-calendar-gregorian": "Label displayed with the formatted dates referring to the Gregorian calendar model.\n{{Identical|Gregorian}}",
7373
"wikibase-time-calendar-julian": "Label displayed with the formatted dates referring to the Julian calendar model.\n{{Identical|Julian}}",
74+
"wikibase-time-with-calendar": "{{optional}}\nFormat for showing a timestamp together with a calendar model.\n\nParameters:\n* $1 - the formatted timestamp\n* $2 - the calendar model (may be {{msg-mw|wikibase-time-calendar-gregorian}}, {{msg-mw|wikibase-time-calendar-julian}}, or an item ID)",
7475
"wikibase-time-precision-CE": "Used to present a point in time CE (current era) with arbitrary precision, if the year on its own could be mistaken for another date component (has one or two digits). $1 is the point in time.\n{{Related|Wikibase-time-precision}}",
7576
"wikibase-time-precision-Gannum": "Used to present a point in time with the precision of 1 billion of years. $1 is the point in time in billion years, rounded to billion years.\n{{Related|Wikibase-time-precision}}",
7677
"wikibase-time-precision-Mannum": "Used to present a point in time with the precision of 1 million of years. $1 is the point in time in million years, rounded to million years.\n{{Related|Wikibase-time-precision}}",

lib/includes/Formatters/HtmlTimeFormatter.php

Lines changed: 10 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99

1010
/**
1111
* A value formatter that creates a basic, single-line HTML representation of a TimeValue's date,
12-
* time and calendar model. The calendar model is added in superscript when needed, either because
13-
* it's not obvious (e.g. a date in 1800 could be Julian or Gregorian) or because it's different
14-
* from what the parsers would detect (where 1582 and before is Julian, and 1583 and later is
15-
* Gregorian).
12+
* time and calendar model. The calendar model is added in superscript when needed,
13+
* as determined by the {@link ShowCalendarModelDecider} (taking the options into account).
1614
*
1715
* @see \Wikibase\Lib\Formatters\TimeDetailsFormatter
1816
*
@@ -38,19 +36,25 @@ class HtmlTimeFormatter implements ValueFormatter {
3836
*/
3937
private $options;
4038

39+
private ShowCalendarModelDecider $decider;
40+
4141
/**
4242
* @param FormatterOptions|null $options
4343
* @param ValueFormatter $dateTimeFormatter A value formatter that accepts TimeValue objects and
4444
* returns the formatted date and time, but not the calendar model. Must return HTML.
45+
* @param ShowCalendarModelDecider $decider
4546
*/
4647
public function __construct(
4748
?FormatterOptions $options,
48-
ValueFormatter $dateTimeFormatter
49+
ValueFormatter $dateTimeFormatter,
50+
ShowCalendarModelDecider $decider
4951
) {
5052
$this->options = $options ?: new FormatterOptions();
5153
$this->options->defaultOption( ValueFormatter::OPT_LANG, 'en' );
54+
$this->options->defaultOption( ShowCalendarModelDecider::OPT_SHOW_CALENDAR, 'auto' );
5255

5356
$this->dateTimeFormatter = $dateTimeFormatter;
57+
$this->decider = $decider;
5458
}
5559

5660
/**
@@ -68,7 +72,7 @@ public function format( $value ) {
6872

6973
$formatted = $this->dateTimeFormatter->format( $value );
7074

71-
if ( $this->calendarNameNeeded( $value ) ) {
75+
if ( $this->decider->showCalendarModel( $value, $this->options ) ) {
7276
$formatted .= '<sup class="wb-calendar-name">'
7377
. $this->formatCalendarName( $value->getCalendarModel() )
7478
. '</sup>';
@@ -77,65 +81,6 @@ public function format( $value ) {
7781
return $formatted;
7882
}
7983

80-
/**
81-
* @param TimeValue $value
82-
*
83-
* @return bool
84-
*/
85-
private function calendarNameNeeded( TimeValue $value ) {
86-
// Do not care about possibly wrong calendar models with precision 10 years and more.
87-
if ( $value->getPrecision() <= TimeValue::PRECISION_YEAR10 ) {
88-
return false;
89-
}
90-
91-
// Loose check if the timestamp string is ISO-ish and starts with a year.
92-
if ( !preg_match( '/^[-+]?\d+\b/', $value->getTime(), $matches ) ) {
93-
return true;
94-
}
95-
96-
// NOTE: PHP limits overly large values to PHP_INT_MAX. No overflow or wrap-around occurs.
97-
$year = (int)$matches[0];
98-
$guessedCalendar = $this->getDefaultCalendar( $year );
99-
100-
// Always show the calendar if it's different from the "guessed" default.
101-
if ( $value->getCalendarModel() !== $guessedCalendar ) {
102-
return true;
103-
}
104-
105-
// If precision is year or less precise, don't show the calendar.
106-
if ( $value->getPrecision() <= TimeValue::PRECISION_YEAR ) {
107-
return false;
108-
}
109-
110-
// If the date is inside the "critical" range where Julian and Gregorian were used
111-
// in parallel, always show the calendar. Gregorian was made "official" in October 1582 but
112-
// may already be used earlier. Julian continued to be official until the 1920s in Russia
113-
// and Greece, see https://en.wikipedia.org/wiki/Julian_calendar.
114-
if ( $year > 1580 && $year < 1930 ) {
115-
return true;
116-
}
117-
118-
// Otherwise, the calendar is "unsurprising", so don't show it.
119-
return false;
120-
}
121-
122-
/**
123-
* This guesses the most likely calendar model based on the given TimeValue,
124-
* ignoring the calendar given in the TimeValue. This should always implement the
125-
* exact same heuristic as IsoTimestampParser::getCalendarModel().
126-
*
127-
* @see IsoTimestampParser::getCalendarModel()
128-
*
129-
* @param int $year
130-
*
131-
* @return string Calendar URI
132-
*/
133-
private function getDefaultCalendar( $year ) {
134-
// The Gregorian calendar was introduced in October 1582,
135-
// so we'll default to Julian for all years before 1583.
136-
return $year <= 1582 ? TimeValue::CALENDAR_JULIAN : TimeValue::CALENDAR_GREGORIAN;
137-
}
138-
13984
/**
14085
* @param string $calendarModel
14186
*
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace Wikibase\Lib\Formatters;
6+
7+
use DataValues\TimeValue;
8+
use ValueFormatters\FormatterOptions;
9+
use ValueFormatters\ValueFormatter;
10+
11+
/**
12+
* A value formatter that formats a time value as plain text,
13+
* including the calendar model if necessary or specified by the formatter options.
14+
* The calendar model is added in parentheses when needed,
15+
* as determined by the {@link ShowCalendarModelDecider} (taking the options into account).
16+
*
17+
* @license GPL-2.0-or-later
18+
*/
19+
class PlaintextTimeFormatter implements ValueFormatter {
20+
21+
private const CALENDAR_KEYS = [
22+
TimeValue::CALENDAR_GREGORIAN => 'wikibase-time-calendar-gregorian',
23+
TimeValue::CALENDAR_JULIAN => 'wikibase-time-calendar-julian',
24+
];
25+
26+
private FormatterOptions $options;
27+
private ValueFormatter $dateTimeFormatter;
28+
private ShowCalendarModelDecider $decider;
29+
30+
/**
31+
* @param FormatterOptions|null $options
32+
* @param ValueFormatter $dateTimeFormatter A value formatter that accepts TimeValue objects and
33+
* returns the formatted date and time, but not the calendar model.
34+
* @param ShowCalendarModelDecider $decider
35+
*/
36+
public function __construct(
37+
?FormatterOptions $options,
38+
ValueFormatter $dateTimeFormatter,
39+
ShowCalendarModelDecider $decider
40+
) {
41+
$this->options = $options ?: new FormatterOptions();
42+
$this->options->defaultOption( ValueFormatter::OPT_LANG, 'en' );
43+
// for backwards compatibility with older versions, of Wikibase,
44+
// never show the calendar model by default (users have to opt into 'auto')
45+
$this->options->defaultOption( ShowCalendarModelDecider::OPT_SHOW_CALENDAR, false );
46+
47+
$this->dateTimeFormatter = $dateTimeFormatter;
48+
$this->decider = $decider;
49+
}
50+
51+
public function format( $value ): string {
52+
$formatted = $this->dateTimeFormatter->format( $value );
53+
54+
if ( $this->decider->showCalendarModel( $value, $this->options ) ) {
55+
$formatted = wfMessage( 'wikibase-time-with-calendar' )
56+
->inLanguage( $this->options->getOption( ValueFormatter::OPT_LANG ) )
57+
->plaintextParams(
58+
$formatted,
59+
$this->formatCalendarName( $value->getCalendarModel() )
60+
)->text();
61+
}
62+
63+
return $formatted;
64+
}
65+
66+
private function formatCalendarName( string $calendarModel ): string {
67+
if ( array_key_exists( $calendarModel, self::CALENDAR_KEYS ) ) {
68+
$key = self::CALENDAR_KEYS[$calendarModel];
69+
$lang = $this->options->getOption( ValueFormatter::OPT_LANG );
70+
$msg = wfMessage( $key )->inLanguage( $lang );
71+
72+
if ( $msg->exists() ) {
73+
return $msg->text();
74+
}
75+
}
76+
77+
return $calendarModel;
78+
}
79+
80+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace Wikibase\Lib\Formatters;
6+
7+
use DataValues\TimeValue;
8+
use ValueFormatters\FormatterOptions;
9+
10+
/**
11+
* A helper class for time value formatters,
12+
* deciding whether the calendar model should be shown or not.
13+
*
14+
* When the 'showcalendar' formatter option is set to 'auto',
15+
* the calendar model is shown if it's not obvious
16+
* (e.g. a date in 1800 could be Julian or Gregorian)
17+
* or if it's different from what the parsers would detect
18+
* (where 1582 and before is Julian, and 1583 and later is Gregorian).
19+
*
20+
* @license GPL-2.0-or-later
21+
*/
22+
class ShowCalendarModelDecider {
23+
24+
/**
25+
* A {@link FormatterOptions formatter option} that determines
26+
* whether the calendar model will be shown or not.
27+
*
28+
* If true or false, always show or don’t show the calendar model;
29+
* if 'auto', show the calendar model if it is needed.
30+
*/
31+
public const OPT_SHOW_CALENDAR = 'showcalendar';
32+
33+
/**
34+
* Decide whether the calendar model should be shown or not.
35+
*
36+
* @param TimeValue $value The time value to which the decision applies.
37+
* @param FormatterOptions $options The formatter options.
38+
* The caller *must* have set a default for {@link ShowCalendarModelDecider::OPT_SHOW_CALENDAR}.
39+
* @return bool
40+
*/
41+
public function showCalendarModel( TimeValue $value, FormatterOptions $options ): bool {
42+
$options->requireOption( self::OPT_SHOW_CALENDAR );
43+
$show = $options->getOption( self::OPT_SHOW_CALENDAR );
44+
if ( $show === 'auto' ) {
45+
$show = $this->calendarNameNeeded( $value );
46+
}
47+
return $show;
48+
}
49+
50+
private function calendarNameNeeded( TimeValue $value ): bool {
51+
// Do not care about possibly wrong calendar models with precision 10 years and more.
52+
if ( $value->getPrecision() <= TimeValue::PRECISION_YEAR10 ) {
53+
return false;
54+
}
55+
56+
// Loose check if the timestamp string is ISO-ish and starts with a year.
57+
if ( !preg_match( '/^[-+]?\d+\b/', $value->getTime(), $matches ) ) {
58+
return true;
59+
}
60+
61+
// NOTE: PHP limits overly large values to PHP_INT_MAX. No overflow or wrap-around occurs.
62+
$year = (int)$matches[0];
63+
$guessedCalendar = $this->getDefaultCalendar( $year );
64+
65+
// Always show the calendar if it's different from the "guessed" default.
66+
if ( $value->getCalendarModel() !== $guessedCalendar ) {
67+
return true;
68+
}
69+
70+
// If precision is year or less precise, don't show the calendar.
71+
if ( $value->getPrecision() <= TimeValue::PRECISION_YEAR ) {
72+
return false;
73+
}
74+
75+
// If the date is inside the "critical" range where Julian and Gregorian were used
76+
// in parallel, always show the calendar. Gregorian was made "official" in October 1582 but
77+
// may already be used earlier. Julian continued to be official until the 1920s in Russia
78+
// and Greece, see https://en.wikipedia.org/wiki/Julian_calendar.
79+
if ( $year > 1580 && $year < 1930 ) {
80+
return true;
81+
}
82+
83+
// Otherwise, the calendar is "unsurprising", so don't show it.
84+
return false;
85+
}
86+
87+
/**
88+
* This guesses the most likely calendar model based on the given TimeValue,
89+
* ignoring the calendar given in the TimeValue. This should always implement the
90+
* exact same heuristic as IsoTimestampParser::getCalendarModel().
91+
*
92+
* @see IsoTimestampParser::getCalendarModel()
93+
*
94+
* @param int $year
95+
*
96+
* @return string Calendar URI
97+
*/
98+
private function getDefaultCalendar( int $year ): string {
99+
// The Gregorian calendar was introduced in October 1582,
100+
// so we'll default to Julian for all years before 1583.
101+
return $year <= 1582 ? TimeValue::CALENDAR_JULIAN : TimeValue::CALENDAR_GREGORIAN;
102+
}
103+
104+
}

lib/includes/Formatters/TimeDetailsFormatter.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,17 @@ class TimeDetailsFormatter implements ValueFormatter {
3131

3232
/**
3333
* @param FormatterOptions|null $options
34-
* @param ValueFormatter|null $timeFormatter A TimeValue formatter that outputs a single line of
34+
* @param ValueFormatter $timeFormatter A TimeValue formatter that outputs a single line of
3535
* HTML, suitable for headings.
3636
*/
3737
public function __construct(
38-
FormatterOptions $options = null,
39-
ValueFormatter $timeFormatter = null
38+
?FormatterOptions $options,
39+
ValueFormatter $timeFormatter
4040
) {
4141
$this->options = $options ?: new FormatterOptions();
4242
$this->options->defaultOption( ValueFormatter::OPT_LANG, 'en' );
4343

44-
$this->timeFormatter = $timeFormatter ?: new HtmlTimeFormatter(
45-
$this->options,
46-
new MwTimeIsoFormatter( $this->options )
47-
);
44+
$this->timeFormatter = $timeFormatter;
4845
}
4946

5047
/**

lib/includes/Formatters/WikibaseValueFormatterBuilders.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,16 +402,16 @@ public function newEntitySchemaFormatter( $format, FormatterOptions $options ) {
402402
* @return ValueFormatter
403403
*/
404404
public function newTimeFormatter( $format, FormatterOptions $options ) {
405-
// TODO: Add a wikitext formatter that shows the calendar model
406405
if ( $this->snakFormat->isPossibleFormat( SnakFormatter::FORMAT_HTML_DIFF, $format ) ) {
407406
return new TimeDetailsFormatter(
408407
$options,
409-
new HtmlTimeFormatter( $options, new MwTimeIsoFormatter( $options ) )
408+
new HtmlTimeFormatter( $options, new MwTimeIsoFormatter( $options ), new ShowCalendarModelDecider() )
410409
);
411410
} elseif ( $this->isHtmlFormat( $format ) ) {
412-
return new HtmlTimeFormatter( $options, new MwTimeIsoFormatter( $options ) );
411+
return new HtmlTimeFormatter( $options, new MwTimeIsoFormatter( $options ), new ShowCalendarModelDecider() );
413412
} else {
414-
return $this->escapeValueFormatter( $format, new MwTimeIsoFormatter( $options ) );
413+
return $this->escapeValueFormatter( $format,
414+
new PlaintextTimeFormatter( $options, new MwTimeIsoFormatter( $options ), new ShowCalendarModelDecider() ) );
415415
}
416416
}
417417

0 commit comments

Comments
 (0)