summaryrefslogtreecommitdiff
path: root/www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js')
-rw-r--r--www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js569
1 files changed, 569 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js b/www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js
new file mode 100644
index 00000000..6db2d062
--- /dev/null
+++ b/www/wiki/resources/src/mediawiki.widgets.datetime/DiscordianDateTimeFormatter.js
@@ -0,0 +1,569 @@
+( function ( $, mw ) {
+
+ /**
+ * Provides various methods needed for formatting dates and times. This
+ * implementation implments the [Discordian calendar][1], mainly for testing with
+ * something very different from the usual Gregorian calendar.
+ *
+ * Being intended mainly for testing, niceties like i18n and better
+ * configurability have been omitted.
+ *
+ * [1]: https://en.wikipedia.org/wiki/Discordian_calendar
+ *
+ * @class
+ * @extends mw.widgets.datetime.DateTimeFormatter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter = function MwWidgetsDatetimeDiscordianDateTimeFormatter( config ) {
+ config = $.extend( {}, config );
+
+ // Parent constructor
+ mw.widgets.datetime.DiscordianDateTimeFormatter[ 'super' ].call( this, config );
+ };
+
+ /* Setup */
+
+ OO.inheritClass( mw.widgets.datetime.DiscordianDateTimeFormatter, mw.widgets.datetime.DateTimeFormatter );
+
+ /* Static */
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.static.formats = {
+ '@time': '${hour|0}:${minute|0}:${second|0}',
+ '@date': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#}',
+ '@datetime': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}',
+ '@default': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}'
+ };
+
+ /* Methods */
+
+ /**
+ * @inheritdoc
+ *
+ * Additional fields implemented here are:
+ * - ${year|#}: Year as a number
+ * - ${season|#}: Season as a number
+ * - ${season|full}: Season as a string
+ * - ${day|#}: Day of the month as a number
+ * - ${day|0}: Day of the month as a number with leading 0
+ * - ${dow|full}: Day of the week as a string
+ * - ${hour|#}: Hour as a number
+ * - ${hour|0}: Hour as a number with leading 0
+ * - ${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.DiscordianDateTimeFormatter.prototype.getFieldForTag = function ( tag, params ) {
+ var spec = null;
+
+ switch ( tag + '|' + params[ 0 ] ) {
+ case 'year|#':
+ spec = {
+ component: 'Year',
+ calendarComponent: true,
+ type: 'number',
+ size: 4,
+ zeropad: false
+ };
+ break;
+
+ case 'season|#':
+ spec = {
+ component: 'Season',
+ calendarComponent: true,
+ type: 'number',
+ size: 1,
+ intercalarySize: { 1: 0 },
+ zeropad: false
+ };
+ break;
+
+ case 'season|full':
+ spec = {
+ component: 'Season',
+ calendarComponent: true,
+ type: 'string',
+ intercalarySize: { 1: 0 },
+ values: {
+ 1: 'Chaos',
+ 2: 'Discord',
+ 3: 'Confusion',
+ 4: 'Bureaucracy',
+ 5: 'The Aftermath'
+ }
+ };
+ break;
+
+ case 'dow|full':
+ spec = {
+ component: 'DOW',
+ calendarComponent: true,
+ editable: false,
+ type: 'string',
+ intercalarySize: { 1: 0 },
+ values: {
+ '-1': 'N/A',
+ 0: 'Sweetmorn',
+ 1: 'Boomtime',
+ 2: 'Pungenday',
+ 3: 'Prickle-Prickle',
+ 4: 'Setting Orange'
+ }
+ };
+ break;
+
+ case 'day|#':
+ case 'day|0':
+ spec = {
+ component: 'Day',
+ calendarComponent: true,
+ type: 'string',
+ size: 2,
+ intercalarySize: { 1: 13 },
+ zeropad: params[ 0 ] === '0',
+ formatValue: function ( v ) {
+ if ( v === 'tib' ) {
+ return 'St. Tib\'s Day';
+ }
+ return mw.widgets.datetime.DateTimeFormatter.prototype.formatSpecValue.call( this, v );
+ },
+ parseValue: function ( v ) {
+ if ( /^\s*(st.?\s*)?tib('?s)?(\s*day)?\s*$/i.test( v ) ) {
+ return 'tib';
+ }
+ return mw.widgets.datetime.DateTimeFormatter.prototype.parseSpecValue.call( this, v );
+ }
+ };
+ break;
+
+ case 'hour|#':
+ case 'hour|0':
+ case 'minute|#':
+ case 'minute|0':
+ case 'second|#':
+ case 'second|0':
+ spec = {
+ component: tag.charAt( 0 ).toUpperCase() + tag.slice( 1 ),
+ calendarComponent: false,
+ type: 'number',
+ size: 2,
+ zeropad: params[ 0 ] === '0'
+ };
+ 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.DiscordianDateTimeFormatter[ 'super' ].prototype.getFieldForTag.call( this, tag, params );
+ }
+
+ if ( spec ) {
+ if ( spec.editable === undefined ) {
+ spec.editable = true;
+ }
+ if ( spec.component !== 'Day' ) {
+ 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}
+ * - Season {number} 1-5
+ * - Day {number|string} 1-73 or 'tib'
+ * - DOW {number} 0-4, or -1 on St. Tib's Day
+ * - Hour {number} 0-23
+ * - Minute {number} 0-59
+ * - Second {number} 0-59
+ * - Millisecond {number} 0-999
+ * - intercalary {string} '1' on St. Tib's Day
+ *
+ * @param {Date|null} date
+ * @return {Object} Components
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getComponentsFromDate = function ( date ) {
+ var ret, day, month,
+ monthDays = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
+
+ if ( !( date instanceof Date ) ) {
+ date = this.defaultDate;
+ }
+
+ if ( this.local ) {
+ day = date.getDate();
+ month = date.getMonth();
+ ret = {
+ Year: date.getFullYear() + 1166,
+ Hour: date.getHours(),
+ Minute: date.getMinutes(),
+ Second: date.getSeconds(),
+ Millisecond: date.getMilliseconds(),
+ zone: date.getTimezoneOffset()
+ };
+ } else {
+ day = date.getUTCDate();
+ month = date.getUTCMonth();
+ ret = {
+ Year: date.getUTCFullYear() + 1166,
+ Hour: date.getUTCHours(),
+ Minute: date.getUTCMinutes(),
+ Second: date.getUTCSeconds(),
+ Millisecond: date.getUTCMilliseconds(),
+ zone: 0
+ };
+ }
+
+ if ( month === 1 && day === 29 ) {
+ ret.Season = 1;
+ ret.Day = 'tib';
+ ret.DOW = -1;
+ ret.intercalary = '1';
+ } else {
+ day = monthDays[ month ] + day - 1;
+ ret.Season = Math.floor( day / 73 ) + 1;
+ ret.Day = ( day % 73 ) + 1;
+ ret.DOW = day % 5;
+ ret.intercalary = '';
+ }
+
+ return ret;
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.adjustComponent = function ( date, component, delta, mode ) {
+ return this.getDateFromComponents(
+ this.adjustComponentInternal(
+ this.getComponentsFromDate( date ), component, delta, mode
+ )
+ );
+ };
+
+ /**
+ * Adjust the components directly
+ *
+ * @private
+ * @param {Object} components Modified in place
+ * @param {string} component
+ * @param {number} delta
+ * @param {string} mode
+ * @return {Object} components
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.adjustComponentInternal = function ( components, component, delta, mode ) {
+ var i, min, max, range, next, preTib, postTib, wasTib;
+
+ if ( delta === 0 ) {
+ return components;
+ }
+
+ switch ( component ) {
+ case 'Year':
+ min = 1166;
+ max = 11165;
+ next = null;
+ break;
+ case 'Season':
+ min = 1;
+ max = 5;
+ next = 'Year';
+ break;
+ case 'Week':
+ if ( components.Day === 'tib' ) {
+ components.Day = 59; // Could choose either one...
+ components.Season = 1;
+ }
+ min = 1;
+ max = 73;
+ next = 'Season';
+ break;
+ case 'Day':
+ min = 1;
+ max = 73;
+ next = 'Season';
+ break;
+ case 'Hour':
+ min = 0;
+ max = 23;
+ next = 'Day';
+ break;
+ case 'Minute':
+ min = 0;
+ max = 59;
+ next = 'Hour';
+ break;
+ case 'Second':
+ min = 0;
+ max = 59;
+ next = 'Minute';
+ break;
+ case 'Millisecond':
+ min = 0;
+ max = 999;
+ next = 'Second';
+ break;
+ default:
+ return components;
+ }
+
+ switch ( mode ) {
+ case 'overflow':
+ case 'clip':
+ case 'wrap':
+ }
+
+ if ( component === 'Day' ) {
+ i = Math.abs( delta );
+ delta = delta < 0 ? -1 : 1;
+ preTib = delta > 0 ? 59 : 60;
+ postTib = delta > 0 ? 60 : 59;
+ while ( i-- > 0 ) {
+ if ( components.Day === preTib && components.Season === 1 && this.isLeapYear( components.Year ) ) {
+ components.Day = 'tib';
+ } else if ( components.Day === 'tib' ) {
+ components.Day = postTib;
+ components.Season = 1;
+ } else {
+ components.Day += delta;
+ if ( components.Day < min ) {
+ switch ( mode ) {
+ case 'overflow':
+ components.Day = max;
+ this.adjustComponentInternal( components, 'Season', -1, mode );
+ break;
+ case 'wrap':
+ components.Day = max;
+ break;
+ case 'clip':
+ components.Day = min;
+ i = 0;
+ break;
+ }
+ }
+ if ( components.Day > max ) {
+ switch ( mode ) {
+ case 'overflow':
+ components.Day = min;
+ this.adjustComponentInternal( components, 'Season', 1, mode );
+ break;
+ case 'wrap':
+ components.Day = min;
+ break;
+ case 'clip':
+ components.Day = max;
+ i = 0;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ if ( component === 'Week' ) {
+ component = 'Day';
+ delta *= 5;
+ }
+ if ( components.Day === 'tib' ) {
+ // For sanity
+ components.Season = 1;
+ }
+ switch ( mode ) {
+ case 'overflow':
+ if ( components.Day === 'tib' && ( component === 'Season' || component === 'Year' ) ) {
+ components.Day = 59; // Could choose either one...
+ wasTib = true;
+ } else {
+ wasTib = false;
+ }
+ i = Math.abs( delta );
+ delta = delta < 0 ? -1 : 1;
+ while ( i-- > 0 ) {
+ components[ component ] += delta;
+ if ( components[ component ] < min ) {
+ components[ component ] = max;
+ components = this.adjustComponentInternal( components, next, -1, mode );
+ }
+ if ( components[ component ] > max ) {
+ components[ component ] = min;
+ components = this.adjustComponentInternal( components, next, 1, mode );
+ }
+ }
+ if ( wasTib && components.Season === 1 && this.isLeapYear( components.Year ) ) {
+ components.Day = 'tib';
+ }
+ break;
+ case 'wrap':
+ range = max - min + 1;
+ components[ component ] += delta;
+ while ( components[ component ] < min ) {
+ components[ component ] += range;
+ }
+ while ( components[ component ] > max ) {
+ components[ component ] -= range;
+ }
+ break;
+ case 'clip':
+ components[ component ] += delta;
+ if ( components[ component ] < min ) {
+ components[ component ] = min;
+ }
+ if ( components[ component ] > max ) {
+ components[ component ] = max;
+ }
+ break;
+ }
+ if ( components.Day === 'tib' &&
+ ( components.Season !== 1 || !this.isLeapYear( components.Year ) )
+ ) {
+ components.Day = 59; // Could choose either one...
+ }
+ }
+
+ return components;
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getDateFromComponents = function ( components ) {
+ var month, day, days,
+ date = new Date(),
+ monthDays = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ];
+
+ components = $.extend( {}, this.getComponentsFromDate( null ), components );
+ if ( components.Day === 'tib' ) {
+ month = 1;
+ day = 29;
+ } else {
+ days = components.Season * 73 + components.Day - 74;
+ month = 0;
+ while ( days >= monthDays[ month + 1 ] ) {
+ month++;
+ }
+ day = days - monthDays[ month ] + 1;
+ }
+
+ if ( components.zone ) {
+ // Can't just use the constructor because that's stupid about ancient years.
+ date.setFullYear( components.Year - 1166, month, day );
+ date.setHours( components.Hour, components.Minute, components.Second, components.Millisecond );
+ } else {
+ // Date.UTC() is stupid about ancient years too.
+ date.setUTCFullYear( components.Year - 1166, month, day );
+ date.setUTCHours( components.Hour, components.Minute, components.Second, components.Millisecond );
+ }
+
+ return date;
+ };
+
+ /**
+ * Get whether the year is a leap year
+ *
+ * @private
+ * @param {number} year
+ * @return {boolean}
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.isLeapYear = function ( year ) {
+ year -= 1166;
+ if ( year % 4 ) {
+ return false;
+ } else if ( year % 100 ) {
+ return true;
+ }
+ return ( year % 400 ) === 0;
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getCalendarHeadings = function () {
+ return [ 'SM', 'BT', 'PD', 'PP', null, 'SO' ];
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.sameCalendarGrid = function ( date1, date2 ) {
+ var components1 = this.getComponentsFromDate( date1 ),
+ components2 = this.getComponentsFromDate( date2 );
+
+ return components1.Year === components2.Year && components1.Season === components2.Season;
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getCalendarData = function ( date ) {
+ var dt, components, season, i, row,
+ ret = {
+ dayComponent: 'Day',
+ weekComponent: 'Week',
+ monthComponent: 'Season'
+ },
+ seasons = [ 'Chaos', 'Discord', 'Confusion', 'Bureaucracy', 'The Aftermath' ],
+ seasonStart = [ 0, -3, -1, -4, -2 ];
+
+ if ( !( date instanceof Date ) ) {
+ date = this.defaultDate;
+ }
+
+ components = this.getComponentsFromDate( date );
+ components.Day = 1;
+ season = components.Season;
+
+ ret.header = seasons[ season - 1 ] + ' ' + components.Year;
+
+ if ( seasonStart[ season - 1 ] ) {
+ this.adjustComponentInternal( components, 'Day', seasonStart[ season - 1 ], 'overflow' );
+ }
+
+ ret.rows = [];
+ do {
+ row = [];
+ for ( i = 0; i < 6; i++ ) {
+ dt = this.getDateFromComponents( components );
+ row[ i ] = {
+ display: components.Day === 'tib' ? 'Tib' : String( components.Day ),
+ date: dt,
+ extra: components.Season < season ? 'prev' : components.Season > season ? 'next' : null
+ };
+
+ this.adjustComponentInternal( components, 'Day', 1, 'overflow' );
+ if ( components.Day !== 'tib' && i === 3 ) {
+ row[ ++i ] = null;
+ }
+ }
+
+ ret.rows.push( row );
+ } while ( components.Season === season );
+
+ return ret;
+ };
+
+}( jQuery, mediaWiki ) );