UNPKG

react-native-common-date-picker

Version:

An awesome and cross-platform React Native date picker and calendar component for iOS and Android

585 lines (513 loc) 21 kB
import {Dimensions} from 'react-native'; import {getWeekDay, getDaysInMonth, getToday, convertDateToString} from '../utils/dateFormat'; export const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get('window'); export const DEFAULT_MIN_DATE = '2000-1-1default'; export const DEFAULT_MAX_DATE = getToday() + 'default'; export const CALENDAR_DEFAULT_MIN_DATE = '2000-1-1'; export const CALENDAR_DEFAULT_MAX_DATE = getToday(); /** Tool bar **/ export const DEFAULT_CANCEL_TEXT = 'Cancel'; export const DEFAULT_CONFIRM_TEXT = 'Confirm'; export const DEFAULT_TOOL_BAR_POSITION = { TOP: 'top', BOTTOM: 'bottom', }; /** Only for Calendar */ export const DEFAULT_WEEK_ZH = ['日', '一', '二', '三', '四', '五', '六']; /** Only for Calendar */ export const DEFAULT_WEEK_EN = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; export const DEFAULT_MONTH_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; export const DEFAULT_MONTH_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; /** Only for Calendar */ export const DEFAULT_DATE_MARK_TYPE = { ELLIPSE: 'ellipse', SEMIELLIPSE: 'semiellipse', RECTANGLE: 'rectangle', SQUARE: 'square', CIRCLE: 'circle', DOT: 'dot', }; /** * Only for Calendar * * Validate date parameter * @param date A string type or Date type * @returns {string|void | string} Returns a string like "2020-6-15" */ export function validateDate(date: any): string { if (!date) { const errorMsg = 'minDate or maxDate are used but no value set'; __DEV__ && console.error(errorMsg); return getToday(); } return convertDateToString(date); } /** * Only for Calendar * * @param weeks * @param firstDay * @returns {[]} Returns a week array according to the first day. */ export function getWeekDays(weeks: [string], firstDay: number): [string] { let _weeks = []; // Use default week if (weeks === DEFAULT_WEEK_EN || weeks === DEFAULT_WEEK_ZH) { const temWeeks = weeks === DEFAULT_WEEK_EN ? DEFAULT_WEEK_EN : DEFAULT_WEEK_ZH; // Nothing changed if (firstDay === 0) { return temWeeks; } const _pre = []; const _later = []; temWeeks.forEach((day, index) => { if (index < firstDay) { _later.push(day); } else { _pre.push(day); } }); _weeks = _pre.concat(_later); } else { _weeks = weeks; } return _weeks; } /** * Construct dates according to given two dates * @param startDate A string date like '2019-05-20' * @param endDate A string date like '2020-06-02' * @param firstDay * @returns {[]} Return a date array consists of year, month and day such as [{year: 2019, month: 1, days: [1, 2, 3, ...]}, {year: 2019, month: 2, days: [1, 2, 3, ...]}, {year: 2020, month: 6, days: [1, 2, 3, ...]}] */ export function getCalendarDates(startDate: string, endDate: string, firstDay: number) { const startArr = startDate.replace(/\//g, '-').split('-'); const endArr = endDate.replace(/\//g, '-').split('-'); const startYear = +startArr[0]; const endYear = +endArr[0]; const startMonth = +startArr[1]; const endMonth = +endArr[1]; let dates = []; for (let year = startYear; year <= endYear; year++) { if (startYear === endYear) { dates = dates.concat(constructCalendarDates(year, startMonth, endMonth, firstDay)); } else if (year === startYear) { dates = dates.concat(constructCalendarDates(year, startMonth, 12, firstDay)); } else if (year === endYear) { dates = dates.concat(constructCalendarDates(year, 1, endMonth, firstDay)); } else { dates = dates.concat(constructCalendarDates(year, 1, 12, firstDay)); } } return dates; } /** * Returns a date array containing all dates via year, month and day * @param year A numeric value equal to the year * @param startMonth A numeric value equal to the month from 1 to 12 * @param endMonth A numeric value equal to the month from 1 to 12 * @param firstDay * @returns {[]} */ function constructCalendarDates(year: number, startMonth: number, endMonth: number, firstDay: number) { const dates = []; for (let month = startMonth; month <= endMonth; month++) { const obj = {year}; obj.month = month; const days = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]; let weekDay = -1; let maxDayInMonth = -1; weekDay = getWeekDay(`${year}-${month <= 9 ? '0' : ''}${month}-01`) - firstDay; if (weekDay < 0) { weekDay = weekDay + 7; } maxDayInMonth = getDaysInMonth(year, month); for (let wd = 0; wd < weekDay; wd++) { days.unshift(-(wd + 1)); } for (let day = 28; day <= maxDayInMonth; day++) { days.push(day); } obj.days = days; dates.push(obj); } return dates; } /** Only for Date Picker */ export const DATE_PICKER_ROWS = 5; export const DATE_PICKER_ROW_HEIGHT = 35; /** Only for Date Picker */ export const BORDER_LINE_POSITION = { TOP: 'top', MIDDLE: 'middle', BOTTOM: 'bottom', }; /** Only for Date Picker */ export const DATE_TYPE = { YYYY_MM_DD: 'YYYY-MM-DD', MM_DD_YYYY: 'MM-DD-YYYY', DD_MM_YYYY: 'DD-MM-YYYY', YYYY_MM: 'YYYY-MM', MM_YYYY: 'MM-YYYY', MM_DD: 'MM-DD', DD_MM: 'DD-MM', YYYY: 'YYYY', MM: 'MM', DD: 'DD', }; /** Only for Date Picker */ export const DATE_KEY_TYPE = { YEAR: 'year', MONTH: 'month', DAY: 'day', }; /** Only for Date Picker */ export const MONTH_DISPLAY_MODE = { DIGIT: 'digit', EN_SHORT: 'en-short', EN_LONG: 'en-long', }; /** Only for Date Picker */ export function selectDatePickerData(index, data) { return index >= 0 && data.length > index ? data[index].data : []; } /** Only for Date Picker */ export function datePickerListWidth(type: string, width: number | string) { if (!type || typeof type !== 'string' || +width > SCREEN_WIDTH) { return SCREEN_WIDTH / type.split('-').length; } return +width / type.split('-').length; } /** Only for Date Picker */ export function getDatePickerData(type, years, months, days) { const _year = {key: DATE_KEY_TYPE.YEAR, data: years}; const _month = {key: DATE_KEY_TYPE.MONTH, data: months}; const _day = {key: DATE_KEY_TYPE.DAY, data: days}; switch (type) { case DATE_TYPE.YYYY_MM_DD: return [_year, _month, _day]; case DATE_TYPE.MM_DD_YYYY: return [_month, _day, _year]; case DATE_TYPE.DD_MM_YYYY: return [_day, _month, _year]; case DATE_TYPE.YYYY_MM: return [_year, _month]; case DATE_TYPE.MM_YYYY: return [_month, _year]; case DATE_TYPE.MM_DD: return [_month, _day]; case DATE_TYPE.DD_MM: return [_day, _month]; case DATE_TYPE.YYYY: return [_year]; case DATE_TYPE.MM: return [_month]; case DATE_TYPE.DD: return [_day]; default: return [_year, _month, _day]; } } /** Only for Date Picker */ export function getDatePickerInitialData(initialProps) { const { type, defaultDate, minDate, maxDate, monthDisplayMode, } = initialProps; const _verifyDate = (date, des) => { const useDefault = (typeof date === 'string' && date.indexOf('default') > 0) || (date instanceof Date); let aDate = date; if (!date) aDate = getToday(); if (date instanceof Date) aDate = date.toISOString().slice(0, 10); aDate = aDate.replace(/\//g, '-'); // Replace "/" with "-". aDate = aDate.replace(/default/g, ''); // Replace "default" with "". const _y = '2000', _m = '1', _d = '1'; const _printError = () => !useDefault && console.warn(`The date type you selected is: ${type}, but ${des} has incorrect date format: ${aDate}`); switch (type) { case DATE_TYPE.YYYY: __DEV__ && aDate.length !== 4 && _printError(); return `${aDate}-${_m}-${_d}`; case DATE_TYPE.MM: __DEV__ && (aDate.length > 2 || +aDate < 1 || +aDate > 12) && _printError(); return `${_y}-${aDate}-${_d}`; case DATE_TYPE.DD: __DEV__ && (aDate.length > 2 || +aDate < 1 || +aDate > 31) && _printError(); return `${_y}-${_m}-${aDate}`; case DATE_TYPE.YYYY_MM: case DATE_TYPE.MM_YYYY: __DEV__ && (aDate.length > 7 || aDate.length < 6) && _printError(); return `${aDate}-${_d}`; case DATE_TYPE.MM_DD: case DATE_TYPE.DD_MM: __DEV__ && (aDate.length < 3 || aDate.length > 5) && _printError(); return `${_y}-${aDate}`; default: return aDate; } }; const _maxDate = _verifyDate(maxDate, 'maxDate'); // If defaultDate doest not exist, default is maxDate const _defaultDate = _verifyDate(defaultDate || maxDate, 'defaultDate'); const _minDate = _verifyDate(minDate, 'minDate'); // maxDate must be greater or equal to defaultDate, then defaultDate must be greater or equal to minDate assertDate(_maxDate, _minDate, _defaultDate); // The default selected date for the first time const _defaultDates = _defaultDate.split('-'); const initialSelectedDate = getDatePickerInitSelectDate(type, _defaultDates); // Validate whether the index is legal const legalIndex = (index, data) => index < 0 ? (data.length - 1) : index; // Years const dates = constructDatePickerDates(monthDisplayMode, _minDate, _maxDate); const yearIndex = dates.findIndex(item => item.date === +_defaultDates[0]); const defaultYearIndex = legalIndex(yearIndex, dates); // Months const months = selectDatePickerData(defaultYearIndex, dates); const monthIndex = months.findIndex(item => item.date === getDatePickerMonth(monthDisplayMode, +_defaultDates[1])); const defaultMonthIndex = legalIndex(monthIndex, months); // Days const days = selectDatePickerData(defaultMonthIndex, months); const dayIndex = days.findIndex(item => item.date === +_defaultDates[2]); const defaultDayIndex = legalIndex(dayIndex, days); return { years: dates, months, days, defaultYearIndex, defaultMonthIndex, defaultDayIndex, ...initialSelectedDate, isDefaultDateChanged: false, }; } /** Only for Date Picker */ function getDatePickerInitSelectDate(type: string, initialDefaultDates: Array) { const _year = initialDefaultDates[0]; const _month = initialDefaultDates[1]; const _day = initialDefaultDates[2]; switch (type) { case DATE_TYPE.YYYY_MM_DD: case DATE_TYPE.MM_DD_YYYY: case DATE_TYPE.DD_MM_YYYY: return {selectedYear: _year, selectedMonth: _month, selectedDay: _day}; case DATE_TYPE.YYYY_MM: case DATE_TYPE.MM_YYYY: return {selectedYear: _year, selectedMonth: _month, selectedDay: ''}; case DATE_TYPE.MM_DD: case DATE_TYPE.DD_MM: return {selectedYear: '', selectedMonth: _month, selectedDay: _day}; case DATE_TYPE.YYYY: return {selectedYear: _year, selectedMonth: '', selectedDay: ''}; case DATE_TYPE.MM: return {selectedYear: '', selectedMonth: _month, selectedDay: ''}; case DATE_TYPE.DD: return {selectedYear: '', selectedMonth: '', selectedDay: _day}; default: return {selectedYear: _year, selectedMonth: _month, selectedDay: _day}; } } /** * Only for Date Picker * * Construct dates according to given two dates * @param monthDisplayMode * @param startDate A string date like '2019-05-20' * @param endDate A string date like '2020-06-02' * @returns {[]} Return a date array consists of year, month and day such as [{date: 2019, data: [{date: 6, data: [{date: 1}, {date: 2}]}]. */ function constructDatePickerDates(monthDisplayMode: string, startDate: string, endDate: string) { const startArr = startDate.split('-'); const endArr = endDate.split('-'); const startYear = +startArr[0]; const endYear = +endArr[0]; const startMonth = +startArr[1]; const endMonth = +endArr[1]; const startDay = +startArr[2]; const endDay = +endArr[2]; const dates = []; for (let year = startYear; year <= endYear; year++) { const yearObj = {date: year}; let _startMonth = startMonth; let _endMonth = endMonth; if (startYear === endYear) { // Only one year // Do nothing. Use the default value above } else if (year === startYear) { // At least two years _endMonth = 12; } else if (year === endYear) { // At least two years _startMonth = 1; } else { // At least three years _startMonth = 1; _endMonth = 12; } const months = []; for (let month = _startMonth; month <= _endMonth; month++) { const monthObj = {date: getDatePickerMonth(monthDisplayMode, month)}; // Get the days in a month const maxDay = getDaysInMonth(year, month); let _startDay = startDay; let _endDay = endDay; if (year > startYear && year < endYear) { _startDay = 1; _endDay = maxDay; } else { if (_startMonth === _endMonth) { _startDay = startYear === endYear || year === startYear ? startDay : 1; _endDay = startYear === endYear || year === endYear ? endDay : maxDay; } else if (month === _startMonth) { _startDay = startYear === endYear || year === startYear ? startDay : 1; _endDay = maxDay; } else if (month === _endMonth) { _startDay = 1; _endDay = startYear === endYear || year === endYear ? endDay : maxDay; } else { _startDay = 1; _endDay = maxDay; } } const days = []; for (let day = _startDay; day <= _endDay; day++) { const dayObj = {date: day}; days.push(dayObj); } monthObj.data = days; months.push(monthObj); } yearObj.data = months; dates.push(yearObj); } return dates; } /** * Only for Date Picker * * Returns the month's displaying text based on both the month given from 1 to 12 and month display mode. * @param monthDisplayMode A string type representing the display mode of month * @param month A numeric value representing a specific month * @returns number or string type */ function getDatePickerMonth(monthDisplayMode: string, month: number) { if (Object.values(MONTH_DISPLAY_MODE).indexOf(monthDisplayMode) < 0) return month; switch (monthDisplayMode) { case MONTH_DISPLAY_MODE.DIGIT: return month; case MONTH_DISPLAY_MODE.EN_SHORT: return DEFAULT_MONTH_SHORT[month - 1]; case MONTH_DISPLAY_MODE.EN_LONG: return DEFAULT_MONTH_LONG[month - 1]; default: return month; } } /** * Only for Date Picker * * Assert date parameters through comparing them. * @param maxDate A string * @param minDate A string * @param defaultDate A string */ function assertDate(maxDate: string, minDate: string, defaultDate: string) { let errorMsg = ''; if (!greaterThanOrEqualTo(defaultDate, minDate)) { errorMsg = `Error! defaultDate must be greater than or equal to minDate! But defaultDate: ${defaultDate} is less than minDate: ${minDate}`; } if (!greaterThanOrEqualTo(maxDate, defaultDate)) { errorMsg = `Error! maxDate must be greater than or equal to defaultDate! But maxDate: ${maxDate} is less than defaultDate: ${defaultDate}`; } if (!greaterThanOrEqualTo(maxDate, minDate)) { errorMsg = `Error! maxDate must be greater than or equal to minDate! But maxDate: ${maxDate} is less than minDate: ${minDate}`; } __DEV__ && errorMsg && errorMsg.length && console.error(errorMsg); } /** * Only for CalendarList. * Returns a date object like {startDate: "2021-6-7", endDate: "2021-7-7"}. * @param dates * @returns {{endDate: string, startDate: string}} */ export function calculateCalendarDefaultDates(dates: Array) { let startDate = ''; let endDate = ''; if (dates && Array.isArray(dates) && dates.length === 2) { const sd = convertDateToString(dates[0]).split('-'); const ed = convertDateToString(dates[1]).split('-'); if (sd.length === 3 || ed.length === 3) { // Remove prefix "0". For example, substitute "2021-06-07" with "2021-6-7". startDate = `${+sd[0]}-${+sd[1]}-${+sd[2]}`; endDate = `${+ed[0]}-${+ed[1]}-${+ed[2]}`; } } return { startDate, endDate, }; } /** * NOTE: Bad performance for Date.parse(startDate.replace(/-/g, '/')) < Date.parse(_d.replace(/-/g, '/')). So using the following * comparison method can greatly improve performance. If interested, you can have a try. * * Compare two date. If date > another, returns true, else false. * @param date A string type representing a date like "2020-1-10" * @param another A string type representing a date like "2019-06-15" * @returns {boolean} */ export function greaterThan(date: string, another: string) { if (!date || typeof date !== 'string' || !another || typeof another !== 'string') return false; const dateArr = date.replace(/\//g, '-').split('-'); const anotherArr = another.replace(/\//g, '-').split('-'); if (parseInt(dateArr[0]) > parseInt(anotherArr[0])) return true; if (parseInt(dateArr[0]) === parseInt(anotherArr[0])) { if (parseInt(dateArr[1]) > parseInt(anotherArr[1])) return true; if (parseInt(dateArr[1]) === parseInt(anotherArr[1])) return parseInt(dateArr[2]) > parseInt(anotherArr[2]); // return day1 > day2 } return false; } /** * Compare two date. If date >= another, returns true, else false. * @param date A string type representing a date * @param another A string type representing a date * @returns {boolean} */ export function greaterThanOrEqualTo(date: string, another: string) { if (!date || typeof date !== 'string' || !another || typeof another !== 'string') return false; if (date === another) return true; const dateArr = date.replace(/\//g, '-').split('-'); const anotherArr = another.replace(/\//g, '-').split('-'); if (parseInt(dateArr[0]) > parseInt(anotherArr[0])) return true; if (parseInt(dateArr[0]) === parseInt(anotherArr[0])) { if (parseInt(dateArr[1]) > parseInt(anotherArr[1])) return true; if (parseInt(dateArr[1]) === parseInt(anotherArr[1])) return parseInt(dateArr[2]) >= parseInt(anotherArr[2]); } return false; } /** * Convert a date string into a standard date string. For example, your can convert "2020-1-6" into "2020-01-06". * Note: "2020-June-1" will be converted into "2020-06-01", "-June-1" into "06-01", "-June-" into "06" that represents * type={"MM"}. In the same way, if type={"DD"}, "--5" will be converted into "05". * @param monthDisplayMode * @param date a date string to be converted. */ export function toStandardStringWith(date: string, monthDisplayMode: string = ''): string { if (!date || typeof date !== 'string') return date; const dates = date.replace(/\//g, '-').split('-'); if (dates.length !== 3) { __DEV__ && console.warn('Sorry! date string format is incorrect!'); return date; } let year = dates[0]; let day = dates[2]; let month = dates[1]; if (monthDisplayMode === MONTH_DISPLAY_MODE.EN_SHORT) { month = DEFAULT_MONTH_SHORT.indexOf(month) >= 0 ? (DEFAULT_MONTH_SHORT.indexOf(month) + 1).toString() : month; } if (monthDisplayMode === MONTH_DISPLAY_MODE.EN_LONG) { month = DEFAULT_MONTH_LONG.indexOf(month) >= 0 ? (DEFAULT_MONTH_LONG.indexOf(month) + 1).toString() : month; } year = year.length ? (year + (month.length ? '-' : '')) : ''; const monthDash = day.length ? '-' : ''; month = month.length ? (month.length === 1 ? `0${month}${monthDash}` : `${month}${monthDash}`) : ''; day = day.length ? (day.length === 1 ? `0${day}` : `${day}`) : ''; return year + month + day; }