@ionic/core
Version: 
Base components for Ionic
1,394 lines (1,387 loc) • 60.2 kB
JavaScript
/*!
 * (C) Ionic http://ionicframework.com - MIT License
 */
import { p as printIonWarning } from './index6.js';
/**
 * Returns true if the selected day is equal to the reference day
 */
const isSameDay = (baseParts, compareParts) => {
    return (baseParts.month === compareParts.month && baseParts.day === compareParts.day && baseParts.year === compareParts.year);
};
/**
 * Returns true is the selected day is before the reference day.
 */
const isBefore = (baseParts, compareParts) => {
    return !!(baseParts.year < compareParts.year ||
        (baseParts.year === compareParts.year && baseParts.month < compareParts.month) ||
        (baseParts.year === compareParts.year &&
            baseParts.month === compareParts.month &&
            baseParts.day !== null &&
            baseParts.day < compareParts.day));
};
/**
 * Returns true is the selected day is after the reference day.
 */
const isAfter = (baseParts, compareParts) => {
    return !!(baseParts.year > compareParts.year ||
        (baseParts.year === compareParts.year && baseParts.month > compareParts.month) ||
        (baseParts.year === compareParts.year &&
            baseParts.month === compareParts.month &&
            baseParts.day !== null &&
            baseParts.day > compareParts.day));
};
const warnIfValueOutOfBounds = (value, min, max) => {
    const valueArray = Array.isArray(value) ? value : [value];
    for (const val of valueArray) {
        if ((min !== undefined && isBefore(val, min)) || (max !== undefined && isAfter(val, max))) {
            printIonWarning('The value provided to ion-datetime is out of bounds.\n\n' +
                `Min: ${JSON.stringify(min)}\n` +
                `Max: ${JSON.stringify(max)}\n` +
                `Value: ${JSON.stringify(value)}`);
            break;
        }
    }
};
/**
 * Determines if given year is a
 * leap year. Returns `true` if year
 * is a leap year. Returns `false`
 * otherwise.
 */
const isLeapYear = (year) => {
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};
/**
 * Determines the hour cycle for a user.
 * If the hour cycle is explicitly defined, just use that.
 * Otherwise, we try to derive it from either the specified
 * locale extension tags or from Intl.DateTimeFormat directly.
 */
const getHourCycle = (locale, hourCycle) => {
    /**
     * If developer has explicitly enabled 24-hour time
     * then return early and do not look at the system default.
     */
    if (hourCycle !== undefined) {
        return hourCycle;
    }
    /**
     * If hourCycle was not specified, check the locale
     * that is set on the user's device. We first check the
     * Intl.DateTimeFormat hourCycle option as developers can encode this
     * option into the locale string. Example: `en-US-u-hc-h23`
     */
    const formatted = new Intl.DateTimeFormat(locale, { hour: 'numeric' });
    const options = formatted.resolvedOptions();
    if (options.hourCycle !== undefined) {
        return options.hourCycle;
    }
    /**
     * If hourCycle is not specified (either through lack
     * of browser support or locale information) then fall
     * back to this slower hourCycle check.
     */
    const date = new Date('5/18/2021 00:00');
    const parts = formatted.formatToParts(date);
    const hour = parts.find((p) => p.type === 'hour');
    if (!hour) {
        throw new Error('Hour value not found from DateTimeFormat');
    }
    /**
     * Midnight for h11 starts at 0:00am
     * Midnight for h12 starts at 12:00am
     * Midnight for h23 starts at 00:00
     * Midnight for h24 starts at 24:00
     */
    switch (hour.value) {
        case '0':
            return 'h11';
        case '12':
            return 'h12';
        case '00':
            return 'h23';
        case '24':
            return 'h24';
        default:
            throw new Error(`Invalid hour cycle "${hourCycle}"`);
    }
};
/**
 * Determine if the hour cycle uses a 24-hour format.
 * Returns true for h23 and h24. Returns false otherwise.
 * If you don't know the hourCycle, use getHourCycle above
 * and pass the result into this function.
 */
const is24Hour = (hourCycle) => {
    return hourCycle === 'h23' || hourCycle === 'h24';
};
/**
 * Given a date object, returns the number
 * of days in that month.
 * Month value begin at 1, not 0.
 * i.e. January = month 1.
 */
const getNumDaysInMonth = (month, year) => {
    return month === 4 || month === 6 || month === 9 || month === 11
        ? 30
        : month === 2
            ? isLeapYear(year)
                ? 29
                : 28
            : 31;
};
/**
 * Certain locales display month then year while
 * others display year then month.
 * We can use Intl.DateTimeFormat to determine
 * the ordering for each locale.
 * The formatOptions param can be used to customize
 * which pieces of a date to compare against the month
 * with. For example, some locales render dd/mm/yyyy
 * while others render mm/dd/yyyy. This function can be
 * used for variations of the same "month first" check.
 */
const isMonthFirstLocale = (locale, formatOptions = {
    month: 'numeric',
    year: 'numeric',
}) => {
    /**
     * By setting month and year we guarantee that only
     * month, year, and literal (slashes '/', for example)
     * values are included in the formatToParts results.
     *
     * The ordering of the parts will be determined by
     * the locale. So if the month is the first value,
     * then we know month should be shown first. If the
     * year is the first value, then we know year should be shown first.
     *
     * This ordering can be controlled by customizing the locale property.
     */
    const parts = new Intl.DateTimeFormat(locale, formatOptions).formatToParts(new Date());
    return parts[0].type === 'month';
};
/**
 * Determines if the given locale formats the day period (am/pm) to the
 * left or right of the hour.
 * @param locale The locale to check.
 * @returns `true` if the locale formats the day period to the left of the hour.
 */
