diff options
author | Tim Starling <tstarling@wikimedia.org> | 2025-04-04 09:56:00 +1100 |
---|---|---|
committer | Tim Starling <tstarling@wikimedia.org> | 2025-04-07 11:29:15 +1000 |
commit | bb9f7a62c807b4c186a22fc111de780ddc1de8a9 (patch) | |
tree | 3ce810573c878b33749429f4f28185d319a15d3d | |
parent | 11fd26f8d49f26aeed2a6e5bc2c483dd88fa1472 (diff) | |
download | mediawikicore-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.js | 33 | ||||
-rw-r--r-- | tests/qunit/resources/mediawiki.DateFormatter/DateFormatter.test.js | 62 |
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() { |