diff --git a/components/vc-picker/generate/dataFns.ts b/components/vc-picker/generate/dataFns.ts new file mode 100644 index 000000000..196f8d7ec --- /dev/null +++ b/components/vc-picker/generate/dataFns.ts @@ -0,0 +1,115 @@ +import { + getDay, + getYear, + getMonth, + getDate, + endOfMonth, + getHours, + getMinutes, + getSeconds, + addYears, + addMonths, + addDays, + setYear, + setMonth, + setDate, + setHours, + setMinutes, + setSeconds, + isAfter, + isValid, + getWeek, + startOfWeek, + format as formatDate, + parse as parseDate, +} from 'date-fns'; +import * as Locale from 'date-fns/locale'; +import type { GenerateConfig } from '.'; + +const dealLocal = (str: string) => { + return str.replace(/_/g, ''); +}; + +const localeParse = (format: string) => { + return format + .replace(/Y/g, 'y') + .replace(/D/g, 'd') + .replace(/gggg/, 'yyyy') + .replace(/g/g, 'G') + .replace(/([Ww])o/g, 'wo'); +}; + +const generateConfig: GenerateConfig = { + // get + getNow: () => new Date(), + getFixedDate: string => new Date(string), + getEndDate: date => endOfMonth(date), + getWeekDay: date => getDay(date), + getYear: date => getYear(date), + getMonth: date => getMonth(date), + getDate: date => getDate(date), + getHour: date => getHours(date), + getMinute: date => getMinutes(date), + getSecond: date => getSeconds(date), + + // set + addYear: (date, diff) => addYears(date, diff), + addMonth: (date, diff) => addMonths(date, diff), + addDate: (date, diff) => addDays(date, diff), + setYear: (date, year) => setYear(date, year), + setMonth: (date, month) => setMonth(date, month), + setDate: (date, num) => setDate(date, num), + setHour: (date, hour) => setHours(date, hour), + setMinute: (date, minute) => setMinutes(date, minute), + setSecond: (date, second) => setSeconds(date, second), + + // Compare + isAfter: (date1, date2) => isAfter(date1, date2), + isValidate: date => isValid(date), + + locale: { + getWeekFirstDay: locale => { + const clone = Locale[dealLocal(locale)]; + return clone.options.weekStartsOn; + }, + getWeekFirstDate: (locale, date) => { + return startOfWeek(date, { locale: Locale[dealLocal(locale)] }); + }, + getWeek: (locale, date) => { + return getWeek(date, { locale: Locale[dealLocal(locale)] }); + }, + getShortWeekDays: locale => { + const clone = Locale[dealLocal(locale)]; + return Array.from({ length: 7 }).map((_, i) => clone.localize.day(i, { width: 'short' })); + }, + getShortMonths: locale => { + const clone = Locale[dealLocal(locale)]; + return Array.from({ length: 12 }).map((_, i) => + clone.localize.month(i, { width: 'abbreviated' }), + ); + }, + format: (locale, date, format) => { + if (!isValid(date)) { + return null; + } + return formatDate(date, localeParse(format), { + locale: Locale[dealLocal(locale)], + }); + }, + parse: (locale, text, formats) => { + for (let i = 0; i < formats.length; i += 1) { + const format = localeParse(formats[i]); + const formatText = text; + const date = parseDate(formatText, format, new Date(), { + locale: Locale[dealLocal(locale)], + }); + if (isValid(date)) { + return date; + } + } + return null; + }, + }, +}; + +export default generateConfig; diff --git a/components/vc-picker/generate/dayjs.ts b/components/vc-picker/generate/dayjs.ts new file mode 100644 index 000000000..a470268ee --- /dev/null +++ b/components/vc-picker/generate/dayjs.ts @@ -0,0 +1,132 @@ +import type { Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; +import weekday from 'dayjs/plugin/weekday'; +import localeData from 'dayjs/plugin/localeData'; +import weekOfYear from 'dayjs/plugin/weekOfYear'; +import weekYear from 'dayjs/plugin/weekYear'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import type { GenerateConfig } from '.'; +import { noteOnce } from 'ant-design-vue/es/vc-util/warning'; + +dayjs.extend(customParseFormat); +dayjs.extend(advancedFormat); +dayjs.extend(weekday); +dayjs.extend(localeData); +dayjs.extend(weekOfYear); +dayjs.extend(weekYear); + +dayjs.extend((o, c) => { + // todo support Wo (ISO week) + const proto = c.prototype; + const oldFormat = proto.format; + proto.format = function f(formatStr: string) { + const str = (formatStr || '').replace('Wo', 'wo'); + return oldFormat.bind(this)(str); + }; +}); + +type IlocaleMapObject = Record; +const localeMap: IlocaleMapObject = { + en_GB: 'en-gb', + en_US: 'en', + zh_CN: 'zh-cn', + zh_TW: 'zh-tw', +}; + +const parseLocale = (locale: string) => { + const mapLocale = localeMap[locale]; + return mapLocale || locale.split('_')[0]; +}; + +const parseNoMatchNotice = () => { + /* istanbul ignore next */ + noteOnce(false, 'Not match any format. Please help to fire a issue about this.'); +}; + +const generateConfig: GenerateConfig = { + // get + getNow: () => dayjs(), + getFixedDate: string => dayjs(string, 'YYYY-MM-DD'), + getEndDate: date => date.endOf('month'), + getWeekDay: date => { + const clone = date.locale('en'); + return clone.weekday() + clone.localeData().firstDayOfWeek(); + }, + getYear: date => date.year(), + getMonth: date => date.month(), + getDate: date => date.date(), + getHour: date => date.hour(), + getMinute: date => date.minute(), + getSecond: date => date.second(), + + // set + addYear: (date, diff) => date.add(diff, 'year'), + addMonth: (date, diff) => date.add(diff, 'month'), + addDate: (date, diff) => date.add(diff, 'day'), + setYear: (date, year) => date.year(year), + setMonth: (date, month) => date.month(month), + setDate: (date, num) => date.date(num), + setHour: (date, hour) => date.hour(hour), + setMinute: (date, minute) => date.minute(minute), + setSecond: (date, second) => date.second(second), + + // Compare + isAfter: (date1, date2) => date1.isAfter(date2), + isValidate: date => date.isValid(), + + locale: { + getWeekFirstDay: locale => + dayjs() + .locale(parseLocale(locale)) + .localeData() + .firstDayOfWeek(), + getWeekFirstDate: (locale, date) => date.locale(parseLocale(locale)).weekday(0), + getWeek: (locale, date) => date.locale(parseLocale(locale)).week(), + getShortWeekDays: locale => + dayjs() + .locale(parseLocale(locale)) + .localeData() + .weekdaysMin(), + getShortMonths: locale => + dayjs() + .locale(parseLocale(locale)) + .localeData() + .monthsShort(), + format: (locale, date, format) => date.locale(parseLocale(locale)).format(format), + parse: (locale, text, formats) => { + const localeStr = parseLocale(locale); + for (let i = 0; i < formats.length; i += 1) { + const format = formats[i]; + const formatText = text; + if (format.includes('wo') || format.includes('Wo')) { + // parse Wo + const year = formatText.split('-')[0]; + const weekStr = formatText.split('-')[1]; + const firstWeek = dayjs(year, 'YYYY') + .startOf('year') + .locale(localeStr); + for (let j = 0; j <= 52; j += 1) { + const nextWeek = firstWeek.add(j, 'week'); + if (nextWeek.format('Wo') === weekStr) { + return nextWeek; + } + } + parseNoMatchNotice(); + return null; + } + const date = dayjs(formatText, format).locale(localeStr); + if (date.isValid()) { + return date; + } + } + + if (text) { + parseNoMatchNotice(); + } + return null; + }, + }, +}; + +export default generateConfig; diff --git a/components/vc-picker/generate/index.ts b/components/vc-picker/generate/index.ts new file mode 100644 index 000000000..b0cd56d5e --- /dev/null +++ b/components/vc-picker/generate/index.ts @@ -0,0 +1,44 @@ +export type GenerateConfig = { + // Get + getWeekDay: (value: DateType) => number; + getSecond: (value: DateType) => number; + getMinute: (value: DateType) => number; + getHour: (value: DateType) => number; + getDate: (value: DateType) => number; + getMonth: (value: DateType) => number; + getYear: (value: DateType) => number; + getNow: () => DateType; + getFixedDate: (fixed: string) => DateType; + getEndDate: (value: DateType) => DateType; + + // Set + addYear: (value: DateType, diff: number) => DateType; + addMonth: (value: DateType, diff: number) => DateType; + addDate: (value: DateType, diff: number) => DateType; + setYear: (value: DateType, year: number) => DateType; + setMonth: (value: DateType, month: number) => DateType; + setDate: (value: DateType, date: number) => DateType; + setHour: (value: DateType, hour: number) => DateType; + setMinute: (value: DateType, minute: number) => DateType; + setSecond: (value: DateType, second: number) => DateType; + + // Compare + isAfter: (date1: DateType, date2: DateType) => boolean; + isValidate: (date: DateType) => boolean; + + locale: { + getWeekFirstDay: (locale: string) => number; + getWeekFirstDate: (locale: string, value: DateType) => DateType; + getWeek: (locale: string, value: DateType) => number; + + format: (locale: string, date: DateType, format: string) => string; + + /** Should only return validate date instance */ + parse: (locale: string, text: string, formats: string[]) => DateType | null; + + /** A proxy for getting locale with moment or other locale library */ + getShortWeekDays?: (locale: string) => string[]; + /** A proxy for getting locale with moment or other locale library */ + getShortMonths?: (locale: string) => string[]; + }; +}; diff --git a/components/vc-picker/generate/moment.ts b/components/vc-picker/generate/moment.ts new file mode 100644 index 000000000..238764edc --- /dev/null +++ b/components/vc-picker/generate/moment.ts @@ -0,0 +1,140 @@ +import { noteOnce } from 'ant-design-vue/es/vc-util/warning'; +import type { Moment } from 'moment'; +import moment from 'moment'; +import type { GenerateConfig } from '.'; + +const generateConfig: GenerateConfig = { + // get + getNow: () => moment(), + getFixedDate: string => moment(string, 'YYYY-MM-DD'), + getEndDate: date => { + const clone = date.clone(); + return clone.endOf('month'); + }, + getWeekDay: date => { + const clone = date.clone().locale('en_US'); + return clone.weekday() + clone.localeData().firstDayOfWeek(); + }, + getYear: date => date.year(), + getMonth: date => date.month(), + getDate: date => date.date(), + getHour: date => date.hour(), + getMinute: date => date.minute(), + getSecond: date => date.second(), + + // set + addYear: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'year'); + }, + addMonth: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'month'); + }, + addDate: (date, diff) => { + const clone = date.clone(); + return clone.add(diff, 'day'); + }, + setYear: (date, year) => { + const clone = date.clone(); + return clone.year(year); + }, + setMonth: (date, month) => { + const clone = date.clone(); + return clone.month(month); + }, + setDate: (date, num) => { + const clone = date.clone(); + return clone.date(num); + }, + setHour: (date, hour) => { + const clone = date.clone(); + return clone.hour(hour); + }, + setMinute: (date, minute) => { + const clone = date.clone(); + return clone.minute(minute); + }, + setSecond: (date, second) => { + const clone = date.clone(); + return clone.second(second); + }, + + // Compare + isAfter: (date1, date2) => date1.isAfter(date2), + isValidate: date => date.isValid(), + + locale: { + getWeekFirstDay: locale => { + const date = moment().locale(locale); + return date.localeData().firstDayOfWeek(); + }, + getWeekFirstDate: (locale, date) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.weekday(0); + }, + getWeek: (locale, date) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.week(); + }, + getShortWeekDays: locale => { + const date = moment().locale(locale); + return date.localeData().weekdaysMin(); + }, + getShortMonths: locale => { + const date = moment().locale(locale); + return date.localeData().monthsShort(); + }, + format: (locale, date, format) => { + const clone = date.clone(); + const result = clone.locale(locale); + return result.format(format); + }, + parse: (locale, text, formats) => { + const fallbackFormatList: string[] = []; + + for (let i = 0; i < formats.length; i += 1) { + let format = formats[i]; + let formatText = text; + + if (format.includes('wo') || format.includes('Wo')) { + format = format.replace(/wo/g, 'w').replace(/Wo/g, 'W'); + const matchFormat = format.match(/[-YyMmDdHhSsWwGg]+/g); + const matchText = formatText.match(/[-\d]+/g); + + if (matchFormat && matchText) { + format = matchFormat.join(''); + formatText = matchText.join(''); + } else { + fallbackFormatList.push(format.replace(/o/g, '')); + } + } + + const date = moment(formatText, format, locale, true); + if (date.isValid()) { + return date; + } + } + + // Fallback to fuzzy matching, this should always not reach match or need fire a issue + for (let i = 0; i < fallbackFormatList.length; i += 1) { + const date = moment(text, fallbackFormatList[i], locale, false); + + /* istanbul ignore next */ + if (date.isValid()) { + noteOnce( + false, + 'Not match any format strictly and fallback to fuzzy match. Please help to fire a issue about this.', + ); + return date; + } + } + + return null; + }, + }, +}; + +export default generateConfig; diff --git a/package.json b/package.json index 94e3ee0a8..1ec273baf 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,8 @@ "@simonwep/pickr": "~1.8.0", "array-tree-filter": "^2.1.0", "async-validator": "^3.3.0", + "date-fns": "^2.22.1", + "dayjs": "^1.10.5", "dom-align": "^1.12.1", "dom-scroll-into-view": "^2.0.0", "lodash": "^4.17.21", diff --git a/v2-doc b/v2-doc index 6819090fb..0f6d531d0 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 6819090fbcc94b248bc761d5f26162f29c04b2ef +Subproject commit 0f6d531d088d5283250c8cec1c7e8be0e0d36a36