const isLocaleDayPeriodRTL = (locale) => {
    const parts = new Intl.DateTimeFormat(locale, { hour: 'numeric' }).formatToParts(new Date());
    return parts[0].type === 'dayPeriod';
};
const ISO_8601_REGEXP = 
// eslint-disable-next-line no-useless-escape
/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
// eslint-disable-next-line no-useless-escape
const TIME_REGEXP = /^((\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
/**
 * Use to convert a string of comma separated numbers or
 * an array of numbers, and clean up any user input
 */
const convertToArrayOfNumbers = (input) => {
    if (input === undefined) {
        return;
    }
    let processedInput = input;
    if (typeof input === 'string') {
        // convert the string to an array of strings
        // auto remove any whitespace and [] characters
        processedInput = input.replace(/\[|\]|\s/g, '').split(',');
    }
    let values;
    if (Array.isArray(processedInput)) {
        // ensure each value is an actual number in the returned array
        values = processedInput.map((num) => parseInt(num, 10)).filter(isFinite);
    }
    else {
        values = [processedInput];
    }
    return values;
};
/**
 * Extracts date information
 * from a .calendar-day element
 * into DatetimeParts.
 */
const getPartsFromCalendarDay = (el) => {
    return {
        month: parseInt(el.getAttribute('data-month'), 10),
        day: parseInt(el.getAttribute('data-day'), 10),
        year: parseInt(el.getAttribute('data-year'), 10),
        dayOfWeek: parseInt(el.getAttribute('data-day-of-week'), 10),
    };
};
function parseDate(val) {
    if (Array.isArray(val)) {
        const parsedArray = [];
        for (const valStr of val) {
            const parsedVal = parseDate(valStr);
            /**
             * If any of the values weren't parsed correctly, consider
             * the entire batch incorrect. This simplifies the type
             * signatures by having "undefined" be a general error case
             * instead of returning (Datetime | undefined)[], which is
             * harder for TS to perform type narrowing on.
             */
            if (!parsedVal) {
                return undefined;
            }
            parsedArray.push(parsedVal);
        }
        return parsedArray;
    }
    // manually parse IS0 cuz Date.parse cannot be trusted
    // ISO 8601 format: 1994-12-15T13:47:20Z
    let parse = null;
    if (val != null && val !== '') {
        // try parsing for just time first, HH:MM
        parse = TIME_REGEXP.exec(val);
        if (parse) {
            // adjust the array so it fits nicely with the datetime parse
            parse.unshift(undefined, undefined);
            parse[2] = parse[3] = undefined;
        }
        else {
            // try parsing for full ISO datetime
            parse = ISO_8601_REGEXP.exec(val);
        }
    }
    if (parse === null) {
        // wasn't able to parse the ISO datetime
        printIonWarning(`Unable to parse date string: ${val}. Please provide a valid ISO 8601 datetime string.`);
        return undefined;
    }
    // ensure all the parse values exist with at least 0
    for (let i = 1; i < 8; i++) {
        parse[i] = parse[i] !== undefined ? parseInt(parse[i], 10) : undefined;
    }
    // can also get second and millisecond from parse[6] and parse[7] if needed
    return {
        year: parse[1],
        month: parse[2],
        day: parse[3],
        hour: parse[4],
        minute: parse[5],
        ampm: parse[4] < 12 ? 'am' : 'pm',
    };
}
const clampDate = (dateParts, minParts, maxParts) => {
    if (minParts && isBefore(dateParts, minParts)) {
        return minParts;
    }
    else if (maxParts && isAfter(dateParts, maxParts)) {
        return maxParts;
    }
    return dateParts;
};
/**
 * Parses an hour and returns if the value is in the morning (am) or afternoon (pm).
 * @param hour The hour to format, should be 0-23
 * @returns `pm` if the hour is greater than or equal to 12, `am` if less than 12.
 */
const parseAmPm = (hour) => {
    return hour >= 12 ? 'pm' : 'am';
};
/**
 * Takes a max date string and creates a DatetimeParts
 * object, filling in any missing information.
 * For example, max="2012" would fill in the missing
 * month, day, hour, and minute information.
 */
const parseMaxParts = (max, todayParts) => {
    const result = parseDate(max);
    /**
     * If min was not a valid date then return undefined.
     */
    if (result === undefined) {
        return;
    }
    const { month, day, year, hour, minute } = result;
    /**
     * When passing in `max` or `min`, developers
     * can pass in any ISO-8601 string. This means
     * that not all of the date/time fields are defined.
     * For example, passing max="2012" is valid even though
     * there is no month, day, hour, or minute data.
     * However, all of this data is required when clamping the date
     * so that the correct initial value can be selected. As a result,
     * we need to fill in any omitted data with the min or max values.
     */
    const yearValue = year !== null && year !== void 0 ? year : todayParts.year;
    const monthValue = month !== null && month !== void 0 ? month : 12;
    return {
        month: monthValue,
        day: day !== null && day !== void 0 ? day : getNumDaysInMonth(monthValue, yearValue),
        /**
         * Passing in "HH:mm" is a valid ISO-8601
         * string, so we just default to the current year
         * in this case.
         */
        year: yearValue,
        hour: hour !== null && hour !== void 0 ? hour : 23,
        minute: minute !== null && minute !== void 0 ? minute : 59,
    };
};
/**
 * Takes a min date string and creates a DatetimeParts
 * object, filling in any missing information.
 * For example, min="2012" would fill in the missing
 * month, day, hour, and minute information.
 */
const parseMinParts = (min, todayParts) => {
    const result = parseDate(min);
    /**
     * If min was not a valid date then return undefined.
     */
    if (result === undefined) {
        return;
    }
    const { month, day, year, hour, minute } = result;
    /**
     * When passing in `max` or `min`, developers
     * can pass in any ISO-8601 string. This means
     * that not all of the date/time fields are defined.
     * For example, passing max="2012" is valid even though
     * there is no month, day, hour, or minute data.
     * However, all of this data is required when clamping the date
     * so that the correct initial value can be selected. As a result,
     * we need to fill in any omitted data with the min or max values.
     */
    return {
        month: month !== null && month !== void 0 ? month : 1,
        day: day !== null && day !== void 0 ? day : 1,
        /**
         * Passing in "HH:mm" is a valid ISO-8601
         * string, so we just default to the current year
         * in this case.
         */
        year: year !== null && year !== void 0 ? year : todayParts.year,
        hour: hour !== null && hour !== void 0 ? hour : 0,
        minute: minute !== null && minute !== void 0 ? minute : 0,
    };
};
const twoDigit = (val) => {
    return ('0' + (val !== undefined ? Math.abs(val) : '0')).slice(-2);
};
const fourDigit = (val) => {
    return ('000' + (val !== undefined ? Math.abs(val) : '0')).slice(-4);
};
function convertDataToISO(data) {
    if (Array.isArray(data)) {
        return data.map((parts) => convertDataToISO(parts));
    }
    // https://www.w3.org/TR/NOTE-datetime
    let rtn = '';
    if (data.year !== undefined) {
        // YYYY
        rtn = fourDigit(data.year);
        if (data.month !== undefined) {
            // YYYY-MM
            rtn += '-' + twoDigit(data.month);
            if (data.day !== undefined) {
                // YYYY-MM-DD
                rtn += '-' + twoDigit(data.day);
                if (data.hour !== undefined) {
                    // YYYY-MM-DDTHH:mm:SS
                    rtn += `T${twoDigit(data.hour)}:${twoDigit(data.minute)}:00`;
                }
            }
        }
    }
    else if (data.hour !== undefined) {
        // HH:mm
        rtn = twoDigit(data.hour) + ':' + twoDigit(data.minute);
    }
    return rtn;
}
/**
 * Converts an 12 hour value to 24 hours.
 */
const convert12HourTo24Hour = (hour, ampm) => {
    if (ampm === undefined) {
        return hour;
    }
    /**
     * If AM and 12am
     * then return 00:00.
     * Otherwise just return
     * the hour since it is
     * already in 24 hour format.
     */
    if (ampm === 'am') {
        if (hour === 12) {
            return 0;
        }
        return hour;
    }
    /**
     * If PM and 12pm
     * just return 12:00
     * since it is already
     * in 24 hour format.
     * Otherwise add 12 hours
     * to the time.
     */
    if (hour === 12) {
        return 12;
    }
    return hour + 12;
};
const getStartOfWeek = (refParts) => {
    const { dayOfWeek } = refParts;
    if (dayOfWeek === null || dayOfWeek === undefined) {
        throw new Error('No day of week provided');
    }
    return subtractDays(refParts, dayOfWeek);
};
const getEndOfWeek = (refParts) => {
    const { dayOfWeek } = refParts;
    if (dayOfWeek === null || dayOfWeek === undefined) {
        throw new Error('No day of week provided');
    }
    return addDays(refParts, 6 - dayOfWeek);
};
const getNextDay = (refParts) => {
    return addDays(refParts, 1);
};
const getPreviousDay = (refParts) => {
    return subtractDays(refParts, 1);
};
const getPreviousWeek = (refParts) => {
    return subtractDays(refParts, 7);
};
const getNextWeek = (refParts) => {
    return addDays(refParts, 7);
};
/**
 * Given datetime parts, subtract
 * numDays from the date.
 * Returns a new DatetimeParts object
 * Currently can only go backward at most 1 month.
 */
const subtractDays = (refParts, numDays) => {
    const { month, day, year } = refParts;
    if (day === null) {
        throw new Error('No day provided');
    }
    const workingParts = {
        month,
        day,
        year,
    };
    workingParts.day = day - numDays;
    /**
     * If wrapping to previous month
     * update days and decrement month
     */
    if (workingParts.day < 1) {
        workingParts.month -= 1;
    }
    /**
     * If moving to previous year, reset
     * month to December and decrement year
     */
    if (workingParts.month < 1) {
        workingParts.month = 12;
        workingParts.year -= 1;
    }
    /**
     * Determine how many days are in the current
     * month
     */
    if (workingParts.day < 1) {
        const daysInMonth = getNumDaysInMonth(workingParts.month, workingParts.year);
        /**
         * Take num days in month and add the
         * number of underflow days. This number will
         * be negative.
         * Example: 1 week before Jan 2, 2021 is
         * December 26, 2021 so:
         * 2 - 7 = -5
         * 31 + (-5) = 26
         */
        workingParts.day = daysInMonth + workingParts.day;
    }
    return workingParts;
};
/**
 * Given datetime parts, add
 * numDays to the date.
 * Returns a new DatetimeParts object
 * Currently can only go forward at most 1 month.
 */
const addDays = (refParts, numDays) => {
    const { month, day, year } = refParts;
    if (day === null) {
        throw new Error('No day provided');
    }
    const workingParts = {
        month,
        day,
        year,
    };
    const daysInMonth = getNumDaysInMonth(month, year);
    workingParts.day = day + numDays;
    /**
     * If wrapping to next month
     * update days and increment month
     */
    if (workingParts.day > daysInMonth) {
        workingParts.day -= daysInMonth;
        workingParts.month += 1;
    }
    /**
     * If moving to next year, reset
     * month to January and increment year
     */
    if (workingParts.month > 12) {
        workingParts.month = 1;
        workingParts.year += 1;
    }
    return workingParts;
};
/**
 * Given DatetimeParts, generate the previous month.
 */
const getPreviousMonth = (refParts) => {
    /**
     * If current month is January, wrap backwards
     *  to December of the previous year.
     */
    const month = refParts.month === 1 ? 12 : refParts.month - 1;
    const year = refParts.month === 1 ? refParts.year - 1 : refParts.year;
    const numDaysInMonth = getNumDaysInMonth(month, year);
    const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
    return { month, year, day };
};
/**
 * Given DatetimeParts, generate the next month.
 */
const getNextMonth = (refParts) => {
    /**
     * If current month is December, wrap forwards
     *  to January of the next year.
     */
    const month = refParts.month === 12 ? 1 : refParts.month + 1;
    const year = refParts.month === 12 ? refParts.year + 1 : refParts.year;
    const numDaysInMonth = getNumDaysInMonth(month, year);
    const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
    return { month, year, day };
};
const changeYear = (refParts, yearDelta) => {
    const month = refParts.month;
    const year = refParts.year + yearDelta;
    const numDaysInMonth = getNumDaysInMonth(month, year);
    const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
    return { month, year, day };
};
/**
 * Given DatetimeParts, generate the previous year.
 */
const getPreviousYear = (refParts) => {
    return changeYear(refParts, -1);
};
/**
 * Given DatetimeParts, generate the next year.
 */
const getNextYear = (refParts) => {
    return changeYear(refParts, 1);
};
/**
 * If PM, then internal value should
 * be converted to 24-hr time.
 * Does not apply when public
 * values are already 24-hr time.
 */
const getInternalHourValue = (hour, use24Hour, ampm) => {
    if (use24Hour) {
        return hour;
    }
    return convert12HourTo24Hour(hour, ampm);
};
/**
 * Unless otherwise stated, all month values are
 * 1 indexed instead of the typical 0 index in JS Date.
 * Example:
 *   January = Month 0 when using JS Date
 *   January = Month 1 when using this datetime util
 */
/**
 * Given the current datetime parts and a new AM/PM value
 * calculate what the hour should be in 24-hour time format.
 * Used when toggling the AM/PM segment since we store our hours
 * in 24-hour time format internally.
 */
const calculateHourFromAMPM = (currentParts, newAMPM) => {
    const { ampm: currentAMPM, hour } = currentParts;
    let newHour = hour;
    /**
     * If going from AM --> PM, need to update the
     *
     */
    if (currentAMPM === 'am' && newAMPM === 'pm') {
        newHour = convert12HourTo24Hour(newHour, 'pm');
        /**
         * If going from PM --> AM
         */
    }
    else if (currentAMPM === 'pm' && newAMPM === 'am') {
        newHour = Math.abs(newHour - 12);
    }
    return newHour;
};
/**
 * Updates parts to ensure that month and day
 * values are valid. For days that do not exist,
 * or are outside the min/max bounds, the closest
 * valid day is used.
 */
const validateParts = (parts, minParts, maxParts) => {
    const { month, day, year } = parts;
    const partsCopy = clampDate(Object.assign({}, parts), minParts, maxParts);
    const numDays = getNumDaysInMonth(month, year);
    /**
     * If the max number of days
     * is greater than the day we want
     * to set, update the DatetimeParts
     * day field to be the max days.
     */
    if (day !== null && numDays < day) {
        partsCopy.day = numDays;
    }
    /**
     * If value is same day as min day,
     * make sure the time value is in bounds.
     */
    if (minParts !== undefined && isSameDay(partsCopy, minParts)) {
        /**
         * If the hour is out of bounds,
         * update both the hour and minute.
         * This is done so that the new time
         * is closest to what the user selected.
         */
        if (partsCopy.hour !== undefined && minParts.hour !== undefined) {
            if (partsCopy.hour < minParts.hour) {
                partsCopy.hour = minParts.hour;
                partsCopy.minute = minParts.minute;
                /**
                 * If only the minute is out of bounds,
                 * set it to the min minute.
                 */
            }
            else if (partsCopy.hour === minParts.hour &&
                partsCopy.minute !== undefined &&
                minParts.minute !== undefined &&
                partsCopy.minute < minParts.minute) {
                partsCopy.minute = minParts.minute;
            }
        }
    }
    /**
     * If value is same day as max day,
     * make sure the time value is in bounds.
     */
    if (maxParts !== undefined && isSameDay(parts, maxParts)) {
        /**
         * If the hour is out of bounds,
         * update both the hour and minute.
         * This is done so that the new time
         * is closest to what the user selected.
         */
        if (partsCopy.hour !== undefined && maxParts.hour !== undefined) {
            if (partsCopy.hour > maxParts.hour) {
                partsCopy.hour = maxParts.hour;
                partsCopy.minute = maxParts.minute;
                /**
                 * If only the minute is out of bounds,
                 * set it to the max minute.
                 */
            }
            else if (partsCopy.hour === maxParts.hour &&
                partsCopy.minute !== undefined &&
                maxParts.minute !== undefined &&
                partsCopy.minute > maxParts.minute) {
                partsCopy.minute = maxParts.minute;
            }
        }
    }
    return partsCopy;
};
/**
 * Returns the closest date to refParts
 * that also meets the constraints of
 * the *Values params.
 */
const getClosestValidDate = ({ refParts, monthValues, dayValues, yearValues, hourValues, minuteValues, minParts, maxParts, }) => {
    const { hour, minute, day, month, year } = refParts;
    const copyParts = Object.assign(Object.assign({}, refParts), { dayOfWeek: undefined });
    if (yearValues !== undefined) {
        // Filters out years that are out of the min/max bounds
        const filteredYears = yearValues.filter((year) => {
            if (minParts !== undefined && year < minParts.year) {
                return false;
            }
            if (maxParts !== undefined && year > maxParts.year) {
                return false;
            }
            return true;
        });
        copyParts.year = findClosestValue(year, filteredYears);
    }
    if (monthValues !== undefined) {
        // Filters out months that are out of the min/max bounds
        const filteredMonths = monthValues.filter((month) => {
            if (minParts !== undefined && copyParts.year === minParts.year && month < minParts.month) {
                return false;
            }
            if (maxParts !== undefined && copyParts.year === maxParts.year && month > maxParts.month) {
                return false;
            }
            return true;
        });
        copyParts.month = findClosestValue(month, filteredMonths);
    }
    // Day is nullable but cannot be undefined
    if (day !== null && dayValues !== undefined) {
        // Filters out days that are out of the min/max bounds
        const filteredDays = dayValues.filter((day) => {
            if (minParts !== undefined && isBefore(Object.assign(Object.assign({}, copyParts), { day }), minParts)) {
                return false;
            }
            if (maxParts !== undefined && isAfter(Object.assign(Object.assign({}, copyParts), { day }), maxParts)) {
                return false;
            }
            return true;
        });
        copyParts.day = findClosestValue(day, filteredDays);
    }
    if (hour !== undefined && hourValues !== undefined) {
        // Filters out hours that are out of the min/max bounds
        const filteredHours = hourValues.filter((hour) => {
            if ((minParts === null || minParts === void 0 ? void 0 : minParts.hour) !== undefined && isSameDay(copyParts, minParts) && hour < minParts.hour) {
                return false;
            }
            if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.hour) !== undefined && isSameDay(copyParts, maxParts) && hour > maxParts.hour) {
                return false;
            }
            return true;
        });
        copyParts.hour = findClosestValue(hour, filteredHours);
        copyParts.ampm = parseAmPm(copyParts.hour);
    }
    if (minute !== undefined && minuteValues !== undefined) {
        // Filters out minutes that are out of the min/max bounds
        const filteredMinutes = minuteValues.filter((minute) => {
            if ((minParts === null || minParts === void 0 ? void 0 : minParts.minute) !== undefined &&
                isSameDay(copyParts, minParts) &&
                copyParts.hour === minParts.hour &&
                minute < minParts.minute) {
                return false;
            }
            if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.minute) !== undefined &&
                isSameDay(copyParts, maxParts) &&
                copyParts.hour === maxParts.hour &&
                minute > maxParts.minute) {
                return false;
            }
            return true;
        });
        copyParts.minute = findClosestValue(minute, filteredMinutes);
    }
    return copyParts;
};
/**
 * Finds the value in "values" that is
 * numerically closest to "reference".
 * This function assumes that "values" is
 * already sorted in ascending order.
 * @param reference The reference number to use
 * when finding the closest value
 * @param values The allowed values that will be
 * searched to find the closest value to "reference"
 */
