Use just like the regular DatePicker. Currently only uses a fixed locale.
-
-
Save julianwachholz/abec7b951508cce1b1e621b6afb65f70 to your computer and use it in GitHub Desktop.
import generatePicker from "antd/lib/date-picker/generatePicker"; | |
import { | |
addDays, | |
addMonths, | |
addYears, | |
format, | |
getWeek, | |
isAfter, | |
isValid, | |
parse, | |
setDate, | |
setHours, | |
setMinutes, | |
setMonth, | |
setSeconds, | |
setYear | |
} from "date-fns"; | |
import { enUS } from "date-fns/locale"; | |
import { GenerateConfig } from "rc-picker/lib/generate"; | |
const generateConfig: GenerateConfig<Date> = { | |
getNow: () => new Date(), | |
getWeekDay: date => date.getDay(), | |
getYear: date => date.getFullYear(), | |
getMonth: date => date.getMonth(), | |
getDate: date => date.getDate(), | |
getHour: date => date.getHours(), | |
getMinute: date => date.getMinutes(), | |
getSecond: date => date.getSeconds(), | |
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), | |
isAfter: (date1, date2) => isAfter(date1, date2), | |
isValidate: date => isValid(date), | |
locale: { | |
getWeekFirstDay: locale => 1, | |
getWeek: (locale, date) => getWeek(date), | |
getShortWeekDays: locale => { | |
const d = Array.from({ length: 7 }).map((_, day) => { | |
console.info("localize day ", day); | |
return enUS.localize.day(day, { width: "abbreviated" }); | |
}); | |
console.log("days", d); | |
return d; | |
}, | |
getShortMonths: locale => { | |
const m = Array.from({ length: 12 }).map((_, month) => | |
enUS.localize.month(month, { width: "abbreviated" }) | |
); | |
console.log("months", m); | |
return m; | |
}, | |
format: (locale, date, fmt) => { | |
fmt = fmt.replace("YYYY", "yyyy"); | |
fmt = fmt.replace("DD", "dd"); | |
return format(date, fmt, { locale: enUS }); | |
}, | |
parse: (locale, text, formats) => { | |
const fmt = formats[0].toLowerCase(); | |
return parse(text, fmt, new Date()); | |
} | |
} | |
}; | |
const DatePicker = generatePicker<Date>(generateConfig); | |
export default DatePicker; |
Hey, thanks for this 🙏 Did you ever make one with locale switchable on runtime?
Is there no way to pass locale dynamically to ant.design DatePicker on runtime?
import dateFnsGenerateConfig from 'rc-picker/lib/generate/dateFns';
import generatePicker, {
PickerProps,
} from 'antd/es/date-picker/generatePicker';
import 'antd/es/date-picker/style/index';
import {
format,
getWeek, isValid,
Locale as DateFnsLocale,
parse
} from 'date-fns';
import { GenerateConfig } from 'rc-picker/lib/generate';
import React from 'react';
import { dateFnsLocaleDict } from 'i18n/dateFnsLocale';
import useLocale from 'i18n/useLocale';
// NOTE: copy-paste from dateFnsGenerateConfig
function 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');
}
// get localized config
const getLocalizedGenerateConfig = (
dateFnsLocale: DateFnsLocale
): GenerateConfig<Date> => {
return {
...dateFnsGenerateConfig, // date-fns defaults defined by ant.design as a starting point
getFixedDate: (fixed: string) => parse(fixed, 'yyyy-MM-dd', new Date()),
locale: {
getWeekFirstDay: (locale) => dateFnsLocale.options?.weekStartsOn || 1,
getWeekFirstDate: (locale) => new Date(),
getWeek: (locale, date) => getWeek(date),
getShortWeekDays: (locale) => {
return Array.from({ length: 7 }).map((_, day) => {
return dateFnsLocale.localize!.day(day, { width: 'short' });
});
},
getShortMonths: (locale) => {
return Array.from({ length: 12 }).map((_, month) =>
dateFnsLocale.localize!.month(month, { width: 'abbreviated' })
);
},
format: (locale, date, fmt) => {
fmt = fmt.replace('YYYY', 'yyyy');
fmt = fmt.replace('DD', 'dd');
return format(date, fmt, { locale: dateFnsLocale });
},
parse: (locale, text, formats) => {
for (let i = 0; i < formats.length; i += 1) {
const format = localeParse(formats[i]);
const date = parse(text, format, new Date(), {
locale: dateFnsLocale,
weekStartsOn: dateFnsLocale.options?.weekStartsOn
});
if (isValid(date)) {
return date;
}
}
return null;
}
},
};
};
const FnsDatePickerDa = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['da'].locale)
);
const FnsDatePickerDe = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['de'].locale)
);
const FnsDatePickerEt = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['et'].locale)
);
const FnsDatePickerEn = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['en'].locale)
);
const FnsDatePickerFr = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['fr'].locale)
);
const FnsDatePickerNl = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['nl'].locale)
);
const FnsDatePickerNo = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['no'].locale)
);
const FnsDatePickerFi = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['fi'].locale)
);
const FnsDatePickerSv = generatePicker<Date>(
getLocalizedGenerateConfig(dateFnsLocaleDict['sv'].locale)
);
type DateFnsTypedPickerProps = PickerProps<Date>;
/***
* Based on https://ant.design/docs/react/replace-moment#Use-date-fns
* And https://gist.github.com/julianwachholz/abec7b951508cce1b1e621b6afb65f70
* I don't think we can dynamically pass locale to ant.d DatePicker??
* Is there a better way to do this?
*/
const DatePicker: React.FC<DateFnsTypedPickerProps> = (props) => {
const locale = useLocale();
switch (locale.localeKey) {
case 'de':
return <FnsDatePickerDe {...props} />;
case 'da':
return <FnsDatePickerDa {...props} />;
case 'en':
return <FnsDatePickerEn {...props} />;
case 'et':
return <FnsDatePickerEt {...props} />;
case 'fr':
return <FnsDatePickerFr {...props} />;
case 'fi':
return <FnsDatePickerFi {...props} />;
case 'nl':
return <FnsDatePickerNl {...props} />;
case 'no':
return <FnsDatePickerNo {...props} />;
case 'sv':
return <FnsDatePickerSv {...props} />;
}
};
export default DatePicker;
dateFnsLocale.ts:
import { Locale as DateFnsLocale } from 'date-fns';
import { da, de, enUS, et, fi, fr, nb, nl, sv } from 'date-fns/locale';
export const dateFnsLocaleDict: {
[code: string]: {
locale: DateFnsLocale;
key: 'da' | 'de' | 'ed' | 'enUS' | 'fr' | 'nl' | 'nb' | 'fi' | 'sv';
};
} = {
da: { locale: da, key: 'da' },
de: { locale: de, key: 'de' },
et: { locale: et, key: 'ed' },
en: { locale: enUS, key: 'enUS' },
fr: { locale: fr, key: 'fr' },
nl: { locale: nl, key: 'nl' },
no: { locale: nb, key: 'nb' },
fi: { locale: fi, key: 'fi' },
sv: { locale: sv, key: 'sv' },
};
// Used in webpack config to reduce bundle size:
// https://date-fns.org/v2.24.0/docs/webpack
export const supportedDateFnsLocales = Object.values(dateFnsLocaleDict).map(
(c) => c.key
);
There are switching to DayJS with antd v5 - maybe that will solve all the problems.
ant-design/ant-design#33862
@thomastvedt For some reason your solution made my bundle grow by nearly 1MB even though I only added 2 languages.
It seems that dateFnsGenerateConfig is to blame.
The solutions above have a serious drawback - when entering an invalid date (for example, if you type a space in input field), the DatePicker crashes with the entire app.
We can fix this by referencing antd implementation: https://github.com/react-component/picker/blob/master/src/generate/dateFns.ts.
We can also modify the locale field so that the picker explicitly works with a specific locale:
import generatePicker from 'antd/lib/date-picker/generatePicker'
import type { GenerateConfig } from 'rc-picker/lib/generate'
import config from 'rc-picker/lib/generate/dateFns'
import {
isValid,
getWeek,
startOfWeek,
format as formatDate,
parse as parseDate
} from 'date-fns'
import { ru } from 'date-fns/locale'
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 locale: GenerateConfig<Date>['locale'] = {
getWeekFirstDay: () => {
const clone = ru
return clone.options?.weekStartsOn ?? 1
},
getWeekFirstDate: (locale, date) => {
return startOfWeek(date, { locale: ru })
},
getWeek: (locale, date) => {
return getWeek(date, { locale: ru })
},
getShortWeekDays: () => {
const clone = ru
return Array.from({ length: 7 }).map((_, i) =>
clone.localize?.day(i, { width: 'short' })
)
},
getShortMonths: () => {
const clone = ru
return Array.from({ length: 12 }).map((_, i) =>
clone.localize?.month(i, { width: 'abbreviated' })
)
},
format: (locale, date, format) => {
if (!isValid(date)) {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
return null as any
}
return formatDate(date, localeParse(format), {
locale: ru
})
},
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: ru
})
if (isValid(date)) {
return date
}
}
return null
}
}
export const DatePicker = generatePicker<Date>({ ...config, locale })
Updated version for [email protected]