You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止用户在Web应用日期时间输入框中输入无效日期

实时验证MM/DD/YYYY HH:MM:SS格式的日期时间输入框

需求:开发Web应用的日期时间输入框,要求输入格式为MM/DD/YYYY HH:MM:SS,需要实时阻止无效输入(比如月份输入13、日期输入32等),而非输入后再验证。现有代码已实现自动格式化,但缺少实时验证逻辑,尝试用keypress事件的prevent.default()未成功。

修改后的代码

HTML代码

<label>Date time:
    <input placeholder="__/__/____ __:__:__" data-slots="_">
</label><br>

JavaScript代码

document.addEventListener('DOMContentLoaded', () => {
    for (const el of document.querySelectorAll("[placeholder][data-slots]")) {
        const pattern = el.getAttribute("placeholder"),
            slots = new Set(el.dataset.slots || "_"),
            prev = (j => Array.from(pattern, (c, i) => slots.has(c) ? j = i + 1 : j))(0),
            first = [...pattern].findIndex(c => slots.has(c)),
            accept = new RegExp(el.dataset.accept || "\d", "g"),
            // 定义各字段的位置范围和验证规则
            fieldRules = [
                { start: 0, end: 2, max: 12, min: 1 }, // 月份 MM
                { start: 3, end: 5, max: (year) => { // 日期 DD,根据年份和月份判断最大天数
                    const month = parseInt(el.value.slice(0,2),10) || 1;
                    const isLeap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
                    const monthDays = [31, isLeap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
                    return monthDays[month - 1];
                }, min: 1 },
                { start: 6, end: 10, max: 9999, min: 1900 }, // 年份 YYYY
                { start: 11, end: 13, max: 23, min: 0 }, // 小时 HH
                { start: 14, end: 16, max: 59, min: 0 }, // 分钟 MM
                { start: 17, end: 19, max: 59, min: 0 }  // 秒数 SS
            ],
            clean = (input) => {
                input = input.match(accept) || [];
                return Array.from(pattern, c =>
                    input[0] === c || slots.has(c) ? input.shift() || c : c
                );
            },
            format = () => {
                const [i, j] = [el.selectionStart, el.selectionEnd].map(i => {
                    i = clean(el.value.slice(0, i)).findIndex(c => slots.has(c));
                    return i < 0 ? prev[prev.length - 1] : back ? prev[i - 1] || first : i;
                });
                el.value = clean(el.value).join``;
                el.setSelectionRange(i, j);
                back = false;
            },
            // 验证当前输入是否合法
            isValidInput = (key, pos) => {
                if (!/\d/.test(key)) return false; // 非数字直接不允许
                // 找到当前光标位置对应的字段
                const field = fieldRules.find(f => pos >= f.start && pos < f.end);
                if (!field) return true; // 不在字段范围内,允许输入(比如分隔符位置)
                
                // 模拟输入后的字段值
                let currentValue = el.value.split('');
                // 替换当前光标位置的字符
                currentValue[pos] = key;
                const fieldStr = currentValue.slice(field.start, field.end).join('').replace(/_/g, '');
                const fieldNum = parseInt(fieldStr, 10);
                
                // 如果是日期字段,需要先获取年份
                if (field.start === 3) {
                    const yearStr = currentValue.slice(6,10).join('').replace(/_/g, '');
                    const year = yearStr ? parseInt(yearStr,10) : new Date().getFullYear();
                    const maxDay = typeof field.max === 'function' ? field.max(year) : field.max;
                    return fieldNum >= field.min && fieldNum <= maxDay;
                }
                
                // 其他字段直接验证范围
                return fieldNum >= field.min && fieldNum <= field.max;
            };
        
        let back = false;
        el.addEventListener("keydown", (e) => {
            back = e.key === "Backspace";
            // 允许特殊按键:退格、删除、方向键、Tab、回车等
            const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'];
            if (allowedKeys.includes(e.key)) return;
            
            // 阻止非数字输入和无效值输入
            if (!isValidInput(e.key, e.target.selectionStart)) {
                e.preventDefault();
            }
        });
        el.addEventListener("input", format);
        el.addEventListener("focus", format);
        el.addEventListener("blur", () => el.value === pattern && (el.value = ""));
    }
});

关键实现说明

  • 字段规则定义:通过fieldRules数组定义每个日期时间字段的位置范围、最小/最大值,其中日期字段的最大值会根据年份和月份动态计算(处理闰年2月的情况)。
  • 实时验证逻辑:在keydown事件中,先判断是否为允许的特殊按键,然后调用isValidInput函数验证输入的数字在当前字段是否合法,不合法则调用e.preventDefault()阻止输入。
  • 动态日期验证:日期字段验证时会优先读取已输入的年份,若年份未输入则默认使用当前年份,确保日期范围的准确性。

内容的提问来源于stack exchange,提问作者J GURURAJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 06:20:27