const findClosestValue = (reference, values) => {
    let closestValue = values[0];
    let rank = Math.abs(closestValue - reference);
    for (let i = 1; i < values.length; i++) {
        const value = values[i];
        /**
         * This code prioritizes the first
         * closest result. Given two values
         * with the same distance from reference,
         * this code will prioritize the smaller of
         * the two values.
         */
        const valueRank = Math.abs(value - reference);
        if (valueRank < rank) {
            closestValue = value;
            rank = valueRank;
        }
    }
    return closestValue;
};
const getFormattedDayPeriod = (dayPeriod) => {
    if (dayPeriod === undefined) {
        return '';
    }
    return dayPeriod.toUpperCase();
};
/**
 * Including time zone options may lead to the rendered text showing a
 * different time from what was selected in the Datetime, which could cause
 * confusion.
 */
const stripTimeZone = (formatOptions) => {
    return Object.assign(Object.assign({}, formatOptions), { 
        /**
         * Setting the time zone to UTC ensures that the value shown is always the
         * same as what was selected and safeguards against older Safari bugs with
         * Intl.DateTimeFormat.
         */
        timeZone: 'UTC', 
        /**
         * We do not want to display the time zone name
         */
        timeZoneName: undefined });
};
const getLocalizedTime = (locale, refParts, hourCycle, formatOptions = { hour: 'numeric', minute: 'numeric' }) => {
    const timeParts = {
        hour: refParts.hour,
        minute: refParts.minute,
    };
    if (timeParts.hour === undefined || timeParts.minute === undefined) {
        return 'Invalid Time';
    }
    return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, stripTimeZone(formatOptions)), { 
        /**
         * We use hourCycle here instead of hour12 due to:
         * https://bugs.chromium.org/p/chromium/issues/detail?id=1347316&q=hour12&can=2
         */
        hourCycle })).format(new Date(convertDataToISO(Object.assign({ 
        /**
         * JS uses a simplified ISO 8601 format which allows for
         * date-only formats and date-time formats, but not
         * time-only formats: https://tc39.es/ecma262/#sec-date-time-string-format
         * As a result, developers who only pass a time will get
         * an "Invalid Date" error. To account for this, we make sure that
         * year/day/month values are set when passing to new Date().
         * The Intl.DateTimeFormat call above only uses the hour/minute
         * values, so passing these date values should have no impact
         * on the time output.
         */
        year: 2023, day: 1, month: 1 }, timeParts)) + 'Z'));
};
/**
 * Adds padding to a time value so
 * that it is always 2 digits.
 */
