mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-13 15:34:01 +00:00
Bug 1381421 - (Part 1) Handle dates earlier than 0001-01-01 and later than 275760-09-13 correctly. r=mconley
MozReview-Commit-ID: Af4ZuYIxRsT --HG-- extra : rebase_source : 0df0422eabc074e1cd761a834931718b8ca6f733
This commit is contained in:
parent
934fb866da
commit
c42e4b8c55
@ -11,6 +11,9 @@
|
||||
* @param {Object} options
|
||||
* {
|
||||
* {Number} calViewSize: Number of days to appear on a calendar view
|
||||
* {Function} getDayString: Transform day number to string
|
||||
* {Function} getWeekHeaderString: Transform day of week number to string
|
||||
* {Function} setSelection: Set selection for dateKeeper
|
||||
* }
|
||||
* @param {Object} context
|
||||
* {
|
||||
@ -24,9 +27,11 @@ function Calendar(options, context) {
|
||||
this.context = context;
|
||||
this.state = {
|
||||
days: [],
|
||||
weekHeaders: []
|
||||
weekHeaders: [],
|
||||
setSelection: options.setSelection,
|
||||
getDayString: options.getDayString,
|
||||
getWeekHeaderString: options.getWeekHeaderString
|
||||
};
|
||||
this.props = {};
|
||||
this.elements = {
|
||||
weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
|
||||
daysView: this._generateNodes(options.calViewSize, context.daysView)
|
||||
@ -45,34 +50,32 @@ Calendar.prototype = {
|
||||
* {Boolean} isVisible: Whether or not the calendar is in view
|
||||
* {Array<Object>} days: Data for days
|
||||
* {
|
||||
* {Number} dateValue: Date in milliseconds
|
||||
* {Number} textContent
|
||||
* {Array<String>} classNames
|
||||
* }
|
||||
* {Array<Object>} weekHeaders: Data for weekHeaders
|
||||
* {
|
||||
* {Number} textContent
|
||||
* {Date} dateObj
|
||||
* {Number} content
|
||||
* {Array<String>} classNames
|
||||
* {Boolean} enabled
|
||||
* }
|
||||
* {Function} getDayString: Transform day number to string
|
||||
* {Function} getWeekHeaderString: Transform day of week number to string
|
||||
* {Function} setSelection: Set selection for dateKeeper
|
||||
* {Array<Object>} weekHeaders: Data for weekHeaders
|
||||
* {
|
||||
* {Number} content
|
||||
* {Array<String>} classNames
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
setProps(props) {
|
||||
if (props.isVisible) {
|
||||
// Transform the days and weekHeaders array for rendering
|
||||
const days = props.days.map(({ dateObj, classNames, enabled }) => {
|
||||
const days = props.days.map(({ dateObj, content, classNames, enabled }) => {
|
||||
return {
|
||||
textContent: props.getDayString(dateObj.getUTCDate()),
|
||||
dateObj,
|
||||
textContent: this.state.getDayString(content),
|
||||
className: classNames.join(" "),
|
||||
enabled
|
||||
};
|
||||
});
|
||||
const weekHeaders = props.weekHeaders.map(({ textContent, classNames }) => {
|
||||
const weekHeaders = props.weekHeaders.map(({ content, classNames }) => {
|
||||
return {
|
||||
textContent: props.getWeekHeaderString(textContent),
|
||||
textContent: this.state.getWeekHeaderString(content),
|
||||
className: classNames.join(" ")
|
||||
};
|
||||
});
|
||||
@ -91,8 +94,6 @@ Calendar.prototype = {
|
||||
this.state.days = days;
|
||||
this.state.weekHeaders = weekHeaders;
|
||||
}
|
||||
|
||||
this.props = Object.assign(this.props, props);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -149,9 +150,9 @@ Calendar.prototype = {
|
||||
case "click": {
|
||||
if (event.target.parentNode == this.context.daysView) {
|
||||
let targetId = event.target.dataset.id;
|
||||
let targetObj = this.props.days[targetId];
|
||||
let targetObj = this.state.days[targetId];
|
||||
if (targetObj.enabled) {
|
||||
this.props.setSelection(targetObj.dateObj);
|
||||
this.state.setSelection(targetObj.dateObj);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -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,35 @@ 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 +111,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 +119,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 +127,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 +173,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 +191,7 @@ function DateKeeper(props) {
|
||||
* @return {Array<Object>}
|
||||
* {
|
||||
* {Date} dateObj
|
||||
* {Number} content
|
||||
* {Array<String>} classNames
|
||||
* {Boolean} enabled
|
||||
* }
|
||||
@ -206,9 +205,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 +256,7 @@ function DateKeeper(props) {
|
||||
}
|
||||
days.push({
|
||||
dateObj,
|
||||
content: dateObj.getUTCDate(),
|
||||
classNames,
|
||||
enabled,
|
||||
});
|
||||
@ -275,7 +288,7 @@ function DateKeeper(props) {
|
||||
* @param {Array<Number>} weekends
|
||||
* @return {Array<Object>}
|
||||
* {
|
||||
* {Number} textContent
|
||||
* {Number} content
|
||||
* {Array<String>} classNames
|
||||
* }
|
||||
*/
|
||||
@ -285,7 +298,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 +331,7 @@ function DateKeeper(props) {
|
||||
* @return {Date}
|
||||
*/
|
||||
_newUTCDate(...parts) {
|
||||
return new Date(Date.UTC(...parts));
|
||||
}
|
||||
return new Date(new Date(0).setUTCFullYear(...parts));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ function DatePicker(context) {
|
||||
isMonthPickerVisible: false,
|
||||
datetimeOrders: new Intl.DateTimeFormat(locale)
|
||||
.formatToParts(new Date(0)).map(part => part.type),
|
||||
getDayString: new Intl.NumberFormat(locale).format,
|
||||
getDayString: day => day ? new Intl.NumberFormat(locale).format(day) : "",
|
||||
getWeekHeaderString: weekday => weekdayStrings[weekday],
|
||||
getMonthString: month => monthStrings[month],
|
||||
setSelection: date => {
|
||||
@ -109,7 +109,10 @@ function DatePicker(context) {
|
||||
this.components = {
|
||||
calendar: new Calendar({
|
||||
calViewSize: CAL_VIEW_SIZE,
|
||||
locale: this.state.locale
|
||||
locale: this.state.locale,
|
||||
setSelection: this.state.setSelection,
|
||||
getDayString: this.state.getDayString,
|
||||
getWeekHeaderString: this.state.getWeekHeaderString
|
||||
}, {
|
||||
weekHeader: this.context.weekHeader,
|
||||
daysView: this.context.daysView
|
||||
@ -150,10 +153,7 @@ function DatePicker(context) {
|
||||
this.components.calendar.setProps({
|
||||
isVisible: !isMonthPickerVisible,
|
||||
days: this.state.days,
|
||||
weekHeaders: dateKeeper.state.weekHeaders,
|
||||
setSelection: this.state.setSelection,
|
||||
getDayString: this.state.getDayString,
|
||||
getWeekHeaderString: this.state.getWeekHeaderString
|
||||
weekHeaders: dateKeeper.state.weekHeaders
|
||||
});
|
||||
|
||||
isMonthPickerVisible ?
|
||||
@ -263,8 +263,8 @@ function DatePicker(context) {
|
||||
set({ year, month, day }) {
|
||||
const { dateKeeper } = this.state;
|
||||
|
||||
dateKeeper.set({
|
||||
year, month, day
|
||||
dateKeeper.setCalendarMonth({
|
||||
year, month
|
||||
});
|
||||
dateKeeper.setSelection({
|
||||
year, month, day
|
||||
|
Loading…
x
Reference in New Issue
Block a user