From 8fd370c9f39b4bc1d78cc0f763bf4e99dfd0c382 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 12:32:46 +0100 Subject: Bug 1283385: Implement UI for --- toolkit/content/widgets/datekeeper.js | 244 ++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 toolkit/content/widgets/datekeeper.js (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js new file mode 100644 index 000000000..62fcfadbc --- /dev/null +++ b/toolkit/content/widgets/datekeeper.js @@ -0,0 +1,244 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * DateKeeper keeps track of the date states. + * + * @param {Object} date parts + * { + * {Number} year + * {Number} month + * {Number} date + * } + * {Object} options + * { + * {Number} firstDayOfWeek [optional] + * {Array} weekends [optional] + * {Number} calViewSize [optional] + * } + */ +function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { + this.state = { + firstDayOfWeek, weekends, calViewSize, + dateObj: new Date(0), + years: [], + months: [], + days: [] + }; + this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek); + this._update(year, month, date); +} + +{ + const DAYS_IN_A_WEEK = 7, + MONTHS_IN_A_YEAR = 12, + YEAR_VIEW_SIZE = 200, + YEAR_BUFFER_SIZE = 10; + + DateKeeper.prototype = { + /** + * Set new date + * @param {Object} date parts + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * } + */ + set({ year = this.state.year, month = this.state.month, date = this.state.date }) { + this._update(year, month, date); + }, + + /** + * Set date with value + * @param {Number} value: Date value + */ + setValue(value) { + const dateObj = new Date(value); + this._update(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate()); + }, + + /** + * Set month. Makes sure the date is <= the last day of the month + * @param {Number} month + */ + setMonth(month) { + const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate(); + this._update(this.state.year, month, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Set year. Makes sure the date is <= the last day of the month + * @param {Number} year + */ + setYear(year) { + const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate(); + this._update(year, this.state.month, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Set month by offset. Makes sure the date is <= the last day of the month + * @param {Number} offset + */ + setMonthByOffset(offset) { + const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate(); + this._update(this.state.year, this.state.month + offset, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Update the states. + * @param {Number} year [description] + * @param {Number} month [description] + * @param {Number} date [description] + */ + _update(year, month, date) { + // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 + this.state.dateObj.setUTCFullYear(year, month, date); + this.state.year = this.state.dateObj.getUTCFullYear(); + this.state.month = this.state.dateObj.getUTCMonth(); + this.state.date = this.state.dateObj.getUTCDate(); + }, + + /** + * Generate the array of months + * @return {Array} + * { + * {Number} value: Month in int + * {Boolean} enabled + * } + */ + getMonths() { + // TODO: add min/max and step support + let months = []; + + for (let i = 0; i < MONTHS_IN_A_YEAR; i++) { + months.push({ + value: i, + enabled: true + }); + } + + return months; + }, + + /** + * Generate the array of years + * @return {Array} + * { + * {Number} value: Year in int + * {Boolean} enabled + * } + */ + getYears() { + // TODO: add min/max and step support + let years = []; + + const firstItem = this.state.years[0]; + const lastItem = this.state.years[this.state.years.length - 1]; + const currentYear = this.state.dateObj.getUTCFullYear(); + + // Generate new years array when the year is outside of the first & + // last item range. If not, return the cached result. + if (!firstItem || !lastItem || + currentYear <= firstItem.value + YEAR_BUFFER_SIZE || + currentYear >= lastItem.value - YEAR_BUFFER_SIZE) { + // The year is set in the middle with items on both directions + for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) { + years.push({ + value: currentYear + i, + enabled: true + }); + } + this.state.years = years; + } + return this.state.years; + }, + + /** + * Get days for calendar + * @return {Array} + * { + * {Number} dateValue + * {Number} textContent + * {Array} classNames + * } + */ + getDays() { + // TODO: add min/max and step support + let firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek); + let days = []; + let month = this.state.dateObj.getUTCMonth(); + + for (let i = 0; i < this.state.calViewSize; i++) { + let dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); + let classNames = []; + if (this.state.weekends.includes(dateObj.getUTCDay())) { + classNames.push("weekend"); + } + if (month != dateObj.getUTCMonth()) { + classNames.push("outside"); + } + days.push({ + dateValue: dateObj.getTime(), + textContent: dateObj.getUTCDate(), + classNames + }); + } + return days; + }, + + /** + * Get week headers for calendar + * @param {Number} firstDayOfWeek + * @return {Array} + * { + * {Number} textContent + * {Array} classNames + * } + */ + _getWeekHeaders(firstDayOfWeek) { + let headers = []; + let day = firstDayOfWeek; + + for (let i = 0; i < DAYS_IN_A_WEEK; i++) { + headers.push({ + textContent: day % DAYS_IN_A_WEEK, + classNames: this.state.weekends.includes(day % DAYS_IN_A_WEEK) ? ["weekend"] : [] + }); + day++; + } + return headers; + }, + + /** + * Get the first day on a calendar month + * @param {Date} dateObj + * @param {Number} firstDayOfWeek + * @return {Date} + */ + _getFirstCalendarDate(dateObj, firstDayOfWeek) { + const daysOffset = 1 - DAYS_IN_A_WEEK; + let firstDayOfMonth = this._newUTCDate(dateObj.getUTCFullYear(), dateObj.getUTCMonth()); + let dayOfWeek = firstDayOfMonth.getUTCDay(); + + return this._newUTCDate( + firstDayOfMonth.getUTCFullYear(), + firstDayOfMonth.getUTCMonth(), + // When first calendar date is the same as first day of the week, add + // another row on top of it. + firstDayOfWeek == dayOfWeek ? daysOffset : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK); + }, + + /** + * Helper function for creating UTC dates + * @param {...[Number]} parts + * @return {Date} + */ + _newUTCDate(...parts) { + return new Date(Date.UTC(...parts)); + } + }; +} -- cgit v1.2.3 From 631b690ac3fecb1406246237d282390283c77e2c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:03:55 +0100 Subject: Bug 1320225: [DateTimeInput] Integration of input type=date input box with picker (part 1) --- toolkit/content/widgets/datekeeper.js | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index 62fcfadbc..de01fdade 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -11,7 +11,7 @@ * { * {Number} year * {Number} month - * {Number} date + * {Number} day * } * {Object} options * { @@ -20,7 +20,7 @@ * {Number} calViewSize [optional] * } */ -function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { +function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { this.state = { firstDayOfWeek, weekends, calViewSize, dateObj: new Date(0), @@ -29,7 +29,7 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], days: [] }; this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek); - this._update(year, month, date); + this._update(year, month, day); } { @@ -48,8 +48,8 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], * {Number} date [optional] * } */ - set({ year = this.state.year, month = this.state.month, date = this.state.date }) { - this._update(year, month, date); + set({ year = this.state.year, month = this.state.month, day = this.state.day }) { + this._update(year, month, day); }, /** @@ -62,44 +62,44 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], }, /** - * Set month. Makes sure the date is <= the last day of the month + * Set month. Makes sure the day is <= the last day of the month * @param {Number} month */ setMonth(month) { const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate(); - this._update(this.state.year, month, Math.min(this.state.date, lastDayOfMonth)); + this._update(this.state.year, month, Math.min(this.state.day, lastDayOfMonth)); }, /** - * Set year. Makes sure the date is <= the last day of the month + * Set year. Makes sure the day is <= the last day of the month * @param {Number} year */ setYear(year) { const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate(); - this._update(year, this.state.month, Math.min(this.state.date, lastDayOfMonth)); + this._update(year, this.state.month, Math.min(this.state.day, lastDayOfMonth)); }, /** - * Set month by offset. Makes sure the date is <= the last day of the month + * Set month by offset. Makes sure the day is <= the last day of the month * @param {Number} offset */ setMonthByOffset(offset) { const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate(); - this._update(this.state.year, this.state.month + offset, Math.min(this.state.date, lastDayOfMonth)); + this._update(this.state.year, this.state.month + offset, Math.min(this.state.day, lastDayOfMonth)); }, /** * Update the states. * @param {Number} year [description] * @param {Number} month [description] - * @param {Number} date [description] + * @param {Number} day [description] */ - _update(year, month, date) { + _update(year, month, day) { // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 - this.state.dateObj.setUTCFullYear(year, month, date); + this.state.dateObj.setUTCFullYear(year, month, day); this.state.year = this.state.dateObj.getUTCFullYear(); this.state.month = this.state.dateObj.getUTCMonth(); - this.state.date = this.state.dateObj.getUTCDate(); + this.state.day = this.state.dateObj.getUTCDate(); }, /** @@ -201,14 +201,14 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], */ _getWeekHeaders(firstDayOfWeek) { let headers = []; - let day = firstDayOfWeek; + let dayOfWeek = firstDayOfWeek; for (let i = 0; i < DAYS_IN_A_WEEK; i++) { headers.push({ - textContent: day % DAYS_IN_A_WEEK, - classNames: this.state.weekends.includes(day % DAYS_IN_A_WEEK) ? ["weekend"] : [] + textContent: dayOfWeek % DAYS_IN_A_WEEK, + classNames: this.state.weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : [] }); - day++; + dayOfWeek++; } return headers; }, -- cgit v1.2.3 From e25430117a67f5c898e5e9388ebd44b185d469ab Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 30 Mar 2018 12:17:17 +0200 Subject: moebius#92: HTML - input - datetime + native in moebius: Bug 1317600: https://bugzilla.mozilla.org/show_bug.cgi?id=1317600 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1283385: https://bugzilla.mozilla.org/show_bug.cgi?id=1283385 Bug 1323109: https://bugzilla.mozilla.org/show_bug.cgi?id=1323109 Bug 1314544: https://bugzilla.mozilla.org/show_bug.cgi?id=1314544 Bug 1286182: https://bugzilla.mozilla.org/show_bug.cgi?id=1286182 Bug 1325922: https://bugzilla.mozilla.org/show_bug.cgi?id=1325922 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1320225: https://bugzilla.mozilla.org/show_bug.cgi?id=1320225 Bug 1341190: https://bugzilla.mozilla.org/show_bug.cgi?id=1341190 --- toolkit/content/widgets/datekeeper.js | 171 +++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 66 deletions(-) (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index de01fdade..9777ee647 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -6,41 +6,72 @@ /** * DateKeeper keeps track of the date states. - * - * @param {Object} date parts - * { - * {Number} year - * {Number} month - * {Number} day - * } - * {Object} options - * { - * {Number} firstDayOfWeek [optional] - * {Array} weekends [optional] - * {Number} calViewSize [optional] - * } */ -function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { - this.state = { - firstDayOfWeek, weekends, calViewSize, - dateObj: new Date(0), - years: [], - months: [], - days: [] - }; - this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek); - this._update(year, month, day); +function DateKeeper(props) { + this.init(props); } { const DAYS_IN_A_WEEK = 7, MONTHS_IN_A_YEAR = 12, YEAR_VIEW_SIZE = 200, - YEAR_BUFFER_SIZE = 10; + YEAR_BUFFER_SIZE = 10, + // The min and max values are derived from the ECMAScript spec: + // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 + MIN_DATE = -8640000000000000, + MAX_DATE = 8640000000000000; DateKeeper.prototype = { + get year() { + return this.state.dateObj.getUTCFullYear(); + }, + + get month() { + return this.state.dateObj.getUTCMonth(); + }, + + get day() { + return this.state.dateObj.getUTCDate(); + }, + + get selection() { + return this.state.selection; + }, + + /** + * Initialize DateKeeper + * @param {Number} year + * @param {Number} month + * @param {Number} day + * @param {String} min + * @param {String} max + * @param {Number} firstDayOfWeek + * @param {Array} weekends + * @param {Number} calViewSize + */ + init({ year, month, day, min, max, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { + const today = new Date(); + const isDateSet = year != undefined && month != undefined && day != undefined; + + this.state = { + firstDayOfWeek, weekends, calViewSize, + min: new Date(min != undefined ? min : MIN_DATE), + max: new Date(max != undefined ? max : MAX_DATE), + today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()), + weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends), + years: [], + months: [], + days: [], + selection: { year, month, day }, + }; + + this.state.dateObj = isDateSet ? + this._newUTCDate(year, month, day) : + new Date(this.state.today); + }, /** - * Set new date + * Set new date. The year is always treated as full year, so the short-form + * is not supported. * @param {Object} date parts * { * {Number} year [optional] @@ -48,17 +79,21 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * {Number} date [optional] * } */ - set({ year = this.state.year, month = this.state.month, day = this.state.day }) { - this._update(year, month, day); + set({ year = this.year, month = this.month, day = this.day }) { + // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 + this.state.dateObj.setUTCFullYear(year, month, day); }, /** - * Set date with value - * @param {Number} value: Date value + * Set selection date + * @param {Number} year + * @param {Number} month + * @param {Number} day */ - setValue(value) { - const dateObj = new Date(value); - this._update(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate()); + setSelection({ year, month, day }) { + this.state.selection.year = year; + this.state.selection.month = month; + this.state.selection.day = day; }, /** @@ -66,8 +101,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * @param {Number} month */ setMonth(month) { - const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate(); - this._update(this.state.year, month, Math.min(this.state.day, lastDayOfMonth)); + const lastDayOfMonth = this._newUTCDate(this.year, month + 1, 0).getUTCDate(); + this.set({ year: this.year, + month, + day: Math.min(this.day, lastDayOfMonth) }); }, /** @@ -75,8 +112,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * @param {Number} year */ setYear(year) { - const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate(); - this._update(year, this.state.month, Math.min(this.state.day, lastDayOfMonth)); + const lastDayOfMonth = this._newUTCDate(year, this.month + 1, 0).getUTCDate(); + this.set({ year, + month: this.month, + day: Math.min(this.day, lastDayOfMonth) }); }, /** @@ -84,22 +123,10 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * @param {Number} offset */ setMonthByOffset(offset) { - const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate(); - this._update(this.state.year, this.state.month + offset, Math.min(this.state.day, lastDayOfMonth)); - }, - - /** - * Update the states. - * @param {Number} year [description] - * @param {Number} month [description] - * @param {Number} day [description] - */ - _update(year, month, day) { - // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 - this.state.dateObj.setUTCFullYear(year, month, day); - this.state.year = this.state.dateObj.getUTCFullYear(); - this.state.month = this.state.dateObj.getUTCMonth(); - this.state.day = this.state.dateObj.getUTCDate(); + const lastDayOfMonth = this._newUTCDate(this.year, this.month + offset + 1, 0).getUTCDate(); + this.set({ year: this.year, + month: this.month + offset, + day: Math.min(this.day, lastDayOfMonth) }); }, /** @@ -111,7 +138,6 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * } */ getMonths() { - // TODO: add min/max and step support let months = []; for (let i = 0; i < MONTHS_IN_A_YEAR; i++) { @@ -133,12 +159,11 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * } */ getYears() { - // TODO: add min/max and step support let years = []; const firstItem = this.state.years[0]; const lastItem = this.state.years[this.state.years.length - 1]; - const currentYear = this.state.dateObj.getUTCFullYear(); + const currentYear = this.year; // Generate new years array when the year is outside of the first & // last item range. If not, return the cached result. @@ -161,30 +186,43 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], * Get days for calendar * @return {Array} * { - * {Number} dateValue - * {Number} textContent + * {Date} dateObj * {Array} classNames + * {Boolean} enabled * } */ getDays() { - // TODO: add min/max and step support - let firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek); + // TODO: add step support + const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek); + const month = this.month; let days = []; - let month = this.state.dateObj.getUTCMonth(); for (let i = 0; i < this.state.calViewSize; i++) { - let dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); + const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); let classNames = []; + let enabled = true; if (this.state.weekends.includes(dateObj.getUTCDay())) { classNames.push("weekend"); } if (month != dateObj.getUTCMonth()) { classNames.push("outside"); } + if (this.state.selection.year == dateObj.getUTCFullYear() && + this.state.selection.month == dateObj.getUTCMonth() && + this.state.selection.day == dateObj.getUTCDate()) { + classNames.push("selection"); + } + if (dateObj.getTime() < this.state.min.getTime() || dateObj.getTime() > this.state.max.getTime()) { + classNames.push("out-of-range"); + enabled = false; + } + if (this.state.today.getTime() == dateObj.getTime()) { + classNames.push("today"); + } days.push({ - dateValue: dateObj.getTime(), - textContent: dateObj.getUTCDate(), - classNames + dateObj, + classNames, + enabled, }); } return days; @@ -193,20 +231,21 @@ function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], /** * Get week headers for calendar * @param {Number} firstDayOfWeek + * @param {Array} weekends * @return {Array} * { * {Number} textContent * {Array} classNames * } */ - _getWeekHeaders(firstDayOfWeek) { + _getWeekHeaders(firstDayOfWeek, weekends) { let headers = []; let dayOfWeek = firstDayOfWeek; for (let i = 0; i < DAYS_IN_A_WEEK; i++) { headers.push({ textContent: dayOfWeek % DAYS_IN_A_WEEK, - classNames: this.state.weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : [] + classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : [] }); dayOfWeek++; } -- cgit v1.2.3 From 6f1fcab2d81caedd96d9404386bc92f9884c30ce Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 30 Mar 2018 20:56:33 +0200 Subject: Bug 1363672 - Add step support to date picker --- toolkit/content/widgets/datekeeper.js | 60 ++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index 9777ee647..9517e2154 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -45,18 +45,21 @@ function DateKeeper(props) { * @param {Number} day * @param {String} min * @param {String} max + * @param {Number} step + * @param {Number} stepBase * @param {Number} firstDayOfWeek * @param {Array} weekends * @param {Number} calViewSize */ - init({ year, month, day, min, max, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { + init({ year, month, day, min, max, step, stepBase, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { const today = new Date(); const isDateSet = year != undefined && month != undefined && day != undefined; this.state = { - firstDayOfWeek, weekends, calViewSize, + step, firstDayOfWeek, weekends, calViewSize, min: new Date(min != undefined ? min : MIN_DATE), max: new Date(max != undefined ? max : MAX_DATE), + stepBase: new Date(stepBase), today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()), weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends), years: [], @@ -192,33 +195,50 @@ function DateKeeper(props) { * } */ getDays() { - // TODO: add step support const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek); const month = this.month; let days = []; for (let i = 0; i < this.state.calViewSize; i++) { - const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); + const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), + firstDayOfMonth.getUTCMonth(), + firstDayOfMonth.getUTCDate() + i); let classNames = []; let enabled = true; - if (this.state.weekends.includes(dateObj.getUTCDay())) { + + const isWeekend = this.state.weekends.includes(dateObj.getUTCDay()); + const isCurrentMonth = month == dateObj.getUTCMonth(); + const isSelection = this.state.selection.year == dateObj.getUTCFullYear() && + this.state.selection.month == dateObj.getUTCMonth() && + this.state.selection.day == dateObj.getUTCDate(); + const isOutOfRange = dateObj.getTime() < this.state.min.getTime() || + dateObj.getTime() > this.state.max.getTime(); + const isToday = this.state.today.getTime() == dateObj.getTime(); + const isOffStep = this._checkIsOffStep(dateObj, + this._newUTCDate(dateObj.getUTCFullYear(), + dateObj.getUTCMonth(), + dateObj.getUTCDate() + 1)); + + if (isWeekend) { classNames.push("weekend"); } - if (month != dateObj.getUTCMonth()) { + if (!isCurrentMonth) { classNames.push("outside"); } - if (this.state.selection.year == dateObj.getUTCFullYear() && - this.state.selection.month == dateObj.getUTCMonth() && - this.state.selection.day == dateObj.getUTCDate()) { + if (isSelection && !isOutOfRange && !isOffStep) { classNames.push("selection"); } - if (dateObj.getTime() < this.state.min.getTime() || dateObj.getTime() > this.state.max.getTime()) { + if (isOutOfRange) { classNames.push("out-of-range"); enabled = false; } - if (this.state.today.getTime() == dateObj.getTime()) { + if (isToday) { classNames.push("today"); } + if (isOffStep) { + classNames.push("off-step"); + enabled = false; + } days.push({ dateObj, classNames, @@ -228,6 +248,24 @@ function DateKeeper(props) { return days; }, + /** + * Check if a date is off step given a starting point and the next increment + * @param {Date} start + * @param {Date} next + * @return {Boolean} + */ + _checkIsOffStep(start, next) { + // If the increment is larger or equal to the step, it must not be off-step. + if (next - start >= this.state.step) { + return false; + } + // Calculate the last valid date + const lastValidStep = Math.floor((next - 1 - this.state.stepBase) / this.state.step); + const lastValidTimeInMs = lastValidStep * this.state.step + this.state.stepBase.getTime(); + // The date is off-step if the last valid date is smaller than the start date + return lastValidTimeInMs < start.getTime(); + }, + /** * Get week headers for calendar * @param {Number} firstDayOfWeek -- cgit v1.2.3 From 6a44ab26592fbe95b69e1bf4d3a3b0de03a99b26 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 30 Mar 2018 21:14:18 +0200 Subject: Bug 1364026 - (Part 2) Check if min and max attributes on input type date are valid date strings --- toolkit/content/widgets/datekeeper.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index 9517e2154..4959b9609 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -16,9 +16,11 @@ function DateKeeper(props) { MONTHS_IN_A_YEAR = 12, YEAR_VIEW_SIZE = 200, YEAR_BUFFER_SIZE = 10, - // The min and max values are derived from the ECMAScript spec: + // The min value is 0001-01-01 based on HTML spec: + // https://html.spec.whatwg.org/#valid-date-string + MIN_DATE = -62135596800000, + // The max value is derived from the ECMAScript spec: // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 - MIN_DATE = -8640000000000000, MAX_DATE = 8640000000000000; DateKeeper.prototype = { @@ -43,8 +45,8 @@ function DateKeeper(props) { * @param {Number} year * @param {Number} month * @param {Number} day - * @param {String} min - * @param {String} max + * @param {Number} min + * @param {Number} max * @param {Number} step * @param {Number} stepBase * @param {Number} firstDayOfWeek @@ -57,8 +59,9 @@ function DateKeeper(props) { this.state = { step, firstDayOfWeek, weekends, calViewSize, - min: new Date(min != undefined ? min : MIN_DATE), - max: new Date(max != undefined ? max : MAX_DATE), + // min & max are NaN if empty or invalid + min: new Date(Number.isNaN(min) ? MIN_DATE : min), + max: new Date(Number.isNaN(max) ? MAX_DATE : max), stepBase: new Date(stepBase), today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()), weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends), -- cgit v1.2.3 From e14c686ac0ad5e6cfdd933049c11b80a425283dc Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 30 Mar 2018 23:50:34 +0200 Subject: Bug 1381421 - (Part 1) Handle dates earlier than 0001-01-01 and later than 275760-09-13 correctly --- toolkit/content/widgets/datekeeper.js | 86 ++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 37 deletions(-) (limited to 'toolkit/content/widgets/datekeeper.js') diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index 4959b9609..5d70416a9 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -19,9 +19,11 @@ function DateKeeper(props) { // The min value is 0001-01-01 based on HTML spec: // https://html.spec.whatwg.org/#valid-date-string MIN_DATE = -62135596800000, - // The max value is derived from the ECMAScript spec: + // The max value is derived from the ECMAScript spec (275760-09-13): // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1 - MAX_DATE = 8640000000000000; + MAX_DATE = 8640000000000000, + MAX_YEAR = 275760, + MAX_MONTH = 9; DateKeeper.prototype = { get year() { @@ -32,10 +34,6 @@ function DateKeeper(props) { return this.state.dateObj.getUTCMonth(); }, - get day() { - return this.state.dateObj.getUTCDate(); - }, - get selection() { return this.state.selection; }, @@ -55,7 +53,6 @@ function DateKeeper(props) { */ init({ year, month, day, min, max, step, stepBase, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { const today = new Date(); - const isDateSet = year != undefined && month != undefined && day != undefined; this.state = { step, firstDayOfWeek, weekends, calViewSize, @@ -66,28 +63,34 @@ function DateKeeper(props) { today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()), weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends), years: [], - months: [], - days: [], + dateObj: new Date(0), selection: { year, month, day }, }; - this.state.dateObj = isDateSet ? - this._newUTCDate(year, month, day) : - new Date(this.state.today); + this.setCalendarMonth({ + year: year === undefined ? today.getFullYear() : year, + month: month === undefined ? today.getMonth() : month + }); }, /** - * Set new date. The year is always treated as full year, so the short-form - * is not supported. + * Set new calendar month. The year is always treated as full year, so the + * short-form is not supported. * @param {Object} date parts * { * {Number} year [optional] * {Number} month [optional] - * {Number} date [optional] * } */ - set({ year = this.year, month = this.month, day = this.day }) { + setCalendarMonth({ year = this.year, month = this.month }) { + // Make sure the date is valid before setting. // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 - this.state.dateObj.setUTCFullYear(year, month, day); + if (year > MAX_YEAR || year === MAX_YEAR && month >= MAX_MONTH) { + this.state.dateObj.setUTCFullYear(MAX_YEAR, MAX_MONTH - 1, 1); + } else if (year < 1 || year === 1 && month < 0) { + this.state.dateObj.setUTCFullYear(1, 0, 1); + } else { + this.state.dateObj.setUTCFullYear(year, month, 1); + } }, /** @@ -107,10 +110,7 @@ function DateKeeper(props) { * @param {Number} month */ setMonth(month) { - const lastDayOfMonth = this._newUTCDate(this.year, month + 1, 0).getUTCDate(); - this.set({ year: this.year, - month, - day: Math.min(this.day, lastDayOfMonth) }); + this.setCalendarMonth({ year: this.year, month }); }, /** @@ -118,10 +118,7 @@ function DateKeeper(props) { * @param {Number} year */ setYear(year) { - const lastDayOfMonth = this._newUTCDate(year, this.month + 1, 0).getUTCDate(); - this.set({ year, - month: this.month, - day: Math.min(this.day, lastDayOfMonth) }); + this.setCalendarMonth({ year, month: this.month }); }, /** @@ -129,10 +126,7 @@ function DateKeeper(props) { * @param {Number} offset */ setMonthByOffset(offset) { - const lastDayOfMonth = this._newUTCDate(this.year, this.month + offset + 1, 0).getUTCDate(); - this.set({ year: this.year, - month: this.month + offset, - day: Math.min(this.day, lastDayOfMonth) }); + this.setCalendarMonth({ year: this.year, month: this.month + offset }); }, /** @@ -178,10 +172,13 @@ function DateKeeper(props) { currentYear >= lastItem.value - YEAR_BUFFER_SIZE) { // The year is set in the middle with items on both directions for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) { - years.push({ - value: currentYear + i, - enabled: true - }); + const year = currentYear + i; + if (year >= 1 && year <= MAX_YEAR) { + years.push({ + value: year, + enabled: true + }); + } } this.state.years = years; } @@ -193,6 +190,7 @@ function DateKeeper(props) { * @return {Array} * { * {Date} dateObj + * {Number} content * {Array} classNames * {Boolean} enabled * } @@ -206,9 +204,22 @@ function DateKeeper(props) { const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); + let classNames = []; let enabled = true; + const isValid = dateObj.getTime() >= MIN_DATE && dateObj.getTime() <= MAX_DATE; + if (!isValid) { + classNames.push("out-of-range"); + enabled = false; + + days.push({ + classNames, + enabled, + }); + continue; + } + const isWeekend = this.state.weekends.includes(dateObj.getUTCDay()); const isCurrentMonth = month == dateObj.getUTCMonth(); const isSelection = this.state.selection.year == dateObj.getUTCFullYear() && @@ -244,6 +255,7 @@ function DateKeeper(props) { } days.push({ dateObj, + content: dateObj.getUTCDate(), classNames, enabled, }); @@ -275,7 +287,7 @@ function DateKeeper(props) { * @param {Array} weekends * @return {Array} * { - * {Number} textContent + * {Number} content * {Array} classNames * } */ @@ -285,7 +297,7 @@ function DateKeeper(props) { for (let i = 0; i < DAYS_IN_A_WEEK; i++) { headers.push({ - textContent: dayOfWeek % DAYS_IN_A_WEEK, + content: dayOfWeek % DAYS_IN_A_WEEK, classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : [] }); dayOfWeek++; @@ -318,7 +330,7 @@ function DateKeeper(props) { * @return {Date} */ _newUTCDate(...parts) { - return new Date(Date.UTC(...parts)); - } + return new Date(new Date(0).setUTCFullYear(...parts)); + }, }; } -- cgit v1.2.3