aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Starling <tstarling@wikimedia.org>2025-04-04 09:56:00 +1100
committerTim Starling <tstarling@wikimedia.org>2025-04-07 11:29:15 +1000
commitbb9f7a62c807b4c186a22fc111de780ddc1de8a9 (patch)
tree3ce810573c878b33749429f4f28185d319a15d3d
parent11fd26f8d49f26aeed2a6e5bc2c483dd88fa1472 (diff)
downloadmediawikicore-bb9f7a62c807b4c186a22fc111de780ddc1de8a9.tar.gz
mediawikicore-bb9f7a62c807b4c186a22fc111de780ddc1de8a9.zip
DateFormatter: Fix exception if user date option is not available
If the user's date option is not available in the selected user language, fall back to the site default and then to "dmy", the last being guaranteed to exist since all languages are merged with MessagesEn.php. Add test. Change-Id: I9ccc6ebe747070fec2b80398a8251924c9a28fbc
-rw-r--r--resources/src/mediawiki.DateFormatter/DateFormatter.js33
-rw-r--r--tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js62
2 files changed, 79 insertions, 16 deletions
diff --git a/resources/src/mediawiki.DateFormatter/DateFormatter.js b/resources/src/mediawiki.DateFormatter/DateFormatter.js
index 4281a1e0163c..f67ba0cca381 100644
--- a/resources/src/mediawiki.DateFormatter/DateFormatter.js
+++ b/resources/src/mediawiki.DateFormatter/DateFormatter.js
@@ -595,7 +595,7 @@ class DateFormatter {
* @return {string}
*/
formatInternal( style, type, date ) {
- const formatName = style ? `${ style } ${ type }` : type;
+ const formatName = this.makeValidFormatName( style, type );
const formatter = this.getIntlFormatInternal( formatName );
const pattern = this.formats[ formatName ].pattern;
if ( pattern ) {
@@ -614,6 +614,35 @@ class DateFormatter {
}
/**
+ * Validate a style/type and combine them into a single string, falling
+ * back to the default style if the user style is not available with the
+ * specified type.
+ *
+ * @internal
+ * @ignore
+ *
+ * @param {string|null} style
+ * @param {string} type
+ * @return {string}
+ */
+ makeValidFormatName( style, type ) {
+ if ( !style ) {
+ return type;
+ }
+ // Try the specified style, then the site default style, then "dmy", a
+ // final fallback which should always exist, because localised date
+ // format arrays are merged with English, which has "dmy".
+ for ( const tryStyle of [ style, config.defaultStyle, 'dmy' ] ) {
+ const name = `${ tryStyle } ${ type }`;
+ if ( name in this.formats ) {
+ return name;
+ }
+ }
+ // Perhaps an invalid type, or bad config?
+ throw new Error( `Unable to find a valid date format for "${ style } ${ type }"` );
+ }
+
+ /**
* Format a time/date range with a specified style
*
* @internal
@@ -626,7 +655,7 @@ class DateFormatter {
* @return {string}
*/
formatRangeInternal( style, type, date1, date2 ) {
- const formatName = style ? `${ style } ${ type }` : type;
+ const formatName = this.makeValidFormatName( style, type );
const formatter = this.getIntlFormatInternal( formatName );
const pattern = this.formats[ formatName ].rangePattern;
if ( pattern ) {
diff --git a/tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js b/tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js
index be6efbf412b6..a0cda3528b20 100644
--- a/tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js
+++ b/tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js
@@ -3,17 +3,19 @@ const midnightZulu = new Date( '2025-01-01T00:00:00Z' );
const oneZulu = new Date( '2025-01-01T01:00:00Z' );
const nextDay = new Date( '2025-01-02T00:00:00Z' );
-function fakeOptionsGet( key, fallback ) {
- const options = {
- timecorrection: 'Offset|60'
- };
- return key in options ? options[ key ] : fallback;
-}
-
QUnit.module( 'mediawiki.DateFormatter static functions', ( hooks ) => {
+ let userOptions;
+
+ function fakeOptionsGet( key, fallback ) {
+ return key in userOptions ? userOptions[ key ] : fallback;
+ }
hooks.beforeEach( function () {
+ userOptions = {
+ timecorrection: 'Offset|60'
+ };
this.sandbox.stub( mw.user.options, 'get', fakeOptionsGet );
+ DateFormatter.clearInstanceCache();
} );
QUnit.test( 'forUser', ( assert ) => {
@@ -41,13 +43,36 @@ QUnit.module( 'mediawiki.DateFormatter static functions', ( hooks ) => {
assert.strictEqual( instance.formatTime( midnightZulu ), '04:00' );
} );
- QUnit.test( 'formatTimeAndDate', ( assert ) => {
- const { formatTimeAndDate } = DateFormatter;
- assert.strictEqual(
- formatTimeAndDate( midnightZulu ),
- '01:00, 1 (january) 2025'
- );
- } );
+ const formatTimeAndDateCases = [
+ {
+ title: 'null',
+ dateOption: null,
+ expected: '01:00, 1 (january) 2025'
+ },
+ {
+ title: 'mdy',
+ dateOption: 'mdy',
+ expected: '01:00, (january) 1, 2025'
+ },
+ {
+ title: 'bad option',
+ dateOption: 'bad',
+ expected: '01:00, 1 (january) 2025'
+ }
+ ];
+
+ QUnit.test.each(
+ 'formatTimeAndDate',
+ formatTimeAndDateCases,
+ ( assert, { dateOption, expected } ) => {
+ userOptions.date = dateOption;
+ const { formatTimeAndDate } = DateFormatter;
+ assert.strictEqual(
+ formatTimeAndDate( midnightZulu ),
+ expected
+ );
+ }
+ );
QUnit.test( 'formatTime', ( assert ) => {
const { formatTime } = DateFormatter;
@@ -188,9 +213,18 @@ QUnit.module( 'mediawiki.DateFormatter static functions', ( hooks ) => {
} );
QUnit.module( 'mediawiki.DateFormatter instance methods', ( hooks ) => {
+ let userOptions;
+
+ function fakeOptionsGet( key, fallback ) {
+ userOptions = {
+ timecorrection: 'Offset|60'
+ };
+ return key in userOptions ? userOptions[ key ] : fallback;
+ }
hooks.beforeEach( function () {
this.sandbox.stub( mw.user.options, 'get', fakeOptionsGet );
+ DateFormatter.clearInstanceCache();
} );
function getInstance() {