diff options
Diffstat (limited to 'www/wiki/resources/src/mediawiki.widgets.datetime/ProlepticGregorianDateTimeFormatter.js')
-rw-r--r-- | www/wiki/resources/src/mediawiki.widgets.datetime/ProlepticGregorianDateTimeFormatter.js | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki.widgets.datetime/ProlepticGregorianDateTimeFormatter.js b/www/wiki/resources/src/mediawiki.widgets.datetime/ProlepticGregorianDateTimeFormatter.js new file mode 100644 index 00000000..0346cc1e --- /dev/null +++ b/www/wiki/resources/src/mediawiki.widgets.datetime/ProlepticGregorianDateTimeFormatter.js @@ -0,0 +1,675 @@ +( function ( $, mw ) { + + /** + * Provides various methods needed for formatting dates and times. This + * implementation implements the proleptic Gregorian calendar over years + * 0000–9999. + * + * @class + * @extends mw.widgets.datetime.DateTimeFormatter + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {Object} [fullMonthNames] Mapping 1–12 to full month names. + * @cfg {Object} [shortMonthNames] Mapping 1–12 to abbreviated month names. + * If {@link #fullMonthNames fullMonthNames} is given and this is not, + * defaults to the first three characters from that setting. + * @cfg {Object} [fullDayNames] Mapping 0–6 to full day of week names. 0 is Sunday, 6 is Saturday. + * @cfg {Object} [shortDayNames] Mapping 0–6 to abbreviated day of week names. 0 is Sunday, 6 is Saturday. + * If {@link #fullDayNames fullDayNames} is given and this is not, defaults to + * the first three characters from that setting. + * @cfg {string[]} [dayLetters] Weekday column headers for a calendar. Array of 7 strings. + * If {@link #fullDayNames fullDayNames} or {@link #shortDayNames shortDayNames} + * are given and this is not, defaults to the first character from + * shortDayNames. + * @cfg {string[]} [hour12Periods] AM and PM texts. Array of 2 strings, AM and PM. + * @cfg {number} [weekStartsOn=0] What day the week starts on: 0 is Sunday, 1 is Monday, 6 is Saturday. + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter = function MwWidgetsDatetimeProlepticGregorianDateTimeFormatter( config ) { + this.constructor.static.setupDefaults(); + + config = $.extend( { + weekStartsOn: 0, + hour12Periods: this.constructor.static.hour12Periods + }, config ); + + if ( config.fullMonthNames && !config.shortMonthNames ) { + config.shortMonthNames = {}; + $.each( config.fullMonthNames, function ( k, v ) { + config.shortMonthNames[ k ] = v.substr( 0, 3 ); + } ); + } + if ( config.shortDayNames && !config.dayLetters ) { + config.dayLetters = []; + $.each( config.shortDayNames, function ( k, v ) { + config.dayLetters[ k ] = v.substr( 0, 1 ); + } ); + } + if ( config.fullDayNames && !config.dayLetters ) { + config.dayLetters = []; + $.each( config.fullDayNames, function ( k, v ) { + config.dayLetters[ k ] = v.substr( 0, 1 ); + } ); + } + if ( config.fullDayNames && !config.shortDayNames ) { + config.shortDayNames = {}; + $.each( config.fullDayNames, function ( k, v ) { + config.shortDayNames[ k ] = v.substr( 0, 3 ); + } ); + } + config = $.extend( { + fullMonthNames: this.constructor.static.fullMonthNames, + shortMonthNames: this.constructor.static.shortMonthNames, + fullDayNames: this.constructor.static.fullDayNames, + shortDayNames: this.constructor.static.shortDayNames, + dayLetters: this.constructor.static.dayLetters + }, config ); + + // Parent constructor + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter[ 'super' ].call( this, config ); + + // Properties + this.weekStartsOn = config.weekStartsOn % 7; + this.fullMonthNames = config.fullMonthNames; + this.shortMonthNames = config.shortMonthNames; + this.fullDayNames = config.fullDayNames; + this.shortDayNames = config.shortDayNames; + this.dayLetters = config.dayLetters; + this.hour12Periods = config.hour12Periods; + }; + + /* Setup */ + + OO.inheritClass( mw.widgets.datetime.ProlepticGregorianDateTimeFormatter, mw.widgets.datetime.DateTimeFormatter ); + + /* Static */ + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.formats = { + '@time': '${hour|0}:${minute|0}:${second|0}', + '@date': '$!{dow|short} ${day|#} ${month|short} ${year|#}', + '@datetime': '$!{dow|short} ${day|#} ${month|short} ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}', + '@default': '$!{dow|short} ${day|#} ${month|short} ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}' + }; + + /** + * Default full month names. + * + * @static + * @inheritable + * @property {Object} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.fullMonthNames = null; + + /** + * Default abbreviated month names. + * + * @static + * @inheritable + * @property {Object} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.shortMonthNames = null; + + /** + * Default full day of week names. + * + * @static + * @inheritable + * @property {Object} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.fullDayNames = null; + + /** + * Default abbreviated day of week names. + * + * @static + * @inheritable + * @property {Object} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.shortDayNames = null; + + /** + * Default day letters. + * + * @static + * @inheritable + * @property {string[]} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.dayLetters = null; + + /** + * Default AM/PM indicators + * + * @static + * @inheritable + * @property {string[]} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.hour12Periods = null; + + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.setupDefaults = function () { + mw.widgets.datetime.DateTimeFormatter.static.setupDefaults.call( this ); + + if ( this.fullMonthNames && !this.shortMonthNames ) { + this.shortMonthNames = {}; + $.each( this.fullMonthNames, function ( k, v ) { + this.shortMonthNames[ k ] = v.substr( 0, 3 ); + }.bind( this ) ); + } + if ( this.shortDayNames && !this.dayLetters ) { + this.dayLetters = []; + $.each( this.shortDayNames, function ( k, v ) { + this.dayLetters[ k ] = v.substr( 0, 1 ); + }.bind( this ) ); + } + if ( this.fullDayNames && !this.dayLetters ) { + this.dayLetters = []; + $.each( this.fullDayNames, function ( k, v ) { + this.dayLetters[ k ] = v.substr( 0, 1 ); + }.bind( this ) ); + } + if ( this.fullDayNames && !this.shortDayNames ) { + this.shortDayNames = {}; + $.each( this.fullDayNames, function ( k, v ) { + this.shortDayNames[ k ] = v.substr( 0, 3 ); + }.bind( this ) ); + } + + if ( !this.fullMonthNames ) { + this.fullMonthNames = { + 1: mw.msg( 'january' ), + 2: mw.msg( 'february' ), + 3: mw.msg( 'march' ), + 4: mw.msg( 'april' ), + 5: mw.msg( 'may_long' ), + 6: mw.msg( 'june' ), + 7: mw.msg( 'july' ), + 8: mw.msg( 'august' ), + 9: mw.msg( 'september' ), + 10: mw.msg( 'october' ), + 11: mw.msg( 'november' ), + 12: mw.msg( 'december' ) + }; + } + if ( !this.shortMonthNames ) { + this.shortMonthNames = { + 1: mw.msg( 'jan' ), + 2: mw.msg( 'feb' ), + 3: mw.msg( 'mar' ), + 4: mw.msg( 'apr' ), + 5: mw.msg( 'may' ), + 6: mw.msg( 'jun' ), + 7: mw.msg( 'jul' ), + 8: mw.msg( 'aug' ), + 9: mw.msg( 'sep' ), + 10: mw.msg( 'oct' ), + 11: mw.msg( 'nov' ), + 12: mw.msg( 'dec' ) + }; + } + + if ( !this.fullDayNames ) { + this.fullDayNames = { + 0: mw.msg( 'sunday' ), + 1: mw.msg( 'monday' ), + 2: mw.msg( 'tuesday' ), + 3: mw.msg( 'wednesday' ), + 4: mw.msg( 'thursday' ), + 5: mw.msg( 'friday' ), + 6: mw.msg( 'saturday' ) + }; + } + if ( !this.shortDayNames ) { + this.shortDayNames = { + 0: mw.msg( 'sun' ), + 1: mw.msg( 'mon' ), + 2: mw.msg( 'tue' ), + 3: mw.msg( 'wed' ), + 4: mw.msg( 'thu' ), + 5: mw.msg( 'fri' ), + 6: mw.msg( 'sat' ) + }; + } + if ( !this.dayLetters ) { + this.dayLetters = []; + $.each( this.shortDayNames, function ( k, v ) { + this.dayLetters[ k ] = v.substr( 0, 1 ); + }.bind( this ) ); + } + + if ( !this.hour12Periods ) { + this.hour12Periods = [ + mw.msg( 'period-am' ), + mw.msg( 'period-pm' ) + ]; + } + }; + + /* Methods */ + + /** + * @inheritdoc + * + * Additional fields implemented here are: + * - ${year|#}: Year as a number + * - ${year|0}: Year as a number, zero-padded to 4 digits + * - ${month|#}: Month as a number + * - ${month|0}: Month as a number with leading 0 + * - ${month|short}: Month from 'shortMonthNames' configuration setting + * - ${month|full}: Month from 'fullMonthNames' configuration setting + * - ${day|#}: Day of the month as a number + * - ${day|0}: Day of the month as a number with leading 0 + * - ${dow|short}: Day of the week from 'shortDayNames' configuration setting + * - ${dow|full}: Day of the week from 'fullDayNames' configuration setting + * - ${hour|#}: Hour as a number + * - ${hour|0}: Hour as a number with leading 0 + * - ${hour|12}: Hour in a 12-hour clock as a number + * - ${hour|012}: Hour in a 12-hour clock as a number, with leading 0 + * - ${hour|period}: Value from 'hour12Periods' configuration setting + * - ${minute|#}: Minute as a number + * - ${minute|0}: Minute as a number with leading 0 + * - ${second|#}: Second as a number + * - ${second|0}: Second as a number with leading 0 + * - ${millisecond|#}: Millisecond as a number + * - ${millisecond|0}: Millisecond as a number, zero-padded to 3 digits + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getFieldForTag = function ( tag, params ) { + var spec = null; + + switch ( tag + '|' + params[ 0 ] ) { + case 'year|#': + case 'year|0': + spec = { + component: 'year', + calendarComponent: true, + type: 'number', + size: 4, + zeropad: params[ 0 ] === '0' + }; + break; + + case 'month|short': + case 'month|full': + spec = { + component: 'month', + calendarComponent: true, + type: 'string', + values: params[ 0 ] === 'short' ? this.shortMonthNames : this.fullMonthNames + }; + break; + + case 'dow|short': + case 'dow|full': + spec = { + component: 'dow', + calendarComponent: true, + editable: false, + type: 'string', + values: params[ 0 ] === 'short' ? this.shortDayNames : this.fullDayNames + }; + break; + + case 'month|#': + case 'month|0': + case 'day|#': + case 'day|0': + spec = { + component: tag, + calendarComponent: true, + type: 'number', + size: 2, + zeropad: params[ 0 ] === '0' + }; + break; + + case 'hour|#': + case 'hour|0': + case 'minute|#': + case 'minute|0': + case 'second|#': + case 'second|0': + spec = { + component: tag, + calendarComponent: false, + type: 'number', + size: 2, + zeropad: params[ 0 ] === '0' + }; + break; + + case 'hour|12': + case 'hour|012': + spec = { + component: 'hour12', + calendarComponent: false, + type: 'number', + size: 2, + zeropad: params[ 0 ] === '012' + }; + break; + + case 'hour|period': + spec = { + component: 'hour12period', + calendarComponent: false, + type: 'boolean', + values: this.hour12Periods + }; + break; + + case 'millisecond|#': + case 'millisecond|0': + spec = { + component: 'millisecond', + calendarComponent: false, + type: 'number', + size: 3, + zeropad: params[ 0 ] === '0' + }; + break; + + default: + return mw.widgets.datetime.ProlepticGregorianDateTimeFormatter[ 'super' ].prototype.getFieldForTag.call( this, tag, params ); + } + + if ( spec ) { + if ( spec.editable === undefined ) { + spec.editable = true; + } + spec.formatValue = this.formatSpecValue; + spec.parseValue = this.parseSpecValue; + if ( spec.values ) { + spec.size = Math.max.apply( + null, $.map( spec.values, function ( v ) { return v.length; } ) + ); + } + } + + return spec; + }; + + /** + * Get components from a Date object + * + * Components are: + * - year {number} + * - month {number} (1-12) + * - day {number} (1-31) + * - dow {number} (0-6, 0 is Sunday) + * - hour {number} (0-23) + * - hour12 {number} (1-12) + * - hour12period {boolean} + * - minute {number} (0-59) + * - second {number} (0-59) + * - millisecond {number} (0-999) + * - zone {number} + * + * @param {Date|null} date + * @return {Object} Components + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getComponentsFromDate = function ( date ) { + var ret; + + if ( !( date instanceof Date ) ) { + date = this.defaultDate; + } + + if ( this.local ) { + ret = { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + dow: date.getDay() % 7, + hour: date.getHours(), + minute: date.getMinutes(), + second: date.getSeconds(), + millisecond: date.getMilliseconds(), + zone: date.getTimezoneOffset() + }; + } else { + ret = { + year: date.getUTCFullYear(), + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + dow: date.getUTCDay() % 7, + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds(), + millisecond: date.getUTCMilliseconds(), + zone: 0 + }; + } + + ret.hour12period = ret.hour >= 12 ? 1 : 0; + ret.hour12 = ret.hour % 12; + if ( ret.hour12 === 0 ) { + ret.hour12 = 12; + } + + return ret; + }; + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getDateFromComponents = function ( components ) { + var date = new Date(); + + components = $.extend( {}, components ); + if ( components.hour === undefined && components.hour12 !== undefined && components.hour12period !== undefined ) { + components.hour = ( components.hour12 % 12 ) + ( components.hour12period ? 12 : 0 ); + } + components = $.extend( {}, this.getComponentsFromDate( null ), components ); + + if ( components.zone ) { + // Can't just use the constructor because that's stupid about ancient years. + date.setFullYear( components.year, components.month - 1, components.day ); + date.setHours( components.hour, components.minute, components.second, components.millisecond ); + } else { + // Date.UTC() is stupid about ancient years too. + date.setUTCFullYear( components.year, components.month - 1, components.day ); + date.setUTCHours( components.hour, components.minute, components.second, components.millisecond ); + } + + return date; + }; + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.adjustComponent = function ( date, component, delta, mode ) { + var min, max, range, components; + + if ( !( date instanceof Date ) ) { + date = this.defaultDate; + } + components = this.getComponentsFromDate( date ); + + switch ( component ) { + case 'year': + min = 0; + max = 9999; + break; + case 'month': + min = 1; + max = 12; + break; + case 'day': + min = 1; + max = this.getDaysInMonth( components.month, components.year ); + break; + case 'hour': + min = 0; + max = 23; + break; + case 'minute': + case 'second': + min = 0; + max = 59; + break; + case 'millisecond': + min = 0; + max = 999; + break; + case 'hour12period': + component = 'hour'; + min = 0; + max = 23; + delta *= 12; + break; + case 'hour12': + component = 'hour'; + min = components.hour12period ? 12 : 0; + max = components.hour12period ? 23 : 11; + break; + default: + return new Date( date.getTime() ); + } + + components[ component ] += delta; + range = max - min + 1; + switch ( mode ) { + case 'overflow': + // Date() will mostly handle it automatically. But months need + // manual handling to prevent e.g. Jan 31 => Mar 3. + if ( component === 'month' || component === 'year' ) { + while ( components.month < 1 ) { + components[ component ] += 12; + components.year--; + } + while ( components.month > 12 ) { + components[ component ] -= 12; + components.year++; + } + } + break; + case 'wrap': + while ( components[ component ] < min ) { + components[ component ] += range; + } + while ( components[ component ] > max ) { + components[ component ] -= range; + } + break; + case 'clip': + if ( components[ component ] < min ) { + components[ component ] = min; + } + if ( components[ component ] < max ) { + components[ component ] = max; + } + break; + } + if ( component === 'month' || component === 'year' ) { + components.day = Math.min( components.day, this.getDaysInMonth( components.month, components.year ) ); + } + + return this.getDateFromComponents( components ); + }; + + /** + * Get the number of days in a month + * + * @protected + * @param {number} month + * @param {number} year + * @return {number} + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getDaysInMonth = function ( month, year ) { + switch ( month ) { + case 4: + case 6: + case 9: + case 11: + return 30; + case 2: + if ( year % 4 ) { + return 28; + } else if ( year % 100 ) { + return 29; + } + return ( year % 400 ) ? 28 : 29; + default: + return 31; + } + }; + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getCalendarHeadings = function () { + var a = this.dayLetters; + + if ( this.weekStartsOn ) { + return a.slice( this.weekStartsOn ).concat( a.slice( 0, this.weekStartsOn ) ); + } else { + return a.slice( 0 ); // clone + } + }; + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.sameCalendarGrid = function ( date1, date2 ) { + if ( this.local ) { + return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth(); + } else { + return date1.getUTCFullYear() === date2.getUTCFullYear() && date1.getUTCMonth() === date2.getUTCMonth(); + } + }; + + /** + * @inheritdoc + */ + mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getCalendarData = function ( date ) { + var dt, t, d, e, i, row, + getDate = this.local ? 'getDate' : 'getUTCDate', + setDate = this.local ? 'setDate' : 'setUTCDate', + ret = { + dayComponent: 'day', + monthComponent: 'month' + }; + + if ( !( date instanceof Date ) ) { + date = this.defaultDate; + } + + dt = new Date( date.getTime() ); + dt[ setDate ]( 1 ); + t = dt.getTime(); + + if ( this.local ) { + ret.header = this.fullMonthNames[ dt.getMonth() + 1 ] + ' ' + dt.getFullYear(); + d = dt.getDay() % 7; + e = this.getDaysInMonth( dt.getMonth() + 1, dt.getFullYear() ); + } else { + ret.header = this.fullMonthNames[ dt.getUTCMonth() + 1 ] + ' ' + dt.getUTCFullYear(); + d = dt.getUTCDay() % 7; + e = this.getDaysInMonth( dt.getUTCMonth() + 1, dt.getUTCFullYear() ); + } + + if ( this.weekStartsOn ) { + d = ( d + 7 - this.weekStartsOn ) % 7; + } + d = 1 - d; + + ret.rows = []; + while ( d <= e ) { + row = []; + for ( i = 0; i < 7; i++, d++ ) { + dt = new Date( t ); + dt[ setDate ]( d ); + row[ i ] = { + display: String( dt[ getDate ]() ), + date: dt, + extra: d < 1 ? 'prev' : d > e ? 'next' : null + }; + } + ret.rows.push( row ); + } + + return ret; + }; + +}( jQuery, mediaWiki ) ); |