const addTimePadding = (value) => {
    const valueToString = value.toString();
    if (valueToString.length > 1) {
        return valueToString;
    }
    return `0${valueToString}`;
};
/**
 * Formats 24 hour times so that
 * it always has 2 digits. For
 * 12 hour times it ensures that
 * hour 0 is formatted as '12'.
 */
const getFormattedHour = (hour, hourCycle) => {
    /**
     * Midnight for h11 starts at 0:00am
     * Midnight for h12 starts at 12:00am
     * Midnight for h23 starts at 00:00
     * Midnight for h24 starts at 24:00
     */
    if (hour === 0) {
        switch (hourCycle) {
            case 'h11':
                return '0';
            case 'h12':
                return '12';
            case 'h23':
                return '00';
            case 'h24':
                return '24';
            default:
                throw new Error(`Invalid hour cycle "${hourCycle}"`);
        }
    }
    const use24Hour = is24Hour(hourCycle);
    /**
     * h23 and h24 use 24 hour times.
     */
    if (use24Hour) {
        return addTimePadding(hour);
    }
    return hour.toString();
};
/**
 * Generates an aria-label to be read by screen readers
 * given a local, a date, and whether or not that date is
 * today's date.
 */
const generateDayAriaLabel = (locale, today, refParts) => {
    if (refParts.day === null) {
        return null;
    }
    /**
     * MM/DD/YYYY will return midnight in the user's timezone.
     */
    const date = getNormalizedDate(refParts);
    const labelString = new Intl.DateTimeFormat(locale, {
        weekday: 'long',
        month: 'long',
        day: 'numeric',
        timeZone: 'UTC',
    }).format(date);
    /**
     * If date is today, prepend "Today" so screen readers indicate
     * that the date is today.
     */
    return today ? `Today, ${labelString}` : labelString;
};
/**
 * Given a locale and a date object,
 * return a formatted string that includes
 * the month name and full year.
 * Example: May 2021
 */
