diff --git a/tests/unit/datepicker/core.js b/tests/unit/datepicker/core.js index 2cc89cd21b9..c5106718f32 100644 --- a/tests/unit/datepicker/core.js +++ b/tests/unit/datepicker/core.js @@ -39,9 +39,11 @@ QUnit.test( "widget method", function( assert ) { QUnit.test( "baseStructure", function( assert ) { var ready = assert.async(); - assert.expect( 58 ); + assert.expect( 60 ); var header, title, table, thead, week, panel, inl, child, - inp = testHelper.initNewInput(), + inp = testHelper.initNewInput( { + defaultDate: $.datepicker._newDate( 1, 2 - 1, 3 ) + } ), dp = $( "#ui-datepicker-div" ); function step1() { @@ -61,7 +63,7 @@ QUnit.test( "baseStructure", function( assert ) { assert.ok( title.is( "div.ui-datepicker-title" ) && title.html() !== "", "Structure - title division" ); assert.equal( title.children().length, 2, "Structure - title child count" ); assert.ok( title.children().first().is( "span.ui-datepicker-month" ) && title.children().first().text() !== "", "Structure - month text" ); - assert.ok( title.children().last().is( "span.ui-datepicker-year" ) && title.children().last().text() !== "", "Structure - year text" ); + assert.ok( title.children().last().is( "span.ui-datepicker-year" ) && title.children().last().text() === "0001", "Structure - year text" ); table = dp.children().eq( 1 ); assert.ok( table.is( "table.ui-datepicker-calendar" ), "Structure - month table" ); @@ -90,12 +92,15 @@ QUnit.test( "baseStructure", function( assert ) { inp = testHelper.initNewInput( { changeMonth: true, changeYear: true, - showButtonPanel: true + showButtonPanel: true, + defaultDate: $.datepicker._newDate( 1, 2 - 1, 3 ) } ); testHelper.onFocus( inp, function() { title = dp.find( "div.ui-datepicker-title" ); assert.ok( title.children().first().is( "select.ui-datepicker-month" ), "Structure - month selector" ); assert.ok( title.children().last().is( "select.ui-datepicker-year" ), "Structure - year selector" ); + assert.equal( title.children().last().children().first().text(), "-9" ); + assert.equal( title.children().last().children().last().text(), "0011" ); panel = dp.children().last(); assert.ok( panel.is( "div.ui-datepicker-buttonpane" ), "Structure - button panel division" ); @@ -457,7 +462,7 @@ QUnit.test( "keystrokes", function( assert ) { } ); QUnit.test( "mouse", function( assert ) { - assert.expect( 15 ); + assert.expect( 16 ); var inl, inp = testHelper.init( "#inp" ), dp = $( "#ui-datepicker-div" ), @@ -470,6 +475,10 @@ QUnit.test( "mouse", function( assert ) { $( ".ui-datepicker-calendar tbody a:contains(12)", dp ).simulate( "click", {} ); testHelper.equalsDate( assert, inp.datepicker( "getDate" ), new Date( 2008, 2 - 1, 12 ), "Mouse click - preset" ); + inp.val( "02/04/0001" ).datepicker( "show" ); + $( ".ui-datepicker-calendar tbody a:contains(12)", dp ).simulate( "click", {} ); + testHelper.equalsDate( assert, inp.datepicker( "getDate" ), $.datepicker._newDate( 1, 2 - 1, 12 ), + "Mouse click - year 0-99" ); inp.val( "02/04/2008" ).datepicker( "show" ); inp.val( "" ).datepicker( "show" ); $( "button.ui-datepicker-close", dp ).simulate( "click", {} ); diff --git a/tests/unit/datepicker/helper.js b/tests/unit/datepicker/helper.js index 7fd149bf156..7c9964d8d77 100644 --- a/tests/unit/datepicker/helper.js +++ b/tests/unit/datepicker/helper.js @@ -18,8 +18,8 @@ return $.extend( helper, { assert.ok( false, message + " - missing date" ); return; } - d1 = new Date( d1.getFullYear(), d1.getMonth(), d1.getDate() ); - d2 = new Date( d2.getFullYear(), d2.getMonth(), d2.getDate() ); + d1 = $.datepicker._newDate( d1.getFullYear(), d1.getMonth(), d1.getDate() ); + d2 = $.datepicker._newDate( d2.getFullYear(), d2.getMonth(), d2.getDate() ); assert.equal( d1.toString(), d2.toString(), message ); }, diff --git a/tests/unit/datepicker/options.js b/tests/unit/datepicker/options.js index 7711e741233..60f22cf37c3 100644 --- a/tests/unit/datepicker/options.js +++ b/tests/unit/datepicker/options.js @@ -267,7 +267,7 @@ QUnit.test( "otherMonths", function( assert ) { } ); QUnit.test( "defaultDate", function( assert ) { - assert.expect( 16 ); + assert.expect( 18 ); var inp = testHelper.init( "#inp" ), date = new Date(); inp.val( "" ).datepicker( "show" ). @@ -350,6 +350,11 @@ QUnit.test( "defaultDate", function( assert ) { simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); date = new Date( 2007, 7 - 1, 4 ); testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date, "Default date 07/04/2007" ); + inp.datepicker( "option", { defaultDate: "07/04/0001" } ). + datepicker( "hide" ).val( "" ).datepicker( "show" ). + simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); + date = $.datepicker._newDate( 1, 7 - 1, 4 ); + testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date, "Default date 07/04/0001" ); inp.datepicker( "option", { dateFormat: "yy-mm-dd", defaultDate: "2007-04-02" } ). datepicker( "hide" ).val( "" ).datepicker( "show" ). simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); @@ -362,6 +367,11 @@ QUnit.test( "defaultDate", function( assert ) { datepicker( "hide" ).val( "" ).datepicker( "show" ). simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date, "Default date 01/26/2007" ); + date = $.datepicker._newDate( 1, 1 - 1, 26 ); + inp.datepicker( "option", { defaultDate: date } ). + datepicker( "hide" ).val( "" ).datepicker( "show" ). + simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); + testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date, "Default date 01/26/0001" ); } ); QUnit.test( "miscellaneous", function( assert ) { @@ -537,14 +547,17 @@ QUnit.test( "minMax", function( assert ) { } ); QUnit.test( "setDate", function( assert ) { - assert.expect( 24 ); + assert.expect( 26 ); var inl, alt, minDate, maxDate, dateAndTimeToSet, dateAndTimeClone, inp = testHelper.init( "#inp" ), date1 = new Date( 2008, 6 - 1, 4 ), - date2 = new Date(); + date2 = new Date(), + date3 = $.datepicker._newDate( 1, 4 - 1, 1 ); assert.ok( inp.datepicker( "getDate" ) == null, "Set date - default" ); inp.datepicker( "setDate", date1 ); testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date1, "Set date - 2008-06-04" ); + inp.datepicker( "setDate", date3 ); + testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date3, "Set date - 0001-04-01" ); date1 = new Date(); date1.setDate( date1.getDate() + 7 ); inp.datepicker( "setDate", +7 ); @@ -568,6 +581,10 @@ QUnit.test( "setDate", function( assert ) { date1.setDate( date1.getDate() - 21 ); inp.datepicker( "setDate", "c -3 w" ); testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date1, "Set date - c -3 w" ); + date3 = new Date( date1 ); + date3.setFullYear( 1 ); + inp.datepicker( "setDate", "c " + ( 1 - date1.getFullYear() ) + " y" ); + testHelper.equalsDate( assert, inp.datepicker( "getDate" ), date3, "Set date - 0001 relatively" ); // Inline inl = testHelper.init( "#inl" ); @@ -926,7 +943,7 @@ QUnit.test( "iso8601Week", function( assert ) { } ); QUnit.test( "parseDate", function( assert ) { - assert.expect( 26 ); + assert.expect( 29 ); testHelper.init( "#inp" ); var currentYear, gmtDate, fr, settings, zh; assert.ok( $.datepicker.parseDate( "d m y", "" ) == null, "Parse date empty" ); @@ -953,6 +970,8 @@ QUnit.test( "parseDate", function( assert ) { testHelper.equalsDate( assert, $.datepicker.parseDate( "'day' d 'of' MM (''DD''), yy", "day 3 of February ('Saturday'), 2001" ), new Date( 2001, 2 - 1, 3 ), "Parse date 'day' d 'of' MM (''DD''), yy" ); + testHelper.equalsDate( assert, $.datepicker.parseDate( "yy-mm-dd", "0001-02-03" ), + $.datepicker._newDate( 1, 2 - 1, 3 ), "Parse ancient date yy-mm-dd" ); currentYear = new Date().getFullYear(); testHelper.equalsDate( assert, $.datepicker.parseDate( "y-m-d", ( currentYear - 2000 ) + "-02-03" ), new Date( currentYear, 2 - 1, 3 ), "Parse date y-m-d - default cutuff" ); @@ -972,6 +991,9 @@ QUnit.test( "parseDate", function( assert ) { gmtDate.setMinutes( gmtDate.getMinutes() - gmtDate.getTimezoneOffset() ); testHelper.equalsDate( assert, $.datepicker.parseDate( "@", "981158400000" ), gmtDate, "Parse date @" ); testHelper.equalsDate( assert, $.datepicker.parseDate( "!", "631167552000000000" ), gmtDate, "Parse date !" ); + gmtDate = $.datepicker._newDate( 1, 2 - 1, 3 ); + testHelper.equalsDate( assert, $.datepicker.parseDate( "@", "-62132724000000" ), gmtDate, "Parse ancient date @" ); + testHelper.equalsDate( assert, $.datepicker.parseDate( "!", "28728000000000" ), gmtDate, "Parse ancient date !" ); fr = $.datepicker.regional.fr; settings = { dayNamesShort: fr.dayNamesShort, dayNames: fr.dayNames, @@ -1066,7 +1088,7 @@ QUnit.test( "Ticket #7244: date parser does not fail when too many numbers are p } ); QUnit.test( "formatDate", function( assert ) { - assert.expect( 16 ); + assert.expect( 17 ); testHelper.init( "#inp" ); var gmtDate, fr, settings; assert.equal( $.datepicker.formatDate( "d m y", new Date( 2001, 2 - 1, 3 ) ), @@ -1090,6 +1112,8 @@ QUnit.test( "formatDate", function( assert ) { assert.equal( $.datepicker.formatDate( "'day' d 'of' MM (''DD''), yy", new Date( 2001, 2 - 1, 3 ) ), "day 3 of February ('Saturday'), 2001", "Format date 'day' d 'of' MM ('DD'), yy" ); + assert.equal( $.datepicker.formatDate( "yy-mm-dd", $.datepicker._newDate( 999, 2 - 1, 3 ) ), + "0999-02-03", "Format ancient date yy-mm-dd" ); gmtDate = new Date( 2001, 2 - 1, 3 ); gmtDate.setMinutes( gmtDate.getMinutes() - gmtDate.getTimezoneOffset() ); assert.equal( $.datepicker.formatDate( "@", gmtDate ), "981158400000", "Format date @" ); diff --git a/ui/widgets/datepicker.js b/ui/widgets/datepicker.js index 5e6321e1d44..1e20f521171 100644 --- a/ui/widgets/datepicker.js +++ b/ui/widgets/datepicker.js @@ -1151,6 +1151,7 @@ $.extend( Datepicker.prototype, { monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, year = -1, + isFullYear = false, month = -1, day = -1, doy = -1, @@ -1172,11 +1173,14 @@ $.extend( Datepicker.prototype, { size = ( match === "@" ? 14 : ( match === "!" ? 20 : ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), minSize = ( match === "y" ? size : 1 ), - digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), + digits = new RegExp( "^" + ( match === "@" ? "-?" : "" ) + "\\d{" + minSize + "," + size + "}" ), num = value.substring( iValue ).match( digits ); if ( !num ) { throw "Missing number at position " + iValue; } + if ( match === "y" ) { + isFullYear = isDoubled; + } iValue += num[ 0 ].length; return parseInt( num[ 0 ], 10 ); }, @@ -1243,12 +1247,14 @@ $.extend( Datepicker.prototype, { case "@": date = new Date( getNumber( "@" ) ); year = date.getFullYear(); + isFullYear = true; month = date.getMonth() + 1; day = date.getDate(); break; case "!": date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); year = date.getFullYear(); + isFullYear = true; month = date.getMonth() + 1; day = date.getDate(); break; @@ -1274,7 +1280,7 @@ $.extend( Datepicker.prototype, { if ( year === -1 ) { year = new Date().getFullYear(); - } else if ( year < 100 ) { + } else if ( year < 100 && !isFullYear ) { year += new Date().getFullYear() - new Date().getFullYear() % 100 + ( year <= shortYearCutoff ? 0 : -100 ); } @@ -1292,7 +1298,7 @@ $.extend( Datepicker.prototype, { } while ( true ); } - date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); + date = this._daylightSavingAdjust( this._newDate( year, month - 1, day ) ); if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { throw "Invalid date"; // E.g. 31/02/00 } @@ -1400,7 +1406,7 @@ $.extend( Datepicker.prototype, { break; case "o": output += formatNumber( "o", - Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); + Math.round( ( this._newDate( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - this._newDate( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); break; case "m": output += formatNumber( "m", date.getMonth() + 1, 2 ); @@ -1409,8 +1415,7 @@ $.extend( Datepicker.prototype, { output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); break; case "y": - output += ( lookAhead( "y" ) ? date.getFullYear() : - ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); + output += lookAhead( "y" ) ? this._formatYear( date.getFullYear() ) : ( "00" + date.getFullYear() ).slice( -2 ); break; case "@": output += date.getTime(); @@ -1558,7 +1563,7 @@ $.extend( Datepicker.prototype, { } matches = pattern.exec( offset ); } - return new Date( year, month, day ); + return $.datepicker._newDate( year, month, day ); }, newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); @@ -1610,7 +1615,7 @@ $.extend( Datepicker.prototype, { /* Retrieve the date(s) directly. */ _getDate: function( inst ) { var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : - this._daylightSavingAdjust( new Date( + this._daylightSavingAdjust( this._newDate( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); return startDate; }, @@ -1662,7 +1667,7 @@ $.extend( Datepicker.prototype, { printDate, dRow, tbody, daySettings, otherMonth, unselectable, tempDate = new Date(), today = this._daylightSavingAdjust( - new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time + this._newDate( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time isRTL = this._get( inst, "isRTL" ), showButtonPanel = this._get( inst, "showButtonPanel" ), hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), @@ -1672,7 +1677,7 @@ $.extend( Datepicker.prototype, { stepMonths = this._get( inst, "stepMonths" ), isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : - new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), + this._newDate( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), minDate = this._getMinMaxDate( inst, "min" ), maxDate = this._getMinMaxDate( inst, "max" ), drawMonth = inst.drawMonth - showCurrentAtPos, @@ -1683,10 +1688,10 @@ $.extend( Datepicker.prototype, { drawYear--; } if ( maxDate ) { - maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), + maxDraw = this._daylightSavingAdjust( this._newDate( maxDate.getFullYear(), maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); - while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { + while ( this._daylightSavingAdjust( this._newDate( drawYear, drawMonth, 1 ) ) > maxDraw ) { drawMonth--; if ( drawMonth < 0 ) { drawMonth = 11; @@ -1699,7 +1704,7 @@ $.extend( Datepicker.prototype, { prevText = this._get( inst, "prevText" ); prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, - this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), + this._daylightSavingAdjust( this._newDate( drawYear, drawMonth - stepMonths, 1 ) ), this._getFormatConfig( inst ) ) ); prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ? @@ -1709,7 +1714,7 @@ $.extend( Datepicker.prototype, { nextText = this._get( inst, "nextText" ); nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, - this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), + this._daylightSavingAdjust( this._newDate( drawYear, drawMonth + stepMonths, 1 ) ), this._getFormatConfig( inst ) ) ); next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ? @@ -1747,7 +1752,7 @@ $.extend( Datepicker.prototype, { group = ""; this.maxRows = 4; for ( col = 0; col < numMonths[ 1 ]; col++ ) { - selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); + selectedDate = this._daylightSavingAdjust( this._newDate( drawYear, drawMonth, inst.selectedDay ) ); cornerClass = " ui-corner-all"; calender = ""; if ( isMultiMonth ) { @@ -1785,7 +1790,7 @@ $.extend( Datepicker.prototype, { curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) this.maxRows = numRows; - printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); + printDate = this._daylightSavingAdjust( this._newDate( drawYear, drawMonth, 1 - leadDays ) ); for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows calender += ""; tbody = ( !showWeek ? "" : "" + @@ -1873,7 +1878,7 @@ $.extend( Datepicker.prototype, { if ( !inst.yearshtml ) { inst.yearshtml = ""; if ( secondary || !changeYear ) { - html += "" + drawYear + ""; + html += "" + this._formatYear( drawYear ) + ""; } else { // determine range of years to display @@ -1893,7 +1898,7 @@ $.extend( Datepicker.prototype, { for ( ; year <= endYear; year++ ) { inst.yearshtml += ""; + ">" + this._formatYear( year ) + ""; } inst.yearshtml += ""; @@ -1915,7 +1920,7 @@ $.extend( Datepicker.prototype, { var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), month = inst.selectedMonth + ( period === "M" ? offset : 0 ), day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), - date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); + date = this._restrictMinMax( inst, this._daylightSavingAdjust( this._newDate( year, month, day ) ) ); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); @@ -1955,18 +1960,18 @@ $.extend( Datepicker.prototype, { /* Find the number of days in a given month. */ _getDaysInMonth: function( year, month ) { - return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); + return 32 - this._daylightSavingAdjust( this._newDate( year, month, 32 ) ).getDate(); }, /* Find the day of the week of the first of a month. */ _getFirstDayOfMonth: function( year, month ) { - return new Date( year, month, 1 ).getDay(); + return this._newDate( year, month, 1 ).getDay(); }, /* Determines if we should allow a "next/prev" month display change. */ _canAdjustMonth: function( inst, offset, curYear, curMonth ) { var numMonths = this._getNumberOfMonths( inst ), - date = this._daylightSavingAdjust( new Date( curYear, + date = this._daylightSavingAdjust( this._newDate( curYear, curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); if ( offset < 0 ) { @@ -2020,9 +2025,28 @@ $.extend( Datepicker.prototype, { inst.currentYear = inst.selectedYear; } var date = ( day ? ( typeof day === "object" ? day : - this._daylightSavingAdjust( new Date( year, month, day ) ) ) : - this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); + this._daylightSavingAdjust( this._newDate( year, month, day ) ) ) : + this._daylightSavingAdjust( this._newDate( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); + }, + + /* Create a Date from a year, month, and day, accounting for years 0-99. */ + _newDate: function( year, month, day ) { + var date = new Date( year, month, day ); + + // Offset dates in the incorrect range. Blindly calling setFullYear(year) would not handle out-of-range + // months/days causing the year to shift. + if ( year >= 0 && year <= 99 ) { + date.setFullYear( date.getFullYear() - 1900 ); + } + return date; + }, + + /* Add leading zeros to produce an at-least-four-digit year. */ + _formatYear: function( year ) { + var yearString = "" + year; + return year < 0 ? yearString : + yearString.length < 4 ? ( "0000" + yearString ).slice( -4 ) : yearString; } } );