Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.widgets.datetime / ProlepticGregorianDateTimeFormatter.js
blobd580605b6a276811f1a2215e73d4d6432826a544
1 ( function () {
3         /**
4          * @classdesc DateTimeFormatter for the proleptic Gregorian calendar.
5          *
6          * Provides various methods needed for formatting dates and times. This
7          * implementation implements the proleptic Gregorian calendar over years
8          * 0000–9999.
9          *
10          * @class
11          * @extends mw.widgets.datetime.DateTimeFormatter
12          *
13          * @constructor
14          * @description Create an instance of `mw.widgets.datetime.ProlepticGregorianDateTimeFormatter`.
15          * @param {Object} [config] Configuration options
16          * @param {Object} [config.fullMonthNames] Mapping 1–12 to full month names.
17          * @param {Object} [config.shortMonthNames] Mapping 1–12 to abbreviated month names.
18          *  If {@link #fullMonthNames fullMonthNames} is given and this is not,
19          *  defaults to the first three characters from that setting.
20          * @param {Object} [config.fullDayNames] Mapping 0–6 to full day of week names. 0 is Sunday, 6 is Saturday.
21          * @param {Object} [config.shortDayNames] Mapping 0–6 to abbreviated day of week names. 0 is Sunday, 6 is Saturday.
22          *  If {@link #fullDayNames fullDayNames} is given and this is not, defaults to
23          *  the first three characters from that setting.
24          * @param {string[]} [config.dayLetters] Weekday column headers for a calendar. Array of 7 strings.
25          *  If {@link #fullDayNames fullDayNames} or {@link #shortDayNames shortDayNames}
26          *  are given and this is not, defaults to the first character from
27          *  shortDayNames.
28          * @param {string[]} [config.hour12Periods] AM and PM texts. Array of 2 strings, AM and PM.
29          * @param {number} [config.weekStartsOn=0] What day the week starts on: 0 is Sunday, 1 is Monday, 6 is Saturday.
30          */
31         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter = function MwWidgetsDatetimeProlepticGregorianDateTimeFormatter( config ) {
32                 this.constructor.static.setupDefaults();
34                 config = Object.assign( {
35                         weekStartsOn: 0,
36                         hour12Periods: this.constructor.static.hour12Periods
37                 }, config );
39                 if ( config.fullMonthNames && !config.shortMonthNames ) {
40                         config.shortMonthNames = {};
41                         // eslint-disable-next-line no-jquery/no-each-util
42                         $.each( config.fullMonthNames, ( k, v ) => {
43                                 config.shortMonthNames[ k ] = v.slice( 0, 3 );
44                         } );
45                 }
46                 if ( config.shortDayNames && !config.dayLetters ) {
47                         config.dayLetters = [];
48                         // eslint-disable-next-line no-jquery/no-each-util
49                         $.each( config.shortDayNames, ( k, v ) => {
50                                 config.dayLetters[ k ] = v.slice( 0, 1 );
51                         } );
52                 }
53                 if ( config.fullDayNames && !config.dayLetters ) {
54                         config.dayLetters = [];
55                         // eslint-disable-next-line no-jquery/no-each-util
56                         $.each( config.fullDayNames, ( k, v ) => {
57                                 config.dayLetters[ k ] = v.slice( 0, 1 );
58                         } );
59                 }
60                 if ( config.fullDayNames && !config.shortDayNames ) {
61                         config.shortDayNames = {};
62                         // eslint-disable-next-line no-jquery/no-each-util
63                         $.each( config.fullDayNames, ( k, v ) => {
64                                 config.shortDayNames[ k ] = v.slice( 0, 3 );
65                         } );
66                 }
67                 config = Object.assign( {
68                         fullMonthNames: this.constructor.static.fullMonthNames,
69                         shortMonthNames: this.constructor.static.shortMonthNames,
70                         fullDayNames: this.constructor.static.fullDayNames,
71                         shortDayNames: this.constructor.static.shortDayNames,
72                         dayLetters: this.constructor.static.dayLetters
73                 }, config );
75                 // Parent constructor
76                 mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.super.call( this, config );
78                 // Properties
79                 this.weekStartsOn = config.weekStartsOn % 7;
80                 this.fullMonthNames = config.fullMonthNames;
81                 this.shortMonthNames = config.shortMonthNames;
82                 this.fullDayNames = config.fullDayNames;
83                 this.shortDayNames = config.shortDayNames;
84                 this.dayLetters = config.dayLetters;
85                 this.hour12Periods = config.hour12Periods;
86         };
88         /* Setup */
90         OO.inheritClass( mw.widgets.datetime.ProlepticGregorianDateTimeFormatter, mw.widgets.datetime.DateTimeFormatter );
92         /* Static */
94         /**
95          * Default format specifications.
96          *
97          * See the `format` parameter in {@link mw.widgets.datetime.DateTimeFormatter}.
98          *
99          * @memberof mw.widgets.datetime.ProlepticGregorianDateTimeFormatter
100          * @type {Object.<string,string>}
101          * @name formats
102          */
103         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.formats = {
104                 '@time': '${hour|0}:${minute|0}:${second|0}',
105                 '@date': '$!{dow|short} ${day|#} ${month|short} ${year|#}',
106                 '@datetime': '$!{dow|short} ${day|#} ${month|short} ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}',
107                 '@default': '$!{dow|short} ${day|#} ${month|short} ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}'
108         };
110         /**
111          * Default full month names.
112          *
113          * @static
114          * @inheritable
115          * @type {Object}
116          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.fullMonthNames
117          */
118         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.fullMonthNames = null;
120         /**
121          * Default abbreviated month names.
122          *
123          * @static
124          * @inheritable
125          * @type {Object}
126          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.shortMonthNames
127          */
128         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.shortMonthNames = null;
130         /**
131          * Default full day of week names.
132          *
133          * @static
134          * @inheritable
135          * @type {Object}
136          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.fullDayNames
137          */
138         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.fullDayNames = null;
140         /**
141          * Default abbreviated day of week names.
142          *
143          * @static
144          * @inheritable
145          * @type {Object}
146          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.shortDayNames
147          */
148         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.shortDayNames = null;
150         /**
151          * Default day letters.
152          *
153          * @static
154          * @inheritable
155          * @type {string[]}
156          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.dayLetters
157          */
158         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.dayLetters = null;
160         /**
161          * Default AM/PM indicators.
162          *
163          * @static
164          * @inheritable
165          * @type {string[]}
166          * @name mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.hour12Periods
167          */
168         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.hour12Periods = null;
170         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.static.setupDefaults = function () {
171                 mw.widgets.datetime.DateTimeFormatter.static.setupDefaults.call( this );
173                 if ( this.fullMonthNames && !this.shortMonthNames ) {
174                         this.shortMonthNames = {};
175                         // eslint-disable-next-line no-jquery/no-each-util
176                         $.each( this.fullMonthNames, ( k, v ) => {
177                                 this.shortMonthNames[ k ] = v.slice( 0, 3 );
178                         } );
179                 }
180                 if ( this.shortDayNames && !this.dayLetters ) {
181                         this.dayLetters = [];
182                         // eslint-disable-next-line no-jquery/no-each-util
183                         $.each( this.shortDayNames, ( k, v ) => {
184                                 this.dayLetters[ k ] = v.slice( 0, 1 );
185                         } );
186                 }
187                 if ( this.fullDayNames && !this.dayLetters ) {
188                         this.dayLetters = [];
189                         // eslint-disable-next-line no-jquery/no-each-util
190                         $.each( this.fullDayNames, ( k, v ) => {
191                                 this.dayLetters[ k ] = v.slice( 0, 1 );
192                         } );
193                 }
194                 if ( this.fullDayNames && !this.shortDayNames ) {
195                         this.shortDayNames = {};
196                         // eslint-disable-next-line no-jquery/no-each-util
197                         $.each( this.fullDayNames, ( k, v ) => {
198                                 this.shortDayNames[ k ] = v.slice( 0, 3 );
199                         } );
200                 }
202                 if ( !this.fullMonthNames ) {
203                         this.fullMonthNames = {
204                                 1: mw.msg( 'january' ),
205                                 2: mw.msg( 'february' ),
206                                 3: mw.msg( 'march' ),
207                                 4: mw.msg( 'april' ),
208                                 5: mw.msg( 'may_long' ),
209                                 6: mw.msg( 'june' ),
210                                 7: mw.msg( 'july' ),
211                                 8: mw.msg( 'august' ),
212                                 9: mw.msg( 'september' ),
213                                 10: mw.msg( 'october' ),
214                                 11: mw.msg( 'november' ),
215                                 12: mw.msg( 'december' )
216                         };
217                 }
218                 if ( !this.shortMonthNames ) {
219                         this.shortMonthNames = {
220                                 1: mw.msg( 'jan' ),
221                                 2: mw.msg( 'feb' ),
222                                 3: mw.msg( 'mar' ),
223                                 4: mw.msg( 'apr' ),
224                                 5: mw.msg( 'may' ),
225                                 6: mw.msg( 'jun' ),
226                                 7: mw.msg( 'jul' ),
227                                 8: mw.msg( 'aug' ),
228                                 9: mw.msg( 'sep' ),
229                                 10: mw.msg( 'oct' ),
230                                 11: mw.msg( 'nov' ),
231                                 12: mw.msg( 'dec' )
232                         };
233                 }
235                 if ( !this.fullDayNames ) {
236                         this.fullDayNames = {
237                                 0: mw.msg( 'sunday' ),
238                                 1: mw.msg( 'monday' ),
239                                 2: mw.msg( 'tuesday' ),
240                                 3: mw.msg( 'wednesday' ),
241                                 4: mw.msg( 'thursday' ),
242                                 5: mw.msg( 'friday' ),
243                                 6: mw.msg( 'saturday' )
244                         };
245                 }
246                 if ( !this.shortDayNames ) {
247                         this.shortDayNames = {
248                                 0: mw.msg( 'sun' ),
249                                 1: mw.msg( 'mon' ),
250                                 2: mw.msg( 'tue' ),
251                                 3: mw.msg( 'wed' ),
252                                 4: mw.msg( 'thu' ),
253                                 5: mw.msg( 'fri' ),
254                                 6: mw.msg( 'sat' )
255                         };
256                 }
257                 if ( !this.dayLetters ) {
258                         const dayLetters = [];
259                         const shortDayNames = this.shortDayNames;
260                         for ( const dayOfWeek in shortDayNames ) {
261                                 const shortDayName = shortDayNames[ dayOfWeek ];
262                                 dayLetters[ dayOfWeek ] = shortDayName.slice( 0, 1 );
263                         }
264                         this.dayLetters = dayLetters;
265                 }
267                 if ( !this.hour12Periods ) {
268                         this.hour12Periods = [
269                                 mw.msg( 'period-am' ),
270                                 mw.msg( 'period-pm' )
271                         ];
272                 }
273         };
275         /* Methods */
277         /**
278          * Turn a tag into a field specification object.
279          *
280          * Additional fields implemented here are:
281          * - ${year|#}: Year as a number
282          * - ${year|0}: Year as a number, zero-padded to 4 digits
283          * - ${month|#}: Month as a number
284          * - ${month|0}: Month as a number with leading 0
285          * - ${month|short}: Month from 'shortMonthNames' configuration setting
286          * - ${month|full}: Month from 'fullMonthNames' configuration setting
287          * - ${day|#}: Day of the month as a number
288          * - ${day|0}: Day of the month as a number with leading 0
289          * - ${dow|short}: Day of the week from 'shortDayNames' configuration setting
290          * - ${dow|full}: Day of the week from 'fullDayNames' configuration setting
291          * - ${hour|#}: Hour as a number
292          * - ${hour|0}: Hour as a number with leading 0
293          * - ${hour|12}: Hour in a 12-hour clock as a number
294          * - ${hour|012}: Hour in a 12-hour clock as a number, with leading 0
295          * - ${hour|period}: Value from 'hour12Periods' configuration setting
296          * - ${minute|#}: Minute as a number
297          * - ${minute|0}: Minute as a number with leading 0
298          * - ${second|#}: Second as a number
299          * - ${second|0}: Second as a number with leading 0
300          * - ${millisecond|#}: Millisecond as a number
301          * - ${millisecond|0}: Millisecond as a number, zero-padded to 3 digits
302          *
303          * @protected
304          * @param {string} tag
305          * @param {string[]} params
306          * @return {FieldSpecificationObject} Field specification object, or null if the tag+params are unrecognized.
307          */
308         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getFieldForTag = function ( tag, params ) {
309                 let spec = null;
311                 switch ( tag + '|' + params[ 0 ] ) {
312                         case 'year|#':
313                         case 'year|0':
314                                 spec = {
315                                         component: 'year',
316                                         calendarComponent: true,
317                                         type: 'number',
318                                         size: 4,
319                                         zeropad: params[ 0 ] === '0'
320                                 };
321                                 break;
323                         case 'month|short':
324                         case 'month|full':
325                                 spec = {
326                                         component: 'month',
327                                         calendarComponent: true,
328                                         type: 'string',
329                                         values: params[ 0 ] === 'short' ? this.shortMonthNames : this.fullMonthNames
330                                 };
331                                 break;
333                         case 'dow|short':
334                         case 'dow|full':
335                                 spec = {
336                                         component: 'dow',
337                                         calendarComponent: true,
338                                         editable: false,
339                                         type: 'string',
340                                         values: params[ 0 ] === 'short' ? this.shortDayNames : this.fullDayNames
341                                 };
342                                 break;
344                         case 'month|#':
345                         case 'month|0':
346                         case 'day|#':
347                         case 'day|0':
348                                 spec = {
349                                         component: tag,
350                                         calendarComponent: true,
351                                         type: 'number',
352                                         size: 2,
353                                         zeropad: params[ 0 ] === '0'
354                                 };
355                                 break;
357                         case 'hour|#':
358                         case 'hour|0':
359                         case 'minute|#':
360                         case 'minute|0':
361                         case 'second|#':
362                         case 'second|0':
363                                 spec = {
364                                         component: tag,
365                                         calendarComponent: false,
366                                         type: 'number',
367                                         size: 2,
368                                         zeropad: params[ 0 ] === '0'
369                                 };
370                                 break;
372                         case 'hour|12':
373                         case 'hour|012':
374                                 spec = {
375                                         component: 'hour12',
376                                         calendarComponent: false,
377                                         type: 'number',
378                                         size: 2,
379                                         zeropad: params[ 0 ] === '012'
380                                 };
381                                 break;
383                         case 'hour|period':
384                                 spec = {
385                                         component: 'hour12period',
386                                         calendarComponent: false,
387                                         type: 'boolean',
388                                         values: this.hour12Periods
389                                 };
390                                 break;
392                         case 'millisecond|#':
393                         case 'millisecond|0':
394                                 spec = {
395                                         component: 'millisecond',
396                                         calendarComponent: false,
397                                         type: 'number',
398                                         size: 3,
399                                         zeropad: params[ 0 ] === '0'
400                                 };
401                                 break;
403                         default:
404                                 return mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.super.prototype.getFieldForTag.call( this, tag, params );
405                 }
407                 if ( spec ) {
408                         if ( spec.editable === undefined ) {
409                                 spec.editable = true;
410                         }
411                         spec.formatValue = this.formatSpecValue;
412                         spec.parseValue = this.parseSpecValue;
413                         if ( spec.values ) {
414                                 spec.size = Math.max.apply(
415                                         // eslint-disable-next-line no-jquery/no-map-util
416                                         null, $.map( spec.values, ( v ) => v.length )
417                                 );
418                         }
419                 }
421                 return spec;
422         };
424         /**
425          * Get components from a Date object.
426          *
427          * Components are:
428          * - year {number}
429          * - month {number} (1-12)
430          * - day {number} (1-31)
431          * - dow {number} (0-6, 0 is Sunday)
432          * - hour {number} (0-23)
433          * - hour12 {number} (1-12)
434          * - hour12period {boolean}
435          * - minute {number} (0-59)
436          * - second {number} (0-59)
437          * - millisecond {number} (0-999)
438          * - zone {number}
439          *
440          * @param {Date|null} date
441          * @return {Object} Components
442          */
443         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getComponentsFromDate = function ( date ) {
444                 let ret;
446                 if ( !( date instanceof Date ) ) {
447                         date = this.defaultDate;
448                 }
450                 if ( this.local ) {
451                         ret = {
452                                 year: date.getFullYear(),
453                                 month: date.getMonth() + 1,
454                                 day: date.getDate(),
455                                 dow: date.getDay() % 7,
456                                 hour: date.getHours(),
457                                 minute: date.getMinutes(),
458                                 second: date.getSeconds(),
459                                 millisecond: date.getMilliseconds(),
460                                 zone: date.getTimezoneOffset()
461                         };
462                 } else {
463                         ret = {
464                                 year: date.getUTCFullYear(),
465                                 month: date.getUTCMonth() + 1,
466                                 day: date.getUTCDate(),
467                                 dow: date.getUTCDay() % 7,
468                                 hour: date.getUTCHours(),
469                                 minute: date.getUTCMinutes(),
470                                 second: date.getUTCSeconds(),
471                                 millisecond: date.getUTCMilliseconds(),
472                                 zone: 0
473                         };
474                 }
476                 ret.hour12period = ret.hour >= 12 ? 1 : 0;
477                 ret.hour12 = ret.hour % 12;
478                 if ( ret.hour12 === 0 ) {
479                         ret.hour12 = 12;
480                 }
482                 return ret;
483         };
485         /**
486          * @inheritdoc
487          */
488         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getDateFromComponents = function ( components ) {
489                 const date = new Date();
491                 components = Object.assign( {}, components );
492                 if ( components.hour === undefined && components.hour12 !== undefined && components.hour12period !== undefined ) {
493                         components.hour = ( components.hour12 % 12 ) + ( components.hour12period ? 12 : 0 );
494                 }
495                 components = Object.assign( {}, this.getComponentsFromDate( null ), components );
497                 if ( components.zone ) {
498                         // Can't just use the constructor because that's stupid about ancient years.
499                         date.setFullYear( components.year, components.month - 1, components.day );
500                         date.setHours( components.hour, components.minute, components.second, components.millisecond );
501                 } else {
502                         // Date.UTC() is stupid about ancient years too.
503                         date.setUTCFullYear( components.year, components.month - 1, components.day );
504                         date.setUTCHours( components.hour, components.minute, components.second, components.millisecond );
505                 }
507                 return date;
508         };
510         /**
511          * @inheritdoc
512          */
513         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.adjustComponent = function ( date, component, delta, mode ) {
514                 let min, max;
516                 if ( !( date instanceof Date ) ) {
517                         date = this.defaultDate;
518                 }
519                 const components = this.getComponentsFromDate( date );
521                 switch ( component ) {
522                         case 'year':
523                                 min = 0;
524                                 max = 9999;
525                                 break;
526                         case 'month':
527                                 min = 1;
528                                 max = 12;
529                                 break;
530                         case 'day':
531                                 min = 1;
532                                 max = this.getDaysInMonth( components.month, components.year );
533                                 break;
534                         case 'hour':
535                                 min = 0;
536                                 max = 23;
537                                 break;
538                         case 'minute':
539                         case 'second':
540                                 min = 0;
541                                 max = 59;
542                                 break;
543                         case 'millisecond':
544                                 min = 0;
545                                 max = 999;
546                                 break;
547                         case 'hour12period':
548                                 component = 'hour';
549                                 min = 0;
550                                 max = 23;
551                                 delta *= 12;
552                                 break;
553                         case 'hour12':
554                                 component = 'hour';
555                                 min = components.hour12period ? 12 : 0;
556                                 max = components.hour12period ? 23 : 11;
557                                 break;
558                         default:
559                                 return new Date( date.getTime() );
560                 }
562                 components[ component ] += delta;
563                 const range = max - min + 1;
564                 switch ( mode ) {
565                         case 'overflow':
566                                 // Date() will mostly handle it automatically. But months need
567                                 // manual handling to prevent e.g. Jan 31 => Mar 3.
568                                 if ( component === 'month' || component === 'year' ) {
569                                         while ( components.month < 1 ) {
570                                                 components[ component ] += 12;
571                                                 components.year--;
572                                         }
573                                         while ( components.month > 12 ) {
574                                                 components[ component ] -= 12;
575                                                 components.year++;
576                                         }
577                                 }
578                                 break;
579                         case 'wrap':
580                                 while ( components[ component ] < min ) {
581                                         components[ component ] += range;
582                                 }
583                                 while ( components[ component ] > max ) {
584                                         components[ component ] -= range;
585                                 }
586                                 break;
587                         case 'clip':
588                                 if ( components[ component ] < min ) {
589                                         components[ component ] = min;
590                                 }
591                                 if ( components[ component ] < max ) {
592                                         components[ component ] = max;
593                                 }
594                                 break;
595                 }
596                 if ( component === 'month' || component === 'year' ) {
597                         components.day = Math.min( components.day, this.getDaysInMonth( components.month, components.year ) );
598                 }
600                 return this.getDateFromComponents( components );
601         };
603         /**
604          * Get the number of days in a month.
605          *
606          * @protected
607          * @param {number} month
608          * @param {number} year
609          * @return {number}
610          */
611         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getDaysInMonth = function ( month, year ) {
612                 switch ( month ) {
613                         case 4:
614                         case 6:
615                         case 9:
616                         case 11:
617                                 return 30;
618                         case 2:
619                                 if ( year % 4 ) {
620                                         return 28;
621                                 } else if ( year % 100 ) {
622                                         return 29;
623                                 }
624                                 return ( year % 400 ) ? 28 : 29;
625                         default:
626                                 return 31;
627                 }
628         };
630         /**
631          * @inheritdoc
632          */
633         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getCalendarHeadings = function () {
634                 const a = this.dayLetters;
636                 if ( this.weekStartsOn ) {
637                         return a.slice( this.weekStartsOn ).concat( a.slice( 0, this.weekStartsOn ) );
638                 } else {
639                         return a.slice( 0 ); // clone
640                 }
641         };
643         /**
644          * @inheritdoc
645          */
646         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.sameCalendarGrid = function ( date1, date2 ) {
647                 if ( this.local ) {
648                         return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth();
649                 } else {
650                         return date1.getUTCFullYear() === date2.getUTCFullYear() && date1.getUTCMonth() === date2.getUTCMonth();
651                 }
652         };
654         /**
655          * @inheritdoc
656          */
657         mw.widgets.datetime.ProlepticGregorianDateTimeFormatter.prototype.getCalendarData = function ( date ) {
658                 const getDate = this.local ? 'getDate' : 'getUTCDate',
659                         setDate = this.local ? 'setDate' : 'setUTCDate';
661                 const ret = {
662                         dayComponent: 'day',
663                         monthComponent: 'month'
664                 };
666                 if ( !( date instanceof Date ) ) {
667                         date = this.defaultDate;
668                 }
670                 let dt = new Date( date.getTime() );
671                 dt[ setDate ]( 1 );
672                 const t = dt.getTime();
674                 let d, e;
675                 if ( this.local ) {
676                         ret.header = this.fullMonthNames[ dt.getMonth() + 1 ] + ' ' + dt.getFullYear();
677                         d = dt.getDay() % 7;
678                         e = this.getDaysInMonth( dt.getMonth() + 1, dt.getFullYear() );
679                 } else {
680                         ret.header = this.fullMonthNames[ dt.getUTCMonth() + 1 ] + ' ' + dt.getUTCFullYear();
681                         d = dt.getUTCDay() % 7;
682                         e = this.getDaysInMonth( dt.getUTCMonth() + 1, dt.getUTCFullYear() );
683                 }
685                 if ( this.weekStartsOn ) {
686                         d = ( d + 7 - this.weekStartsOn ) % 7;
687                 }
688                 d = 1 - d;
690                 ret.rows = [];
691                 while ( d <= e ) {
692                         const row = [];
693                         for ( let i = 0; i < 7; i++, d++ ) {
694                                 dt = new Date( t );
695                                 dt[ setDate ]( d );
696                                 row[ i ] = {
697                                         display: String( dt[ getDate ]() ),
698                                         date: dt,
699                                         extra: d < 1 ? 'prev' : d > e ? 'next' : null
700                                 };
701                         }
702                         ret.rows.push( row );
703                 }
705                 return ret;
706         };
708 }() );