UNPKG

@mui/x-date-pickers

Version:

The community edition of the Date and Time Picker components (MUI X).

555 lines (549 loc) 18.6 kB
import _extends from "@babel/runtime/helpers/esm/extends"; /* eslint-disable class-methods-use-this */ import defaultDayjs from 'dayjs'; import weekOfYearPlugin from 'dayjs/plugin/weekOfYear'; import customParseFormatPlugin from 'dayjs/plugin/customParseFormat'; import localizedFormatPlugin from 'dayjs/plugin/localizedFormat'; import isBetweenPlugin from 'dayjs/plugin/isBetween'; import advancedFormatPlugin from 'dayjs/plugin/advancedFormat'; import { warnOnce } from '@mui/x-internals/warning'; defaultDayjs.extend(localizedFormatPlugin); defaultDayjs.extend(weekOfYearPlugin); defaultDayjs.extend(isBetweenPlugin); defaultDayjs.extend(advancedFormatPlugin); const formatTokenMap = { // Year YY: 'year', YYYY: { sectionType: 'year', contentType: 'digit', maxLength: 4 }, // Month M: { sectionType: 'month', contentType: 'digit', maxLength: 2 }, MM: 'month', MMM: { sectionType: 'month', contentType: 'letter' }, MMMM: { sectionType: 'month', contentType: 'letter' }, // Day of the month D: { sectionType: 'day', contentType: 'digit', maxLength: 2 }, DD: 'day', Do: { sectionType: 'day', contentType: 'digit-with-letter' }, // Day of the week d: { sectionType: 'weekDay', contentType: 'digit', maxLength: 2 }, dd: { sectionType: 'weekDay', contentType: 'letter' }, ddd: { sectionType: 'weekDay', contentType: 'letter' }, dddd: { sectionType: 'weekDay', contentType: 'letter' }, // Meridiem A: 'meridiem', a: 'meridiem', // Hours H: { sectionType: 'hours', contentType: 'digit', maxLength: 2 }, HH: 'hours', h: { sectionType: 'hours', contentType: 'digit', maxLength: 2 }, hh: 'hours', // Minutes m: { sectionType: 'minutes', contentType: 'digit', maxLength: 2 }, mm: 'minutes', // Seconds s: { sectionType: 'seconds', contentType: 'digit', maxLength: 2 }, ss: 'seconds' }; const defaultFormats = { year: 'YYYY', month: 'MMMM', monthShort: 'MMM', dayOfMonth: 'D', dayOfMonthFull: 'Do', weekday: 'dddd', weekdayShort: 'dd', hours24h: 'HH', hours12h: 'hh', meridiem: 'A', minutes: 'mm', seconds: 'ss', fullDate: 'll', keyboardDate: 'L', shortDate: 'MMM D', normalDate: 'D MMMM', normalDateWithWeekday: 'ddd, MMM D', fullTime: 'LT', fullTime12h: 'hh:mm A', fullTime24h: 'HH:mm', keyboardDateTime: 'L LT', keyboardDateTime12h: 'L hh:mm A', keyboardDateTime24h: 'L HH:mm' }; const MISSING_UTC_PLUGIN = ['Missing UTC plugin', 'To be able to use UTC or timezones, you have to enable the `utc` plugin', 'Find more information on https://mui.com/x/react-date-pickers/timezone/#day-js-and-utc'].join('\n'); const MISSING_TIMEZONE_PLUGIN = ['Missing timezone plugin', 'To be able to use timezones, you have to enable both the `utc` and the `timezone` plugin', 'Find more information on https://mui.com/x/react-date-pickers/timezone/#day-js-and-timezone'].join('\n'); const withLocale = (dayjs, locale) => !locale ? dayjs : (...args) => dayjs(...args).locale(locale); /** * Based on `@date-io/dayjs` * * MIT License * * Copyright (c) 2017 Dmitriy Kovalenko * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ export class AdapterDayjs { constructor({ locale: _locale, formats } = {}) { this.isMUIAdapter = true; this.isTimezoneCompatible = true; this.lib = 'dayjs'; this.dayjs = void 0; this.locale = void 0; this.formats = void 0; this.escapedCharacters = { start: '[', end: ']' }; this.formatTokenMap = formatTokenMap; this.setLocaleToValue = value => { const expectedLocale = this.getCurrentLocaleCode(); if (expectedLocale === value.locale()) { return value; } return value.locale(expectedLocale); }; this.hasUTCPlugin = () => typeof defaultDayjs.utc !== 'undefined'; this.hasTimezonePlugin = () => typeof defaultDayjs.tz !== 'undefined'; this.isSame = (value, comparing, comparisonTemplate) => { const comparingInValueTimezone = this.setTimezone(comparing, this.getTimezone(value)); return value.format(comparisonTemplate) === comparingInValueTimezone.format(comparisonTemplate); }; /** * Replaces "default" by undefined and "system" by the system timezone before passing it to `dayjs`. */ this.cleanTimezone = timezone => { switch (timezone) { case 'default': { return undefined; } case 'system': { return defaultDayjs.tz.guess(); } default: { return timezone; } } }; this.createSystemDate = value => { if (this.hasUTCPlugin() && this.hasTimezonePlugin()) { const timezone = defaultDayjs.tz.guess(); // We can't change the system timezone in the tests /* istanbul ignore next */ if (timezone !== 'UTC') { return defaultDayjs.tz(value, timezone); } return defaultDayjs(value); } return defaultDayjs(value); }; this.createUTCDate = value => { /* istanbul ignore next */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } return defaultDayjs.utc(value); }; this.createTZDate = (value, timezone) => { /* istanbul ignore next */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } /* istanbul ignore next */ if (!this.hasTimezonePlugin()) { throw new Error(MISSING_TIMEZONE_PLUGIN); } const keepLocalTime = value !== undefined && !value.endsWith('Z'); return defaultDayjs(value).tz(this.cleanTimezone(timezone), keepLocalTime); }; this.getLocaleFormats = () => { const locales = defaultDayjs.Ls; const locale = this.locale || 'en'; let localeObject = locales[locale]; if (localeObject === undefined) { /* istanbul ignore next */ if (process.env.NODE_ENV !== 'production') { warnOnce(['MUI X: Your locale has not been found.', 'Either the locale key is not a supported one. Locales supported by dayjs are available here: https://github.com/iamkun/dayjs/tree/dev/src/locale.', "Or you forget to import the locale from 'dayjs/locale/{localeUsed}'", 'fallback on English locale.']); } localeObject = locales.en; } return localeObject.formats; }; /** * If the new day does not have the same offset as the old one (when switching to summer day time for example), * Then dayjs will not automatically adjust the offset (moment does). * We have to parse again the value to make sure the `fixOffset` method is applied. * See https://github.com/iamkun/dayjs/blob/b3624de619d6e734cd0ffdbbd3502185041c1b60/src/plugin/timezone/index.js#L72 */ this.adjustOffset = value => { if (!this.hasTimezonePlugin()) { return value; } const timezone = this.getTimezone(value); if (timezone !== 'UTC') { const fixedValue = value.tz(this.cleanTimezone(timezone), true); // TODO: Simplify the case when we raise the `dayjs` peer dep to 1.11.12 (https://github.com/iamkun/dayjs/releases/tag/v1.11.12) /* istanbul ignore next */ // @ts-ignore if (fixedValue.$offset === (value.$offset ?? 0)) { return value; } // Change only what is needed to avoid creating a new object with unwanted data // Especially important when used in an environment where utc or timezone dates are used only in some places // Reference: https://github.com/mui/mui-x/issues/13290 // @ts-ignore value.$offset = fixedValue.$offset; } return value; }; this.date = (value, timezone = 'default') => { if (value === null) { return null; } let parsedValue; if (timezone === 'UTC') { parsedValue = this.createUTCDate(value); } else if (timezone === 'system' || timezone === 'default' && !this.hasTimezonePlugin()) { parsedValue = this.createSystemDate(value); } else { parsedValue = this.createTZDate(value, timezone); } if (this.locale === undefined) { return parsedValue; } return parsedValue.locale(this.locale); }; this.getInvalidDate = () => defaultDayjs(new Date('Invalid date')); this.getTimezone = value => { if (this.hasTimezonePlugin()) { // @ts-ignore const zone = value.$x?.$timezone; if (zone) { return zone; } } if (this.hasUTCPlugin() && value.isUTC()) { return 'UTC'; } return 'system'; }; this.setTimezone = (value, timezone) => { if (this.getTimezone(value) === timezone) { return value; } if (timezone === 'UTC') { /* istanbul ignore next */ if (!this.hasUTCPlugin()) { throw new Error(MISSING_UTC_PLUGIN); } return value.utc(); } // We know that we have the UTC plugin. // Otherwise, the value timezone would always equal "system". // And it would be caught by the first "if" of this method. if (timezone === 'system') { return value.local(); } if (!this.hasTimezonePlugin()) { if (timezone === 'default') { return value; } /* istanbul ignore next */ throw new Error(MISSING_TIMEZONE_PLUGIN); } return defaultDayjs.tz(value, this.cleanTimezone(timezone)); }; this.toJsDate = value => { return value.toDate(); }; this.parse = (value, format) => { if (value === '') { return null; } return this.dayjs(value, format, this.locale, true); }; this.getCurrentLocaleCode = () => { return this.locale || 'en'; }; this.is12HourCycleInCurrentLocale = () => { /* istanbul ignore next */ return /A|a/.test(this.getLocaleFormats().LT || ''); }; this.expandFormat = format => { const localeFormats = this.getLocaleFormats(); // @see https://github.com/iamkun/dayjs/blob/dev/src/plugin/localizedFormat/index.js const t = formatBis => formatBis.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1)); return format.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => { const B = b && b.toUpperCase(); return a || localeFormats[b] || t(localeFormats[B]); }); }; this.isValid = value => { if (value == null) { return false; } return value.isValid(); }; this.format = (value, formatKey) => { return this.formatByString(value, this.formats[formatKey]); }; this.formatByString = (value, formatString) => { return this.dayjs(value).format(formatString); }; this.formatNumber = numberToFormat => { return numberToFormat; }; this.isEqual = (value, comparing) => { if (value === null && comparing === null) { return true; } if (value === null || comparing === null) { return false; } return value.toDate().getTime() === comparing.toDate().getTime(); }; this.isSameYear = (value, comparing) => { return this.isSame(value, comparing, 'YYYY'); }; this.isSameMonth = (value, comparing) => { return this.isSame(value, comparing, 'YYYY-MM'); }; this.isSameDay = (value, comparing) => { return this.isSame(value, comparing, 'YYYY-MM-DD'); }; this.isSameHour = (value, comparing) => { return value.isSame(comparing, 'hour'); }; this.isAfter = (value, comparing) => { return value > comparing; }; this.isAfterYear = (value, comparing) => { if (!this.hasUTCPlugin()) { return value.isAfter(comparing, 'year'); } return !this.isSameYear(value, comparing) && value.utc() > comparing.utc(); }; this.isAfterDay = (value, comparing) => { if (!this.hasUTCPlugin()) { return value.isAfter(comparing, 'day'); } return !this.isSameDay(value, comparing) && value.utc() > comparing.utc(); }; this.isBefore = (value, comparing) => { return value < comparing; }; this.isBeforeYear = (value, comparing) => { if (!this.hasUTCPlugin()) { return value.isBefore(comparing, 'year'); } return !this.isSameYear(value, comparing) && value.utc() < comparing.utc(); }; this.isBeforeDay = (value, comparing) => { if (!this.hasUTCPlugin()) { return value.isBefore(comparing, 'day'); } return !this.isSameDay(value, comparing) && value.utc() < comparing.utc(); }; this.isWithinRange = (value, [start, end]) => { return value >= start && value <= end; }; this.startOfYear = value => { return this.adjustOffset(value.startOf('year')); }; this.startOfMonth = value => { return this.adjustOffset(value.startOf('month')); }; this.startOfWeek = value => { return this.adjustOffset(this.setLocaleToValue(value).startOf('week')); }; this.startOfDay = value => { return this.adjustOffset(value.startOf('day')); }; this.endOfYear = value => { return this.adjustOffset(value.endOf('year')); }; this.endOfMonth = value => { return this.adjustOffset(value.endOf('month')); }; this.endOfWeek = value => { return this.adjustOffset(this.setLocaleToValue(value).endOf('week')); }; this.endOfDay = value => { return this.adjustOffset(value.endOf('day')); }; this.addYears = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'year') : value.add(amount, 'year')); }; this.addMonths = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'month') : value.add(amount, 'month')); }; this.addWeeks = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'week') : value.add(amount, 'week')); }; this.addDays = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'day') : value.add(amount, 'day')); }; this.addHours = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'hour') : value.add(amount, 'hour')); }; this.addMinutes = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'minute') : value.add(amount, 'minute')); }; this.addSeconds = (value, amount) => { return this.adjustOffset(amount < 0 ? value.subtract(Math.abs(amount), 'second') : value.add(amount, 'second')); }; this.getYear = value => { return value.year(); }; this.getMonth = value => { return value.month(); }; this.getDate = value => { return value.date(); }; this.getHours = value => { return value.hour(); }; this.getMinutes = value => { return value.minute(); }; this.getSeconds = value => { return value.second(); }; this.getMilliseconds = value => { return value.millisecond(); }; this.setYear = (value, year) => { return this.adjustOffset(value.set('year', year)); }; this.setMonth = (value, month) => { return this.adjustOffset(value.set('month', month)); }; this.setDate = (value, date) => { return this.adjustOffset(value.set('date', date)); }; this.setHours = (value, hours) => { return this.adjustOffset(value.set('hour', hours)); }; this.setMinutes = (value, minutes) => { return this.adjustOffset(value.set('minute', minutes)); }; this.setSeconds = (value, seconds) => { return this.adjustOffset(value.set('second', seconds)); }; this.setMilliseconds = (value, milliseconds) => { return this.adjustOffset(value.set('millisecond', milliseconds)); }; this.getDaysInMonth = value => { return value.daysInMonth(); }; this.getWeekArray = value => { const start = this.startOfWeek(this.startOfMonth(value)); const end = this.endOfWeek(this.endOfMonth(value)); let count = 0; let current = start; const nestedWeeks = []; while (current < end) { const weekNumber = Math.floor(count / 7); nestedWeeks[weekNumber] = nestedWeeks[weekNumber] || []; nestedWeeks[weekNumber].push(current); current = this.addDays(current, 1); count += 1; } return nestedWeeks; }; this.getWeekNumber = value => { return value.week(); }; this.getYearRange = ([start, end]) => { const startDate = this.startOfYear(start); const endDate = this.endOfYear(end); const years = []; let current = startDate; while (this.isBefore(current, endDate)) { years.push(current); current = this.addYears(current, 1); } return years; }; this.dayjs = withLocale(defaultDayjs, _locale); this.locale = _locale; this.formats = _extends({}, defaultFormats, formats); // Moved plugins to the constructor to allow for users to use options on the library // for reference: https://github.com/mui/mui-x/pull/11151 defaultDayjs.extend(customParseFormatPlugin); } getDayOfWeek(value) { return value.day() + 1; } }