const getMonthAndYear = (locale, refParts) => {
    const date = getNormalizedDate(refParts);
    return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
};
/**
 * Given a locale and a date object,
 * return a formatted string that includes
 * the numeric day.
 * Note: Some languages will add literal characters
 * to the end. This function removes those literals.
 * Example: 29
 */
const getDay = (locale, refParts) => {
    return getLocalizedDateTimeParts(locale, refParts, { day: 'numeric' }).find((obj) => obj.type === 'day').value;
};
/**
 * Given a locale and a date object,
 * return a formatted string that includes
 * the numeric year.
 * Example: 2022
 */
const getYear = (locale, refParts) => {
    return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
};
/**
 * Given reference parts, return a JS Date object
 * with a normalized time.
 */
const getNormalizedDate = (refParts) => {
    var _a, _b, _c;
    const timeString = refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
    /**
     * We use / notation here for the date
     * so we do not need to do extra work and pad values with zeroes.
     * Values such as YYYY-MM are still valid, so
     * we add fallback values so we still get
     * a valid date otherwise we will pass in a string
     * like "//2023". Some browsers, such as Chrome, will
     * account for this and still return a valid date. However,
     * this is not a consistent behavior across all browsers.
     */
    return new Date(`${(_a = refParts.month) !== null && _a !== void 0 ? _a : 1}/${(_b = refParts.day) !== null && _b !== void 0 ? _b : 1}/${(_c = refParts.year) !== null && _c !== void 0 ? _c : 2023}${timeString} GMT+0000`);
};
/**
 * Given a locale, DatetimeParts, and options
 * format the DatetimeParts according to the options
 * and locale combination. This returns a string. If
 * you want an array of the individual pieces
 * that make up the localized date string, use
 * getLocalizedDateTimeParts.
 */
