1 /* eslint-disable no-restricted-properties */
5 * Provides various methods needed for formatting dates and times. This
6 * implementation implments the [Discordian calendar][1], mainly for testing with
7 * something very different from the usual Gregorian calendar.
9 * Being intended mainly for testing, niceties like i18n and better
10 * configurability have been omitted.
12 * [1]: https://en.wikipedia.org/wiki/Discordian_calendar
15 * @extends mw.widgets.datetime.DateTimeFormatter
18 * @param {Object} [config] Configuration options
20 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
= function MwWidgetsDatetimeDiscordianDateTimeFormatter( config
) {
21 config
= $.extend( {}, config
);
24 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
[ 'super' ].call( this, config
);
29 OO
.inheritClass( mw
.widgets
.datetime
.DiscordianDateTimeFormatter
, mw
.widgets
.datetime
.DateTimeFormatter
);
36 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.static.formats
= {
37 '@time': '${hour|0}:${minute|0}:${second|0}',
38 '@date': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#}',
39 '@datetime': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}',
40 '@default': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}'
48 * Additional fields implemented here are:
49 * - ${year|#}: Year as a number
50 * - ${season|#}: Season as a number
51 * - ${season|full}: Season as a string
52 * - ${day|#}: Day of the month as a number
53 * - ${day|0}: Day of the month as a number with leading 0
54 * - ${dow|full}: Day of the week as a string
55 * - ${hour|#}: Hour as a number
56 * - ${hour|0}: Hour as a number with leading 0
57 * - ${minute|#}: Minute as a number
58 * - ${minute|0}: Minute as a number with leading 0
59 * - ${second|#}: Second as a number
60 * - ${second|0}: Second as a number with leading 0
61 * - ${millisecond|#}: Millisecond as a number
62 * - ${millisecond|0}: Millisecond as a number, zero-padded to 3 digits
64 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getFieldForTag = function ( tag
, params
) {
67 switch ( tag
+ '|' + params
[ 0 ] ) {
71 calendarComponent
: true,
81 calendarComponent
: true,
84 intercalarySize
: { 1: 0 },
92 calendarComponent
: true,
94 intercalarySize
: { 1: 0 },
108 calendarComponent
: true,
111 intercalarySize
: { 1: 0 },
117 3: 'Prickle-Prickle',
127 calendarComponent
: true,
130 intercalarySize
: { 1: 13 },
131 zeropad
: params
[ 0 ] === '0',
132 formatValue: function ( v
) {
134 return 'St. Tib\'s Day';
136 return mw
.widgets
.datetime
.DateTimeFormatter
.prototype.formatSpecValue
.call( this, v
);
138 parseValue: function ( v
) {
139 if ( /^\s*(st.?\s*)?tib('?s)?(\s*day)?\s*$/i.test( v
) ) {
142 return mw
.widgets
.datetime
.DateTimeFormatter
.prototype.parseSpecValue
.call( this, v
);
154 component
: tag
.charAt( 0 ).toUpperCase() + tag
.slice( 1 ),
155 calendarComponent
: false,
158 zeropad
: params
[ 0 ] === '0'
162 case 'millisecond|#':
163 case 'millisecond|0':
165 component
: 'Millisecond',
166 calendarComponent
: false,
169 zeropad
: params
[ 0 ] === '0'
174 return mw
.widgets
.datetime
.DiscordianDateTimeFormatter
[ 'super' ].prototype.getFieldForTag
.call( this, tag
, params
);
178 if ( spec
.editable
=== undefined ) {
179 spec
.editable
= true;
181 if ( spec
.component
!== 'Day' ) {
182 spec
.formatValue
= this.formatSpecValue
;
183 spec
.parseValue
= this.parseSpecValue
;
186 spec
.size
= Math
.max
.apply(
187 null, $.map( spec
.values
, function ( v
) { return v
.length
; } )
196 * Get components from a Date object
200 * - Season {number} 1-5
201 * - Day {number|string} 1-73 or 'tib'
202 * - DOW {number} 0-4, or -1 on St. Tib's Day
203 * - Hour {number} 0-23
204 * - Minute {number} 0-59
205 * - Second {number} 0-59
206 * - Millisecond {number} 0-999
207 * - intercalary {string} '1' on St. Tib's Day
209 * @param {Date|null} date
210 * @return {Object} Components
212 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getComponentsFromDate = function ( date
) {
214 monthDays
= [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
216 if ( !( date
instanceof Date
) ) {
217 date
= this.defaultDate
;
221 day
= date
.getDate();
222 month
= date
.getMonth();
224 Year
: date
.getFullYear() + 1166,
225 Hour
: date
.getHours(),
226 Minute
: date
.getMinutes(),
227 Second
: date
.getSeconds(),
228 Millisecond
: date
.getMilliseconds(),
229 zone
: date
.getTimezoneOffset()
232 day
= date
.getUTCDate();
233 month
= date
.getUTCMonth();
235 Year
: date
.getUTCFullYear() + 1166,
236 Hour
: date
.getUTCHours(),
237 Minute
: date
.getUTCMinutes(),
238 Second
: date
.getUTCSeconds(),
239 Millisecond
: date
.getUTCMilliseconds(),
244 if ( month
=== 1 && day
=== 29 ) {
248 ret
.intercalary
= '1';
250 day
= monthDays
[ month
] + day
- 1;
251 ret
.Season
= Math
.floor( day
/ 73 ) + 1;
252 ret
.Day
= ( day
% 73 ) + 1;
254 ret
.intercalary
= '';
263 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.adjustComponent = function ( date
, component
, delta
, mode
) {
264 return this.getDateFromComponents(
265 this.adjustComponentInternal(
266 this.getComponentsFromDate( date
), component
, delta
, mode
272 * Adjust the components directly
275 * @param {Object} components Modified in place
276 * @param {string} component
277 * @param {number} delta
278 * @param {string} mode
279 * @return {Object} components
281 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.adjustComponentInternal = function ( components
, component
, delta
, mode
) {
282 var i
, min
, max
, range
, next
, preTib
, postTib
, wasTib
;
288 switch ( component
) {
300 if ( components
.Day
=== 'tib' ) {
301 components
.Day
= 59; // Could choose either one...
302 components
.Season
= 1;
343 if ( component
=== 'Day' ) {
344 i
= Math
.abs( delta
);
345 delta
= delta
< 0 ? -1 : 1;
346 preTib
= delta
> 0 ? 59 : 60;
347 postTib
= delta
> 0 ? 60 : 59;
349 if ( components
.Day
=== preTib
&& components
.Season
=== 1 && this.isLeapYear( components
.Year
) ) {
350 components
.Day
= 'tib';
351 } else if ( components
.Day
=== 'tib' ) {
352 components
.Day
= postTib
;
353 components
.Season
= 1;
355 components
.Day
+= delta
;
356 if ( components
.Day
< min
) {
359 components
.Day
= max
;
360 this.adjustComponentInternal( components
, 'Season', -1, mode
);
363 components
.Day
= max
;
366 components
.Day
= min
;
371 if ( components
.Day
> max
) {
374 components
.Day
= min
;
375 this.adjustComponentInternal( components
, 'Season', 1, mode
);
378 components
.Day
= min
;
381 components
.Day
= max
;
389 if ( component
=== 'Week' ) {
393 if ( components
.Day
=== 'tib' ) {
395 components
.Season
= 1;
399 if ( components
.Day
=== 'tib' && ( component
=== 'Season' || component
=== 'Year' ) ) {
400 components
.Day
= 59; // Could choose either one...
405 i
= Math
.abs( delta
);
406 delta
= delta
< 0 ? -1 : 1;
408 components
[ component
] += delta
;
409 if ( components
[ component
] < min
) {
410 components
[ component
] = max
;
411 components
= this.adjustComponentInternal( components
, next
, -1, mode
);
413 if ( components
[ component
] > max
) {
414 components
[ component
] = min
;
415 components
= this.adjustComponentInternal( components
, next
, 1, mode
);
418 if ( wasTib
&& components
.Season
=== 1 && this.isLeapYear( components
.Year
) ) {
419 components
.Day
= 'tib';
423 range
= max
- min
+ 1;
424 components
[ component
] += delta
;
425 while ( components
[ component
] < min
) {
426 components
[ component
] += range
;
428 while ( components
[ component
] > max
) {
429 components
[ component
] -= range
;
433 components
[ component
] += delta
;
434 if ( components
[ component
] < min
) {
435 components
[ component
] = min
;
437 if ( components
[ component
] > max
) {
438 components
[ component
] = max
;
442 if ( components
.Day
=== 'tib' &&
443 ( components
.Season
!== 1 || !this.isLeapYear( components
.Year
) )
445 components
.Day
= 59; // Could choose either one...
455 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getDateFromComponents = function ( components
) {
456 var month
, day
, days
,
458 monthDays
= [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ];
460 components
= $.extend( {}, this.getComponentsFromDate( null ), components
);
461 if ( components
.Day
=== 'tib' ) {
465 days
= components
.Season
* 73 + components
.Day
- 74;
467 while ( days
>= monthDays
[ month
+ 1 ] ) {
470 day
= days
- monthDays
[ month
] + 1;
473 if ( components
.zone
) {
474 // Can't just use the constructor because that's stupid about ancient years.
475 date
.setFullYear( components
.Year
- 1166, month
, day
);
476 date
.setHours( components
.Hour
, components
.Minute
, components
.Second
, components
.Millisecond
);
478 // Date.UTC() is stupid about ancient years too.
479 date
.setUTCFullYear( components
.Year
- 1166, month
, day
);
480 date
.setUTCHours( components
.Hour
, components
.Minute
, components
.Second
, components
.Millisecond
);
487 * Get whether the year is a leap year
490 * @param {number} year
493 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.isLeapYear = function ( year
) {
497 } else if ( year
% 100 ) {
500 return ( year
% 400 ) === 0;
506 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getCalendarHeadings = function () {
507 return [ 'SM', 'BT', 'PD', 'PP', null, 'SO' ];
513 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.sameCalendarGrid = function ( date1
, date2
) {
514 var components1
= this.getComponentsFromDate( date1
),
515 components2
= this.getComponentsFromDate( date2
);
517 return components1
.Year
=== components2
.Year
&& components1
.Season
=== components2
.Season
;
523 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getCalendarData = function ( date
) {
524 var dt
, components
, season
, i
, row
,
527 weekComponent
: 'Week',
528 monthComponent
: 'Season'
530 seasons
= [ 'Chaos', 'Discord', 'Confusion', 'Bureaucracy', 'The Aftermath' ],
531 seasonStart
= [ 0, -3, -1, -4, -2 ];
533 if ( !( date
instanceof Date
) ) {
534 date
= this.defaultDate
;
537 components
= this.getComponentsFromDate( date
);
539 season
= components
.Season
;
541 ret
.header
= seasons
[ season
- 1 ] + ' ' + components
.Year
;
543 if ( seasonStart
[ season
- 1 ] ) {
544 this.adjustComponentInternal( components
, 'Day', seasonStart
[ season
- 1 ], 'overflow' );
550 for ( i
= 0; i
< 6; i
++ ) {
551 dt
= this.getDateFromComponents( components
);
553 display
: components
.Day
=== 'tib' ? 'Tib' : String( components
.Day
),
555 extra
: components
.Season
< season
? 'prev' : components
.Season
> season
? 'next' : null
558 this.adjustComponentInternal( components
, 'Day', 1, 'overflow' );
559 if ( components
.Day
!== 'tib' && i
=== 3 ) {
564 ret
.rows
.push( row
);
565 } while ( components
.Season
=== season
);
570 }( jQuery
, mediaWiki
) );