import dateFnsFormat from 'date-fns/format'; import dateFnsParse from 'date-fns/parse'; import dateFnsIsValid from 'date-fns/isValid'; import { extractDateParts, parseDate as _parseDate } from '@vaadin/date-picker/src/vaadin-date-picker-helper.js'; (function () { const tryCatchWrapper = function (callback) { return window.Vaadin.Flow.tryCatchWrapper(callback, 'Vaadin Date Picker'); }; window.Vaadin.Flow.datepickerConnector = { initLazy: (datepicker) => tryCatchWrapper(function (datepicker) { // Check whether the connector was already initialized for the datepicker if (datepicker.$connector) { return; } datepicker.$connector = {}; const createLocaleBasedDateFormat = function (locale) { try { // Check whether the locale is supported or not new Date().toLocaleDateString(locale); } catch (e) { console.warn('The locale is not supported, using default format setting (ISO 8601).'); return 'yyyy-MM-dd'; } // format test date and convert to date-fns pattern const testDate = new Date(Date.UTC(1234, 4, 6)); let pattern = testDate.toLocaleDateString(locale, { timeZone: 'UTC' }); pattern = pattern // escape date-fns pattern letters by enclosing them in single quotes .replace(/([a-zA-Z]+)/g, "'$1'") // insert date placeholder .replace('06', 'dd') .replace('6', 'd') // insert month placeholder .replace('05', 'MM') .replace('5', 'M') // insert year placeholder .replace('1234', 'yyyy'); const isValidPattern = pattern.includes('d') && pattern.includes('M') && pattern.includes('y'); if (!isValidPattern) { console.warn('The locale is not supported, using default format setting (ISO 8601).'); return 'yyyy-MM-dd'; } return pattern; }; const createFormatterAndParser = tryCatchWrapper(function (formats) { if (!formats || formats.length === 0) { throw new Error('Array of custom date formats is null or empty'); } function getShortYearFormat(format) { if (format.includes('yyyy') && !format.includes('yyyyy')) { return format.replace('yyyy', 'yy'); } if (format.includes('YYYY') && !format.includes('YYYYY')) { return format.replace('YYYY', 'YY'); } return undefined; } function isFormatWithYear(format) { return format.includes('y') || format.includes('Y'); } function isShortYearFormat(format) { // Format is long if it includes a four-digit year. return !format.includes('yyyy') && !format.includes('YYYY'); } function getExtendedFormats(formats) { return formats.reduce((acc, format) => { // We first try to match the date with the shorter version, // as short years are supported with the long date format. if (isFormatWithYear(format) && !isShortYearFormat(format)) { acc.push(getShortYearFormat(format)); } acc.push(format); return acc; }, []); } function correctFullYear(date) { // The last parsed date check handles the case where a four-digit year is parsed, then formatted // as a two-digit year, and then parsed again. In this case we want to keep the century of the // originally parsed year, instead of using the century of the reference date. // Do not apply any correction if the previous parse attempt was failed. if (datepicker.$connector._lastParseStatus === 'error') { return; } // Update century if the last parsed date is the same except the century. if (datepicker.$connector._lastParseStatus === 'successful') { if ( datepicker.$connector._lastParsedDate.day === date.getDate() && datepicker.$connector._lastParsedDate.month === date.getMonth() && datepicker.$connector._lastParsedDate.year % 100 === date.getFullYear() % 100 ) { date.setFullYear(datepicker.$connector._lastParsedDate.year); } return; } // Update century if this is the first parse after overlay open. const currentValue = _parseDate(datepicker.value); if ( dateFnsIsValid(currentValue) && currentValue.getDate() === date.getDate() && currentValue.getMonth() === date.getMonth() && currentValue.getFullYear() % 100 === date.getFullYear() % 100 ) { date.setFullYear(currentValue.getFullYear()); } } function formatDate(dateParts) { const format = formats[0]; const date = _parseDate(`${dateParts.year}-${dateParts.month + 1}-${dateParts.day}`); return dateFnsFormat(date, format); } function doParseDate(dateString, format, referenceDate) { // When format does not contain a year, then current year should be used. const refDate = isFormatWithYear(format) ? referenceDate : new Date(); const date = dateFnsParse(dateString, format, refDate); if (dateFnsIsValid(date)) { if (isFormatWithYear(format) && isShortYearFormat(format)) { correctFullYear(date); } return { day: date.getDate(), month: date.getMonth(), year: date.getFullYear() }; } } function parseDate(dateString) { const referenceDate = _getReferenceDate(); for (let format of getExtendedFormats(formats)) { const parsedDate = doParseDate(dateString, format, referenceDate); if (parsedDate) { datepicker.$connector._lastParseStatus = 'successful'; datepicker.$connector._lastParsedDate = parsedDate; return parsedDate; } } datepicker.$connector._lastParseStatus = 'error'; return false; } return { formatDate: formatDate, parseDate: parseDate }; }); function _getReferenceDate() { const { referenceDate } = datepicker.i18n; return referenceDate ? new Date(referenceDate.year, referenceDate.month, referenceDate.day) : new Date(); } datepicker.$connector.updateI18n = tryCatchWrapper(function (locale, i18n) { // Either use custom formats specified in I18N, or create format from locale const hasCustomFormats = i18n && i18n.dateFormats && i18n.dateFormats.length > 0; if (i18n && i18n.referenceDate) { i18n.referenceDate = extractDateParts(new Date(i18n.referenceDate)); } const usedFormats = hasCustomFormats ? i18n.dateFormats : [createLocaleBasedDateFormat(locale)]; const formatterAndParser = createFormatterAndParser(usedFormats); // Merge current web component I18N settings with new I18N settings and the formatting and parsing functions datepicker.i18n = Object.assign({}, datepicker.i18n, i18n, formatterAndParser); }); datepicker.addEventListener('opened-changed', () => (datepicker.$connector._lastParseStatus = undefined)); })(datepicker) }; })();