const getLocalizedDateTime = (locale, refParts, options) => {
    const date = getNormalizedDate(refParts);
    return getDateTimeFormat(locale, stripTimeZone(options)).format(date);
};
/**
 * Given a locale, DatetimeParts, and options
 * format the DatetimeParts according to the options
 * and locale combination. This returns an array of
 * each piece of the date.
 */
const getLocalizedDateTimeParts = (locale, refParts, options) => {
    const date = getNormalizedDate(refParts);
    return getDateTimeFormat(locale, options).formatToParts(date);
};
/**
 * Wrapper function for Intl.DateTimeFormat.
 * Allows developers to apply an allowed format to DatetimeParts.
 * This function also has built in safeguards for older browser bugs
 * with Intl.DateTimeFormat.
 */
const getDateTimeFormat = (locale, options) => {
    return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, options), { timeZone: 'UTC' }));
};
/**
 * Gets a localized version of "Today"
 * Falls back to "Today" in English for
 * browsers that do not support RelativeTimeFormat.
 */
const getTodayLabel = (locale) => {
    if ('RelativeTimeFormat' in Intl) {
        const label = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(0, 'day');
        return label.charAt(0).toUpperCase() + label.slice(1);
    }
    else {
        return 'Today';
    }
};
/**
 * When calling toISOString(), the browser
 * will convert the date to UTC time by either adding
 * or subtracting the time zone offset.
 * To work around this, we need to either add
 * or subtract the time zone offset to the Date
 * object prior to calling toISOString().
 * This allows us to get an ISO string
 * that is in the user's time zone.
 *
 * Example:
 * Time zone offset is 240
 * Meaning: The browser needs to add 240 minutes
 * to the Date object to get UTC time.
 * What Ionic does: We subtract 240 minutes
 * from the Date object. The browser then adds
 * 240 minutes in toISOString(). The result
 * is a time that is in the user's time zone
 * and not UTC.
 *
 * Note: Some timezones include minute adjustments
 * such as 30 or 45 minutes. This is why we use setMinutes
 * instead of setHours.
 * Example: India Standard Time
 * Timezone offset: -330 = -5.5 hours.
 *
 * List of timezones with 30 and 45 minute timezones:
 * https://www.timeanddate.com/time/time-zones-interesting.html
 */
