4 * Provides various methods needed for formatting dates and times. This
5 * implementation implments the [Discordian calendar][1], mainly for testing with
6 * something very different from the usual Gregorian calendar.
8 * Being intended mainly for testing, niceties like i18n and better
9 * configurability have been omitted.
11 * [1]: https://en.wikipedia.org/wiki/Discordian_calendar
14 * @extends mw.widgets.datetime.DateTimeFormatter
17 * @param {Object} [config] Configuration options
19 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
= function MwWidgetsDatetimeDiscordianDateTimeFormatter( config
) {
20 config
= $.extend( {}, config
);
23 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
[ 'super' ].call( this, config
);
28 OO
.inheritClass( mw
.widgets
.datetime
.DiscordianDateTimeFormatter
, mw
.widgets
.datetime
.DateTimeFormatter
);
35 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.static.formats
= {
36 '@time': '${hour|0}:${minute|0}:${second|0}',
37 '@date': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#}',
38 '@datetime': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}',
39 '@default': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}'
47 * Additional fields implemented here are:
48 * - ${year|#}: Year as a number
49 * - ${season|#}: Season as a number
50 * - ${season|full}: Season as a string
51 * - ${day|#}: Day of the month as a number
52 * - ${day|0}: Day of the month as a number with leading 0
53 * - ${dow|full}: Day of the week as a string
54 * - ${hour|#}: Hour as a number
55 * - ${hour|0}: Hour as a number with leading 0
56 * - ${minute|#}: Minute as a number
57 * - ${minute|0}: Minute as a number with leading 0
58 * - ${second|#}: Second as a number
59 * - ${second|0}: Second as a number with leading 0
60 * - ${millisecond|#}: Millisecond as a number
61 * - ${millisecond|0}: Millisecond as a number, zero-padded to 3 digits
63 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getFieldForTag = function ( tag
, params
) {
66 switch ( tag
+ '|' + params
[ 0 ] ) {
70 calendarComponent
: true,
80 calendarComponent
: true,
83 intercalarySize
: { 1: 0 },
91 calendarComponent
: true,
93 intercalarySize
: { 1: 0 },
107 calendarComponent
: true,
110 intercalarySize
: { 1: 0 },
116 3: 'Prickle-Prickle',
126 calendarComponent
: true,
129 intercalarySize
: { 1: 13 },
130 zeropad
: params
[ 0 ] === '0',
131 formatValue: function ( v
) {
133 return 'St. Tib\'s Day';
135 return mw
.widgets
.datetime
.DateTimeFormatter
.prototype.formatSpecValue
.call( this, v
);
137 parseValue: function ( v
) {
138 if ( /^\s*(st.?\s*)?tib('?s)?(\s*day)?\s*$/i.test( v
) ) {
141 return mw
.widgets
.datetime
.DateTimeFormatter
.prototype.parseSpecValue
.call( this, v
);
153 component
: tag
.charAt( 0 ).toUpperCase() + tag
.slice( 1 ),
154 calendarComponent
: false,
157 zeropad
: params
[ 0 ] === '0'
161 case 'millisecond|#':
162 case 'millisecond|0':
164 component
: 'Millisecond',
165 calendarComponent
: false,
168 zeropad
: params
[ 0 ] === '0'
173 return mw
.widgets
.datetime
.DiscordianDateTimeFormatter
[ 'super' ].prototype.getFieldForTag
.call( this, tag
, params
);
177 if ( spec
.editable
=== undefined ) {
178 spec
.editable
= true;
180 if ( spec
.component
!== 'Day' ) {
181 spec
.formatValue
= this.formatSpecValue
;
182 spec
.parseValue
= this.parseSpecValue
;
185 spec
.size
= Math
.max
.apply(
186 null, $.map( spec
.values
, function ( v
) { return v
.length
; } )
195 * Get components from a Date object
199 * - Season {number} 1-5
200 * - Day {number|string} 1-73 or 'tib'
201 * - DOW {number} 0-4, or -1 on St. Tib's Day
202 * - Hour {number} 0-23
203 * - Minute {number} 0-59
204 * - Second {number} 0-59
205 * - Millisecond {number} 0-999
206 * - intercalary {string} '1' on St. Tib's Day
208 * @param {Date|null} date
209 * @return {Object} Components
211 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getComponentsFromDate = function ( date
) {
213 monthDays
= [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
215 if ( !( date
instanceof Date
) ) {
216 date
= this.defaultDate
;
220 day
= date
.getDate();
221 month
= date
.getMonth();
223 Year
: date
.getFullYear() + 1166,
224 Hour
: date
.getHours(),
225 Minute
: date
.getMinutes(),
226 Second
: date
.getSeconds(),
227 Millisecond
: date
.getMilliseconds(),
228 zone
: date
.getTimezoneOffset()
231 day
= date
.getUTCDate();
232 month
= date
.getUTCMonth();
234 Year
: date
.getUTCFullYear() + 1166,
235 Hour
: date
.getUTCHours(),
236 Minute
: date
.getUTCMinutes(),
237 Second
: date
.getUTCSeconds(),
238 Millisecond
: date
.getUTCMilliseconds(),
243 if ( month
=== 1 && day
=== 29 ) {
247 ret
.intercalary
= '1';
249 day
= monthDays
[ month
] + day
- 1;
250 ret
.Season
= Math
.floor( day
/ 73 ) + 1;
251 ret
.Day
= ( day
% 73 ) + 1;
253 ret
.intercalary
= '';
262 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.adjustComponent = function ( date
, component
, delta
, mode
) {
263 return this.getDateFromComponents(
264 this.adjustComponentInternal(
265 this.getComponentsFromDate( date
), component
, delta
, mode
271 * Adjust the components directly
274 * @param {Object} components Modified in place
275 * @param {string} component
276 * @param {number} delta
277 * @param {string} mode
278 * @return {Object} components
280 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.adjustComponentInternal = function ( components
, component
, delta
, mode
) {
281 var i
, min
, max
, range
, next
, preTib
, postTib
, wasTib
;
287 switch ( component
) {
299 if ( components
.Day
=== 'tib' ) {
300 components
.Day
= 59; // Could choose either one...
301 components
.Season
= 1;
342 if ( component
=== 'Day' ) {
343 i
= Math
.abs( delta
);
344 delta
= delta
< 0 ? -1 : 1;
345 preTib
= delta
> 0 ? 59 : 60;
346 postTib
= delta
> 0 ? 60 : 59;
348 if ( components
.Day
=== preTib
&& components
.Season
=== 1 && this.isLeapYear( components
.Year
) ) {
349 components
.Day
= 'tib';
350 } else if ( components
.Day
=== 'tib' ) {
351 components
.Day
= postTib
;
352 components
.Season
= 1;
354 components
.Day
+= delta
;
355 if ( components
.Day
< min
) {
358 components
.Day
= max
;
359 this.adjustComponentInternal( components
, 'Season', -1, mode
);
362 components
.Day
= max
;
365 components
.Day
= min
;
370 if ( components
.Day
> max
) {
373 components
.Day
= min
;
374 this.adjustComponentInternal( components
, 'Season', 1, mode
);
377 components
.Day
= min
;
380 components
.Day
= max
;
388 if ( component
=== 'Week' ) {
392 if ( components
.Day
=== 'tib' ) {
394 components
.Season
= 1;
398 if ( components
.Day
=== 'tib' && ( component
=== 'Season' || component
=== 'Year' ) ) {
399 components
.Day
= 59; // Could choose either one...
404 i
= Math
.abs( delta
);
405 delta
= delta
< 0 ? -1 : 1;
407 components
[ component
] += delta
;
408 if ( components
[ component
] < min
) {
409 components
[ component
] = max
;
410 components
= this.adjustComponentInternal( components
, next
, -1, mode
);
412 if ( components
[ component
] > max
) {
413 components
[ component
] = min
;
414 components
= this.adjustComponentInternal( components
, next
, 1, mode
);
417 if ( wasTib
&& components
.Season
=== 1 && this.isLeapYear( components
.Year
) ) {
418 components
.Day
= 'tib';
422 range
= max
- min
+ 1;
423 components
[ component
] += delta
;
424 while ( components
[ component
] < min
) {
425 components
[ component
] += range
;
427 while ( components
[ component
] > max
) {
428 components
[ component
] -= range
;
432 components
[ component
] += delta
;
433 if ( components
[ component
] < min
) {
434 components
[ component
] = min
;
436 if ( components
[ component
] > max
) {
437 components
[ component
] = max
;
441 if ( components
.Day
=== 'tib' &&
442 ( components
.Season
!== 1 || !this.isLeapYear( components
.Year
) )
444 components
.Day
= 59; // Could choose either one...
454 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getDateFromComponents = function ( components
) {
455 var month
, day
, days
,
457 monthDays
= [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ];
459 components
= $.extend( {}, this.getComponentsFromDate( null ), components
);
460 if ( components
.Day
=== 'tib' ) {
464 days
= components
.Season
* 73 + components
.Day
- 74;
466 while ( days
>= monthDays
[ month
+ 1 ] ) {
469 day
= days
- monthDays
[ month
] + 1;
472 if ( components
.zone
) {
473 // Can't just use the constructor because that's stupid about ancient years.
474 date
.setFullYear( components
.Year
- 1166, month
, day
);
475 date
.setHours( components
.Hour
, components
.Minute
, components
.Second
, components
.Millisecond
);
477 // Date.UTC() is stupid about ancient years too.
478 date
.setUTCFullYear( components
.Year
- 1166, month
, day
);
479 date
.setUTCHours( components
.Hour
, components
.Minute
, components
.Second
, components
.Millisecond
);
486 * Get whether the year is a leap year
489 * @param {number} year
492 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.isLeapYear = function ( year
) {
496 } else if ( year
% 100 ) {
499 return ( year
% 400 ) === 0;
505 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getCalendarHeadings = function () {
506 return [ 'SM', 'BT', 'PD', 'PP', null, 'SO' ];
512 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.sameCalendarGrid = function ( date1
, date2
) {
513 var components1
= this.getComponentsFromDate( date1
),
514 components2
= this.getComponentsFromDate( date2
);
516 return components1
.Year
=== components2
.Year
&& components1
.Season
=== components2
.Season
;
522 mw
.widgets
.datetime
.DiscordianDateTimeFormatter
.prototype.getCalendarData = function ( date
) {
523 var dt
, components
, season
, i
, row
,
526 weekComponent
: 'Week',
527 monthComponent
: 'Season'
529 seasons
= [ 'Chaos', 'Discord', 'Confusion', 'Bureaucracy', 'The Aftermath' ],
530 seasonStart
= [ 0, -3, -1, -4, -2 ];
532 if ( !( date
instanceof Date
) ) {
533 date
= this.defaultDate
;
536 components
= this.getComponentsFromDate( date
);
538 season
= components
.Season
;
540 ret
.header
= seasons
[ season
- 1 ] + ' ' + components
.Year
;
542 if ( seasonStart
[ season
- 1 ] ) {
543 this.adjustComponentInternal( components
, 'Day', seasonStart
[ season
- 1 ], 'overflow' );
549 for ( i
= 0; i
< 6; i
++ ) {
550 dt
= this.getDateFromComponents( components
);
552 display
: components
.Day
=== 'tib' ? 'Tib' : String( components
.Day
),
554 extra
: components
.Season
< season
? 'prev' : components
.Season
> season
? 'next' : null
557 this.adjustComponentInternal( components
, 'Day', 1, 'overflow' );
558 if ( components
.Day
!== 'tib' && i
=== 3 ) {
563 ret
.rows
.push( row
);
564 } while ( components
.Season
=== season
);
569 }( jQuery
, mediaWiki
) );