const removeDateTzOffset = (date) => {
    const tzOffset = date.getTimezoneOffset();
    date.setMinutes(date.getMinutes() - tzOffset);
    return date;
};
const DATE_AM = removeDateTzOffset(new Date('2022T01:00'));
const DATE_PM = removeDateTzOffset(new Date('2022T13:00'));
/**
 * Formats the locale's string representation of the day period (am/pm) for a given
 * ref parts day period.
 *
 * @param locale The locale to format the day period in.
 * @param value The date string, in ISO format.
 * @returns The localized day period (am/pm) representation of the given value.
 */
const getLocalizedDayPeriod = (locale, dayPeriod) => {
    const date = dayPeriod === 'am' ? DATE_AM : DATE_PM;
    const localizedDayPeriod = new Intl.DateTimeFormat(locale, {
        hour: 'numeric',
        timeZone: 'UTC',
    })
        .formatToParts(date)
        .find((part) => part.type === 'dayPeriod');
    if (localizedDayPeriod) {
        return localizedDayPeriod.value;
    }
    return getFormattedDayPeriod(dayPeriod);
};
/**
 * Formats the datetime's value to a string, for use in the native input.
 *
 * @param value The value to format, either an ISO string or an array thereof.
 */
const formatValue = (value) => {
    return Array.isArray(value) ? value.join(',') : value;
};
/**
 * Returns the current date as
 * an ISO string in the user's
 * time zone.
 */
const getToday = () => {
    /**
     * ion-datetime intentionally does not
     * parse time zones/do automatic time zone
     * conversion when accepting user input.
     * However when we get today's date string,
     * we want it formatted relative to the user's
     * time zone.
     *
     * When calling toISOString(), the browser
     * will convert the date to UTC time by either adding
     * or subtracting the time zone offset.
     * To work around this, we need to either add
     * or subtract the time zone offset to the Date
     * object prior to calling toISOString().
     * This allows us to get an ISO string
     * that is in the user's time zone.
     */
    return removeDateTzOffset(new Date()).toISOString();
};
const minutes = [
    0, 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, 28, 29, 30, 31,
    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
];
// h11 hour system uses 0-11. Midnight starts at 0:00am.
const hour11 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
// h12 hour system uses 0-12. Midnight starts at 12:00am.
const hour12 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
// h23 hour system uses 0-23. Midnight starts at 0:00.
const hour23 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
// h24 hour system uses 1-24. Midnight starts at 24:00.
const hour24 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0];
/**
 * Given a locale and a mode,
 * return an array with formatted days
 * of the week. iOS should display days
 * such as "Mon" or "Tue".
 * MD should display days such as "M"
 * or "T".
 */
const getDaysOfWeek = (locale, mode, firstDayOfWeek = 0) => {
    /**
     * Nov 1st, 2020 starts on a Sunday.
     * ion-datetime assumes weeks start on Sunday,
     * but is configurable via `firstDayOfWeek`.
     */
    const weekdayFormat = mode === 'ios' ? 'short' : 'narrow';
    const intl = new Intl.DateTimeFormat(locale, { weekday: weekdayFormat });
    const startDate = new Date('11/01/2020');
    const daysOfWeek = [];
    /**
     * For each day of the week,
     * get the day name.
     */
    for (let i = firstDayOfWeek; i < firstDayOfWeek + 7; i++) {
        const currentDate = new Date(startDate);
        currentDate.setDate(currentDate.getDate() + i);
        daysOfWeek.push(intl.format(currentDate));
    }
    return daysOfWeek;
};
/**
 * Returns an array containing all of the
 * days in a month for a given year. Values are
 * aligned with a week calendar starting on
 * the firstDayOfWeek value (Sunday by default)
 * using null values.
 */
const getDaysOfMonth = (month, year, firstDayOfWeek) => {
    const numDays = getNumDaysInMonth(month, year);
    const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
    /**
     * To get the first day of the month aligned on the correct
     * day of the week, we need to determine how many "filler" days
     * to generate. These filler days as empty/disabled buttons
     * that fill the space of the days of the week before the first
     * of the month.
     *
     * There are two cases here:
     *
     * 1. If firstOfMonth = 4, firstDayOfWeek = 0 then the offset
     * is (4 - (0 + 1)) = 3. Since the offset loop goes from 0 to 3 inclusive,
     * this will generate 4 filler days (0, 1, 2, 3), and then day of week 4 will have
     * the first day of the month.
     *
     * 2. If firstOfMonth = 2, firstDayOfWeek = 4 then the offset
     * is (6 - (4 - 2)) = 4. Since the offset loop goes from 0 to 4 inclusive,
     * this will generate 5 filler days (0, 1, 2, 3, 4), and then day of week 5 will have
     * the first day of the month.
     */
    const offset = firstOfMonth >= firstDayOfWeek ? firstOfMonth - (firstDayOfWeek + 1) : 6 - (firstDayOfWeek - firstOfMonth);
    let days = [];
    for (let i = 1; i <= numDays; i++) {
        days.push({ day: i, dayOfWeek: (offset + i) % 7 });
    }
    for (let i = 0; i <= offset; i++) {
        days = [{ day: null, dayOfWeek: null }, ...days];
    }
    return days;
};
/**
 * Returns an array of pre-defined hour
 * values based on the provided hourCycle.
 */
const getHourData = (hourCycle) => {
    switch (hourCycle) {
        case 'h11':
            return hour11;
        case 'h12':
            return hour12;
        case 'h23':
            return hour23;
        case 'h24':
            return hour24;
        default:
            throw new Error(`Invalid hour cycle "${hourCycle}"`);
    }
};
/**
 * Given a local, reference datetime parts and option
 * max/min bound datetime parts, calculate the acceptable
 * hour and minute values according to the bounds and locale.
 */
const generateTime = (locale, refParts, hourCycle = 'h12', minParts, maxParts, hourValues, minuteValues) => {
    const computedHourCycle = getHourCycle(locale, hourCycle);
    const use24Hour = is24Hour(computedHourCycle);
    let processedHours = getHourData(computedHourCycle);
    let processedMinutes = minutes;
    let isAMAllowed = true;
    let isPMAllowed = true;
    if (hourValues) {
        processedHours = processedHours.filter((hour) => hourValues.includes(hour));
    }
    if (minuteValues) {
        processedMinutes = processedMinutes.filter((minute) => minuteValues.includes(minute));
    }
    if (minParts) {
        /**
         * If ref day is the same as the
         * minimum allowed day, filter hour/minute
         * values according to min hour and minute.
         */
        if (isSameDay(refParts, minParts)) {
            /**
             * Users may not always set the hour/minute for
             * min value (i.e. 2021-06-02) so we should allow
             * all hours/minutes in that case.
             */
            if (minParts.hour !== undefined) {
                processedHours = processedHours.filter((hour) => {
                    const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
                    return (use24Hour ? hour : convertedHour) >= minParts.hour;
                });
                isAMAllowed = minParts.hour < 13;
            }
            if (minParts.minute !== undefined) {
                /**
                 * The minimum minute range should not be enforced when
                 * the hour is greater than the min hour.
                 *
                 * For example with a minimum range of 09:30, users
                 * should be able to select 10:00-10:29 and beyond.
                 */
                let isPastMinHour = false;
                if (minParts.hour !== undefined && refParts.hour !== undefined) {
                    if (refParts.hour > minParts.hour) {
                        isPastMinHour = true;
                    }
                }
                processedMinutes = processedMinutes.filter((minute) => {
                    if (isPastMinHour) {
                        return true;
                    }
                    return minute >= minParts.minute;
                });
            }
            /**
             * If ref day is before minimum
             * day do not render any hours/minute values
             */
        }
        else if (isBefore(refParts, minParts)) {
            processedHours = [];
            processedMinutes = [];
            isAMAllowed = isPMAllowed = false;
        }
    }
    if (maxParts) {
        /**
         * If ref day is the same as the
         * maximum allowed day, filter hour/minute
         * values according to max hour and minute.
         */
        if (isSameDay(refParts, maxParts)) {
            /**
             * Users may not always set the hour/minute for
             * max value (i.e. 2021-06-02) so we should allow
             * all hours/minutes in that case.
             */
            if (maxParts.hour !== undefined) {
                processedHours = processedHours.filter((hour) => {
                    const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
                    return (use24Hour ? hour : convertedHour) <= maxParts.hour;
                });
                isPMAllowed = maxParts.hour >= 12;
            }
            if (maxParts.minute !== undefined && refParts.hour === maxParts.hour) {
                // The available minutes should only be filtered when the hour is the same as the max hour.
                // For example if the max hour is 10:30 and the current hour is 10:00,
                // users should be able to select 00-30 minutes.
                // If the current hour is 09:00, users should be able to select 00-60 minutes.
                processedMinutes = processedMinutes.filter((minute) => minute <= maxParts.minute);
            }
            /**
             * If ref day is after minimum
             * day do not render any hours/minute values
             */
        }
        else if (isAfter(refParts, maxParts)) {
            processedHours = [];
            processedMinutes = [];
            isAMAllowed = isPMAllowed = false;
        }
    }
    return {
        hours: processedHours,
        minutes: processedMinutes,
        am: isAMAllowed,
        pm: isPMAllowed,
    };
};
/**
 * Given DatetimeParts, generate the previous,
 * current, and and next months.
 */
const generateMonths = (refParts, forcedDate) => {
    const current = { month: refParts.month, year: refParts.year, day: refParts.day };
    /**
     * If we're forcing a month to appear, and it's different from the current month,
     * ensure it appears by replacing the next or previous month as appropriate.
     */
    if (forcedDate !== undefined && (refParts.month !== forcedDate.month || refParts.year !== forcedDate.year)) {
        const forced = { month: forcedDate.month, year: forcedDate.year, day: forcedDate.day };
        const forcedMonthIsBefore = isBefore(forced, current);
        return forcedMonthIsBefore
            ? [forced, current, getNextMonth(refParts)]
            : [getPreviousMonth(refParts), current, forced];
    }
    return [getPreviousMonth(refParts), current, getNextMonth(refParts)];
};
const getMonthColumnData = (locale, refParts, minParts, maxParts, monthValues, formatOptions = {
    month: 'long',
}) => {
    const { year } = refParts;
    const months = [];
    if (monthValues !== undefined) {
        let processedMonths = monthValues;
        if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.month) !== undefined) {
            processedMonths = processedMonths.filter((month) => month <= maxParts.month);
        }
        if ((minParts === null || minParts === void 0 ? void 0 : minParts.month) !== undefined) {
            processedMonths = processedMonths.filter((month) => month >= minParts.month);
        }
        processedMonths.forEach((processedMonth) => {
            const date = new Date(`${processedMonth}/1/${year} GMT+0000`);
            const monthString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), { timeZone: 'UTC' })).format(date);
            months.push({ text: monthString, value: processedMonth });
        });
    }
    else {
        const maxMonth = maxParts && maxParts.year === year ? maxParts.month : 12;
        const minMonth = minParts && minParts.year === year ? minParts.month : 1;
        for (let i = minMonth; i <= maxMonth; i++) {
            /**
             *
             * There is a bug on iOS 14 where
             * Intl.DateTimeFormat takes into account