JavaScript时间操作全攻略:从加减运算到格式化输出

JavaScript的一些时间计算方法 获取当前时间距n天后的日期:function GetDateFuture(AddDayCount) { var dd = new Date(); dd.setDate(dd.getDate() + AddDayCount); //获取AddDayCount天后的日期 var y = dd.getFullYear(); //获取当前月份的日期,不足10补... 阅读详情

1. 从零开始:理解JavaScript的Date对象

大家好,我是你们的老朋友,一个在JavaScript世界里摸爬滚打了十多年的开发者。今天我想和大家聊聊JavaScript中时间处理这个话题,这可能是每个前端开发者都会遇到,但又常常让人头疼的问题。我记得刚入行那会儿,为了计算两个日期相差多少天,我写了一大堆复杂的代码,结果还经常出错。后来才发现,原来JavaScript的Date对象已经帮我们做了很多工作,只是我们没用好它。

JavaScript的Date对象本质上是一个时间戳的封装,它记录的是从1970年1月1日00:00:00 UTC(协调世界时)开始经过的毫秒数。这个起点被称为“Unix纪元”。听起来有点抽象?我打个比方,这就像我们生活中用“公元”来纪年一样,1970年1月1日就是JavaScript时间的“公元元年”。

创建一个Date对象非常简单,有几种常用的方式:

// 1. 获取当前时间
let now = new Date();
console.log(now); // 输出类似:Mon Feb 17 2025 14:30:25 GMT+0800 (中国标准时间)

// 2. 从时间戳创建
let timestamp = 1739773825000; // 2025年2月17日的时间戳
let dateFromTimestamp = new Date(timestamp);

// 3. 从日期字符串创建
let dateFromString = new Date('2025-02-17T14:30:25');

// 4. 指定年月日等参数创建
let specificDate = new Date(2025, 1, 17, 14, 30, 25); // 注意:月份从0开始,1代表二月

这里有个坑我得提醒大家:当你用new Date(2025, 1, 17)这种方式创建日期时,第二个参数(月份)是从0开始计数的!也就是说,0代表一月,1代表二月,以此类推。这个设计确实有点反人类,我当年就因为这个踩过坑,明明想创建3月1日,结果写成了new Date(2025, 3, 1),实际得到的却是4月1日。

Date对象提供了很多方法来获取时间的各个部分。比如getFullYear()获取四位数的年份,getMonth()获取月份(0-11),getDate()获取一个月中的第几天(1-31)。这里又有一个容易混淆的地方:getDate()是获取日期(几号),而getDay()是获取星期几(0代表周日,1代表周一,以此类推)。我建议大家在写代码时,可以加个注释提醒自己,避免搞混。

在实际项目中,我经常看到有人用getYear()这个方法,这里我必须郑重提醒:千万不要用getYear()!这个方法在不同的浏览器中行为不一致,有些返回两位数的年份(比如2025年返回125),这是个历史遗留问题。正确的做法是始终使用getFullYear(),它保证返回四位数的完整年份。

2. 时间加减运算:避开那些常见的坑

时间加减是日常开发中最常见的需求之一。比如电商网站要计算优惠券的过期时间,社交应用要显示“3分钟前”这样的相对时间,或者报表系统要生成上个月的数据统计。JavaScript的Date对象提供了非常方便的自动校准功能,这让时间加减变得简单,但也需要理解它的工作原理。

2.1 基础加减:天、小时、分钟、秒

我们先从最简单的开始。给一个日期加一天,你会怎么写?我见过很多新手会这样写:

// 错误示范:直接修改getDate()的返回值
let date = new Date('2025-02-28');
let day = date.getDate(); // 28
date.setDate(day + 1); // 这样写虽然能工作,但不是最佳实践

其实有更优雅的方式。Date对象的setDate()、setMonth()、setFullYear()等方法有个很智能的特性:自动校准。这意味着你可以设置“超出范围”的值,JavaScript会自动帮你调整。比如:

// 正确做法:利用自动校准
let date = new Date('2025-02-28');
date.setDate(date.getDate() + 1); // 直接加1,JavaScript会自动处理跨月
console.log(date); // 输出:Sat Mar 01 2025 00:00:00 GMT+0800

// 同样的原理适用于其他时间单位
date.setHours(date.getHours() + 3); // 加3小时
date.setMinutes(date.getMinutes() + 30); // 加30分钟
date.setSeconds(date.getSeconds() + 45); // 加45秒

我封装了一个常用的时间加减函数,在实际项目中非常好用:

/**
 * 日期加减工具函数
 * @param {Date} date - 原始日期
 * @param {number} value - 要加减的值
 * @param {string} unit - 单位:'days'、'months'、'years'、'hours'、'minutes'、'seconds'
 * @returns {Date} 计算后的新日期
 */
function addTime(date, value, unit) {
    // 创建副本,避免修改原日期
    const result = new Date(date);
    
    switch(unit) {
        case 'days':
            result.setDate(result.getDate() + value);
            break;
        case 'months':
            result.setMonth(result.getMonth() + value);
            break;
        case 'years':
            result.setFullYear(result.getFullYear() + value);
            break;
        case 'hours':
            result.setHours(result.getHours() + value);
            break;
        case 'minutes':
            result.setMinutes(result.getMinutes() + value);
            break;
        case 'seconds':
            result.setSeconds(result.getSeconds() + value);
            break;
        default:
            throw new Error(`不支持的unit参数:${unit}`);
    }
    
    return result;
}

// 使用示例
let today = new Date();
let nextWeek = addTime(today, 7, 'days');
let lastMonth = addTime(today, -1, 'months');

2.2 处理月底和年底的特殊情况

时间加减中最容易出问题的地方就是月底和年底的边界情况。比如给2月28日加1个月,应该得到3月28日,但如果给1月31日加1个月呢?2月没有31日,这时候JavaScript的自动校准就派上用场了。

// 月底加一天:自动跳到次月第一天
let endOfMonth = new Date('2025-01-31');
endOfMonth.setDate(endOfMonth.getDate() + 1);
console.log(endOfMonth.getDate()); // 输出:1(2月1日)
console.log(endOfMonth.getMonth()); // 输出:1(二月)

// 年底加一个月:自动跳到次年一月
let endOfYear = new Date('2025-12-15');
endOfYear.setMonth(endOfYear.getMonth() + 1);
console.log(endOfYear.getFullYear()); // 输出:2026
console.log(endOfYear.getMonth()); // 输出:0(一月)

这里有个重要的细节需要注意:当你使用setMonth()进行月份加减时,如果目标月份的天数少于当前日期,JavaScript会自动调整到目标月份的最后一天。比如:

let date = new Date('2025-01-31');
date.setMonth(date.getMonth() + 1); // 加一个月
console.log(date); // 输出:Fri Feb 28 2025 00:00:00 GMT+0800
// 2月没有31日,所以自动调整到2月28日

在实际项目中,我建议对月底操作进行特殊处理。比如在做财务报表时,如果用户选择“加一个月”,通常希望得到的是下个月的同一天,但如果下个月没有这一天(比如1月31日到2月),业务上可能希望取最后一天。这时候就需要根据具体业务需求来写逻辑:

function addMonthsSafely(date, months) {
    const result = new Date(date);
    const originalDate = result.getDate();
    
    // 先加上月份
    result.setMonth(result.getMonth() + months);
    
    // 检查日期是否发生变化(说明目标月份天数较少)
    if (result.getDate() !== originalDate) {
        // 如果日期变了,说明自动调整到了目标月份的最后一天
        // 这里可以根据业务需求决定是否要调整
        // 比如有些业务希望始终返回月份的最后一天
        // 有些则希望返回目标月份中与原始日期最接近的一天
    }
    
    return result;
}

2.3 计算时间差:毫秒、秒、天、月

计算两个日期之间的差值也是常见需求。JavaScript让这个变得很简单,因为Date对象可以直接相减:

let date1 = new Date('2025-02-17');
let date2 = new Date('2025-02-20');
let diffInMs = date2 - date1; // 直接相减得到毫秒差
console.log(diffInMs); // 输出:259200000(3天的毫秒数)

// 转换为天数
let diffInDays = diffInMs / (1000 * 60 * 60 * 24);
console.log(diffInDays); // 输出:3

但是计算月份差就比较复杂了,因为每个月的天数不同。我写过一个计算月份差的函数,考虑了各种边界情况:

function monthDiff(startDate, endDate) {
    // 计算完整的年份差和月份差
    let years = endDate.getFullYear() - startDate.getFullYear();
    let months = endDate.getMonth() - startDate.getMonth();
    
    let totalMonths = years * 12 + months;
    
    // 调整:如果结束日期的日期小于开始日期的日期,说明不满一个月
    if (endDate.getDate() < startDate.getDate()) {
        totalMonths--;
    }
    
    return totalMonths;
}

// 使用示例
let start = new Date('2025-01-15');
let end = new Date('2025-03-10');
console.log(monthDiff(start, end)); // 输出:1(虽然跨了3个月,但实际只有1个完整月)

对于性能敏感的场景,比如需要频繁计算时间差,我推荐使用时间戳来计算,因为数字运算比Date对象的方法调用要快:

// 高性能的时间差计算
function dateDiffFast(date1, date2) {
    const time1 = date1.getTime(); // 转换为时间戳
    const time2 = date2.getTime();
    return Math.abs(time2 - time1); // 返回毫秒差
}

3. 时间戳与日期字符串的相互转换

时间戳和日期字符串的转换是前后端数据交互中的高频操作。后端通常返回时间戳或ISO格式的字符串,前端需要将其转换为用户友好的格式展示;用户输入的日期又需要转换为时间戳或特定格式的字符串传给后端。

3.1 时间戳转日期字符串:yyyy-MM-dd HH:mm:ss

时间戳转日期字符串是最常见的需求。虽然网上有很多现成的代码,但我发现很多都有点复杂或者考虑不周全。这里我分享一个经过多年实战检验的格式化函数:

/**
 * 时间戳转格式化字符串
 * @param {number|Date} timestamp - 时间戳(毫秒)或Date对象
 * @param {string} format - 格式,默认'yyyy-MM-dd HH:mm:ss'
 * @returns {string} 格式化后的日期字符串
 */
function formatDate(timestamp, format = 'yyyy-MM-dd HH:mm:ss') {
    // 处理Date对象或时间戳
    const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
    
    // 如果时间戳是秒级(10位),需要转换为毫秒
    if (typeof timestamp === 'number' && timestamp.toString().length === 10) {
        date.setTime(timestamp * 1000);
    }
    
    const map = {
        'yyyy': date.getFullYear(),
        'MM': String(date.getMonth() + 1).padStart(2, '0'),
        'dd': String(date.getDate()).padStart(2, '0'),
        'HH': String(date.getHours()).padStart(2, '0'),
        'mm': String(date.getMinutes()).padStart(2, '0'),
        'ss': String(date.getSeconds()).padStart(2, '0'),
        'M': date.getMonth() + 1, // 不补零的月份
        'd': date.getDate(),      // 不补零的日期
        'H': date.getHours(),     // 不补零的小时
        'm': date.getMinutes(),   // 不补零的分钟
        's': date.getSeconds()    // 不补零的秒
    };
    
    return format.replace(/yyyy|MM|dd|HH|mm|ss|M|d|H|m|s/g, matched => map[matched]);
}

// 使用示例
let timestamp = 1739773825123;
console.log(formatDate(timestamp)); // 输出:2025-02-17 14:30:25
console.log(formatDate(timestamp, 'yyyy年MM月dd日 HH:mm')); // 输出:2025年02月17日 14:30
console.log(formatDate(timestamp, 'yyyy-M-d H:m:s')); // 输出:2025-2-17 14:30:25

这个函数有几个亮点:第一,它同时支持Date对象和时间戳;第二,它能自动识别10位秒级时间戳并正确转换;第三,它支持多种格式,包括补零和不补零的版本;第四,使用正则表达式替换,性能较好。

3.2 日期字符串转时间戳

反过来,把日期字符串转成时间戳也很重要。这里最容易踩的坑是时区问题。我强烈建议在创建Date对象时,使用/作为分隔符,或者使用ISO 8601格式:

// 安全的方式:使用/分隔符
let date1 = new Date('2025/02/17 14:30:25');
console.log(date1.getTime()); // 正确的时间戳

// 安全的方式:ISO 8601格式
let date2 = new Date('2025-02-17T14:30:25');
console.log(date2.getTime()); // 正确的时间戳

// 危险的方式:直接使用yyyy-MM-dd HH:mm:ss格式
let date3 = new Date('2025-02-17 14:30:25');
// 在某些浏览器中,这种格式可能被解析为UTC时间,导致时区问题

如果你从后端接收到的就是yyyy-MM-dd HH:mm:ss格式的字符串,我建议先替换分隔符:

function parseDateString(dateStr) {
    // 将yyyy-MM-dd HH:mm:ss格式转换为yyyy/MM/dd HH:mm:ss
    const normalizedStr = dateStr.replace(/-/g, '/');
    return new Date(normalizedStr);
}

// 或者更健壮的版本
function parseDateStringSafe(dateStr) {
    // 处理多种可能的格式
    if (dateStr.includes('T')) {
        // ISO格式,直接解析
        return new Date(dateStr);
    } else if (dateStr.includes('-')) {
        // yyyy-MM-dd格式,替换分隔符
        return new Date(dateStr.replace(/-/g, '/'));
    } else {
        // 其他格式,尝试直接解析
        return new Date(dateStr);
    }
}

3.3 处理时区问题

时区问题是日期处理中最棘手的部分之一。我经历过一个项目,用户反馈说他们的预约时间总是差8小时,排查了半天才发现是时区问题。JavaScript的Date对象内部存储的是UTC时间,但显示时会根据本地时区转换。

// 创建日期时指定时区
let utcDate = new Date('2025-02-17T14:30:25Z'); // Z表示UTC时间
let localDate = new Date('2025-02-17T14:30:25+08:00'); // 东八区时间

console.log(utcDate.toLocaleString()); // 本地时间显示
console.log(localDate.toLocaleString()); // 本地时间显示

// 获取UTC时间
console.log(utcDate.getUTCHours()); // 获取UTC小时
console.log(localDate.getUTCHours()); // 获取UTC小时

// 时区偏移(分钟)
let offset = new Date().getTimezoneOffset(); // 中国标准时间返回-480
console.log(`本地时间比UTC快${-offset/60}小时`);

对于需要处理多时区的应用,我建议在存储和传输时都使用UTC时间,只在显示时转换为本地时间:

// 存储和传输:使用UTC时间戳或ISO字符串
function toUTCString(date) {
    return date.toISOString(); // 例如:2025-02-17T06:30:25.000Z
}

// 显示:根据用户时区格式化
function formatForUser(date, timezone = 'Asia/Shanghai') {
    return date.toLocaleString('zh-CN', {
        timeZone: timezone,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
    });
}

4. 实战应用:常见场景的完整解决方案

经过前面的学习,我们已经掌握了JavaScript时间处理的核心技能。现在让我们看看如何在实际项目中应用这些知识。我挑选了几个最常见的场景,给出完整的解决方案。

4.1 倒计时功能的实现

倒计时是电商、活动页面中的常见功能。实现一个健壮的倒计时需要考虑性能、准确性和用户体验。

class CountdownTimer {
    constructor(endTime, options = {}) {
        this.endTime = new Date(endTime).getTime();
        this.onTick = options.onTick || (() => {});
        this.onEnd = options.onEnd || (() => {});
        this.interval = null;
        this.isRunning = false;
        
        // 立即计算一次
        this.tick();
    }
    
    start() {
        if (this.isRunning) return;
        
        this.isRunning = true;
        this.interval = setInterval(() => {
            this.tick();
        }, 1000); // 每秒更新一次
    }
    
    stop() {
        this.isRunning = false;
        if (this.interval) {
            clearInterval(this.interval);
            this.interval = null;
        }
    }
    
    tick() {
        const now = Date.now();
        const remaining = this.endTime - now;
        
        if (remaining <= 0) {
            this.stop();
            this.onEnd();
            return;
        }
        
        // 计算天、小时、分钟、秒
        const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
        const hours = Math.floor((remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
        const seconds = Math.floor((remaining % (1000 * 60)) / 1000);
        
        this.onTick({
            days,
            hours,
            minutes,
            seconds,
            totalMs: remaining
        });
    }
    
    // 格式化显示
    static formatTime({days, hours, minutes, seconds}) {
        if (days > 0) {
            return `${days}天${hours}小时${minutes}分${seconds}秒`;
        } else if (hours > 0) {
            return `${hours}小时${minutes}分${seconds}秒`;
        } else if (minutes > 0) {
            return `${minutes}分${seconds}秒`;
        } else {
            return `${seconds}秒`;
        }
    }
}

// 使用示例
const timer = new CountdownTimer('2025-02-20 23:59:59', {
    onTick: (time) => {
        console.log(`剩余时间:${CountdownTimer.formatTime(time)}`);
    },
    onEnd: () => {
        console.log('倒计时结束!');
    }
});

timer.start();

这个倒计时类有几个优点:第一,它使用类封装,便于复用;第二,它提供了开始、停止的控制方法;第三,它支持回调函数,方便与UI框架集成;第四,它考虑了性能,使用Date.now()而不是new Date()获取当前时间。

4.2 相对时间显示(如"3分钟前")

社交应用、新闻网站经常需要显示相对时间,比如"刚刚"、"3分钟前"、"昨天"等。这种显示方式比完整的日期时间更友好。

function formatRelativeTime(date, baseDate = new Date()) {
    const targetTime = date instanceof Date ? date.getTime() : new Date(date).getTime();
    const baseTime = baseDate.getTime();
    const diffInMs = baseTime - targetTime;
    const diffInSeconds = Math.floor(diffInMs / 1000);
    const diffInMinutes = Math.floor(diffInSeconds / 60);
    const diffInHours = Math.floor(diffInMinutes / 60);
    const diffInDays = Math.floor(diffInHours / 24);
    
    // 如果是未来时间
    if (diffInMs < 0) {
        return '刚刚';
    }
    
    // 规则定义
    const rules = [
        {limit: 60, unit: '秒', text: '刚刚'},
        {limit: 3600, unit: '分钟', calc: () => diffInMinutes},
        {limit: 86400, unit: '小时', calc: () => diffInHours},
        {limit: 2592000, unit: '天', calc: () => diffInDays},
        {limit: 31536000, unit: '个月', calc: () => Math.floor(diffInDays / 30)},
        {limit: Infinity, unit: '年', calc: () => Math.floor(diffInDays / 365)}
    ];
    
    const diffInSecondsAbs = Math.abs(diffInSeconds);
    
    for (const rule of rules) {
        if (diffInSecondsAbs < rule.limit) {
            if (rule.text) return rule.text;
            const value = rule.calc();
            return `${value}${rule.unit}前`;
        }
    }
    
    // 超过一年,显示完整日期
    return formatDate(date, 'yyyy-MM-dd');
}

// 增强版:支持更多人性化显示
function formatRelativeTimeSmart(date) {
    const now = new Date();
    const target = new Date(date);
    const diffInDays = Math.floor((now - target) / (1000 * 60 * 60 * 24));
    
    // 今天
    if (diffInDays === 0) {
        const diffInHours = Math.floor((now - target) / (1000 * 60 * 60));
        if (diffInHours === 0) {
            const diffInMinutes = Math.floor((now - target) / (1000 * 60));
            if (diffInMinutes === 0) {
                return '刚刚';
            }
            return `${diffInMinutes}分钟前`;
        }
        return `${diffInHours}小时前`;
    }
    
    // 昨天
    if (diffInDays === 1) {
        return '昨天 ' + formatDate(target, 'HH:mm');
    }
    
    // 本周内
    if (diffInDays < 7) {
        const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
        return weekdays[target.getDay()] + ' ' + formatDate(target, 'HH:mm');
    }
    
    // 今年内
    if (target.getFullYear() === now.getFullYear()) {
        return formatDate(target, 'MM月dd日 HH:mm');
    }
    
    // 其他
    return formatDate(target, 'yyyy年MM月dd日');
}

// 使用示例
console.log(formatRelativeTimeSmart(new Date())); // 刚刚
console.log(formatRelativeTimeSmart(new Date(Date.now() - 5 * 60 * 1000))); // 5分钟前
console.log(formatRelativeTimeSmart(new Date(Date.now() - 25 * 60 * 60 * 1000))); // 昨天 14:30

4.3 日期范围选择与验证

在表单中,日期范围选择是常见需求。比如选择入住和离店日期、选择生日范围等。这里我分享一个完整的日期范围处理工具:

class DateRangeValidator {
    /**
     * 验证日期范围是否有效
     * @param {Date} startDate - 开始日期
     * @param {Date} endDate - 结束日期
     * @param {Object} options - 配置选项
     * @returns {Object} 验证结果
     */
    static validateRange(startDate, endDate, options = {}) {
        const {
            minDays = 1,    // 最小天数
            maxDays = 365,  // 最大天数
            allowSameDay = false, // 是否允许同一天
            disabledDates = []    // 禁用日期
        } = options;
        
        // 确保是Date对象
        const start = new Date(startDate);
        const end = new Date(endDate);
        
        // 清除时间部分,只比较日期
        start.setHours(0, 0, 0, 0);
        end.setHours(0, 0, 0, 0);
        
        const result = {
            isValid: true,
            error: null,
            days: 0
        };
        
        // 计算天数差
        const timeDiff = end.getTime() - start.getTime();
        const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
        result.days = dayDiff;
        
        // 验证开始日期不能晚于结束日期
        if (dayDiff < 0) {
            result.isValid = false;
            result.error = '开始日期不能晚于结束日期';
            return result;
        }
        
        // 验证是否允许同一天
        if (dayDiff === 0 && !allowSameDay) {
            result.isValid = false;
            result.error = '开始日期和结束日期不能是同一天';
            return result;
        }
        
        // 验证最小天数
        if (dayDiff < minDays - 1) { // -1是因为包含开始日期
            result.isValid = false;
            result.error = `最少需要选择${minDays}天`;
            return result;
        }
        
        // 验证最大天数
        if (dayDiff > maxDays - 1) {
            result.isValid = false;
            result.error = `最多只能选择${maxDays}天`;
            return result;
        }
        
        // 检查禁用日期
        for (let i = 0; i <= dayDiff; i++) {
            const currentDate = new Date(start);
            currentDate.setDate(start.getDate() + i);
            
            // 检查是否在禁用日期中
            const isDisabled = disabledDates.some(disabledDate => {
                const disabled = new Date(disabledDate);
                disabled.setHours(0, 0, 0, 0);
                return currentDate.getTime() === disabled.getTime();
            });
            
            if (isDisabled) {
                result.isValid = false;
                result.error = `选择的日期中包含不可用的日期:${formatDate(currentDate, 'yyyy-MM-dd')}`;
                return result;
            }
        }
        
        return result;
    }
    
    /**
     * 生成日期范围内的所有日期
     * @param {Date} startDate - 开始日期
     * @param {Date} endDate - 结束日期
     * @returns {Date[]} 日期数组
     */
    static generateDateRange(startDate, endDate) {
        const start = new Date(startDate);
        const end = new Date(endDate);
        start.setHours(0, 0, 0, 0);
        end.setHours(0, 0, 0, 0);
        
        const dates = [];
        const current = new Date(start);
        
        while (current <= end) {
            dates.push(new Date(current));
            current.setDate(current.getDate() + 1);
        }
        
        return dates;
    }
    
    /**
     * 获取两个日期的交集
     * @param {Object} range1 - 第一个日期范围 {start, end}
     * @param {Object} range2 - 第二个日期范围 {start, end}
     * @returns {Object|null} 交集范围,如果没有交集返回null
     */
    static getIntersection(range1, range2) {
        const start1 = new Date(range1.start);
        const end1 = new Date(range1.end);
        const start2 = new Date(range2.start);
        const end2 = new Date(range2.end);
        
        // 计算交集的开始和结束
        const intersectionStart = start1 > start2 ? start1 : start2;
        const intersectionEnd = end1 < end2 ? end1 : end2;
        
        // 如果没有交集
        if (intersectionStart > intersectionEnd) {
            return null;
        }
        
        return {
            start: intersectionStart,
            end: intersectionEnd,
            days: Math.floor((intersectionEnd - intersectionStart) / (1000 * 60 * 60 * 24)) + 1
        };
    }
}

// 使用示例
const bookingRange = {
    start: '2025-02-20',
    end: '2025-02-25'
};

const validation = DateRangeValidator.validateRange(
    bookingRange.start,
    bookingRange.end,
    {
        minDays: 2,
        maxDays: 30,
        allowSameDay: false,
        disabledDates: ['2025-02-22', '2025-02-23'] // 禁用这两天
    }
);

if (validation.isValid) {
    console.log(`预订成功,共${validation.days}天`);
} else {
    console.log(`预订失败:${validation.error}`);
}

// 生成日期范围
const dateRange = DateRangeValidator.generateDateRange('2025-02-20', '2025-02-25');
console.log(dateRange.map(date => formatDate(date, 'yyyy-MM-dd')));
// 输出:['2025-02-20', '2025-02-21', '2025-02-22', '2025-02-23', '2025-02-24', '2025-02-25']

4.4 工作日计算和节假日排除

在企业应用中,经常需要计算工作日,排除周末和节假日。这是一个比较复杂但实用的功能:

class WorkdayCalculator {
    constructor(options = {}) {
        this.weekends = options.weekends || [0, 6]; // 默认周末是周日(0)和周六(6)
        this.holidays = options.holidays || []; // 节假日数组
        this.workdays = options.workdays || []; // 调休工作日数组
    }
    
    /**
     * 判断是否是工作日
     * @param {Date} date - 要判断的日期
     * @returns {boolean} 是否是工作日
     */
    isWorkday(date) {
        const day = date.getDay();
        
        // 检查是否是周末
        if (this.weekends.includes(day)) {
            // 如果是周末,检查是否是调休工作日
            return this.workdays.some(workday => 
                this.isSameDay(date, new Date(workday))
            );
        }
        
        // 检查是否是节假日
        const isHoliday = this.holidays.some(holiday =>
            this.isSameDay(date, new Date(holiday))
        );
        
        return !isHoliday;
    }
    
    /**
     * 计算两个日期之间的工作日天数
     * @param {Date} startDate - 开始日期
     * @param {Date} endDate - 结束日期
     * @param {boolean} inclusive - 是否包含开始和结束日期
     * @returns {number} 工作日天数
     */
    getWorkdaysBetween(startDate, endDate, inclusive = true) {
        const start = new Date(startDate);
        const end = new Date(endDate);
        
        // 确保开始日期不晚于结束日期
        if (start > end) {
            [start, end] = [end, start];
        }
        
        start.setHours(0, 0, 0, 0);
        end.setHours(0, 0, 0, 0);
        
        let workdayCount = 0;
        const current = new Date(start);
        
        // 调整包含性
        if (!inclusive) {
            current.setDate(current.getDate() + 1);
        }
        
        const endDateForLoop = new Date(end);
        if (!inclusive) {
            endDateForLoop.setDate(endDateForLoop.getDate() - 1);
        }
        
        // 遍历每一天
        while (current <= endDateForLoop) {
            if (this.isWorkday(current)) {
                workdayCount++;
            }
            current.setDate(current.getDate() + 1);
        }
        
        return workdayCount;
    }
    
    /**
     * 添加n个工作日后的日期
     * @param {Date} startDate - 开始日期
     * @param {number} workdays - 要添加的工作日数
     * @returns {Date} 计算后的日期
     */
    addWorkdays(startDate, workdays) {
        if (workdays === 0) return new Date(startDate);
        
        const result = new Date(startDate);
        const direction = workdays > 0 ? 1 : -1;
        let remaining = Math.abs(workdays);
        
        while (remaining > 0) {
            result.setDate(result.getDate() + direction);
            
            if (this.isWorkday(result)) {
                remaining--;
            }
        }
        
        return result;
    }
    
    /**
     * 判断两个日期是否是同一天
     * @private
     */
    isSameDay(date1, date2) {
        return date1.getFullYear() === date2.getFullYear() &&
               date1.getMonth() === date2.getMonth() &&
               date1.getDate() === date2.getDate();
    }
}

// 使用示例:配置中国的节假日和调休
const chinaHolidays2025 = [
    '2025-01-01', // 元旦
    '2025-01-28', '2025-01-29', '2025-01-30', // 春节
    '2025-04-04', '2025-04-05', '2025-04-06', // 清明节
    '2025-05-01', '2025-05-02', '2025-05-03', // 劳动节
    '2025-06-10', // 端午节
    '2025-09-17', '2025-09-18', // 中秋节
    '2025-10-01', '2025-10-02', '2025-10-03', '2025-10-04', '2025-10-05', '2025-10-06', '2025-10-07' // 国庆节
];

const chinaWorkdays2025 = [
    '2025-01-26', // 春节调休
    '2025-02-08', // 春节调休
    '2025-04-27', // 劳动节调休
    '2025-05-10', // 劳动节调休
    '2025-09-14', // 中秋节调休
    '2025-09-29', // 国庆节调休
    '2025-10-12'  // 国庆节调休
];

const calculator = new WorkdayCalculator({
    weekends: [0, 6], // 周六周日休息
    holidays: chinaHolidays2025,
    workdays: chinaWorkdays2025
});

// 计算2025年2月的工作日
const start = new Date('2025-02-01');
const end = new Date('2025-02-28');
const workdaysInFeb = calculator.getWorkdaysBetween(start, end);
console.log(`2025年2月有${workdaysInFeb}个工作日`);

// 计算10个工作日后的日期
const today = new Date();
const deadline = calculator.addWorkdays(today, 10);
console.log(`今天起10个工作日后的日期是:${formatDate(deadline, 'yyyy-MM-dd')}`);

// 检查特定日期是否是工作日
const testDate = new Date('2025-02-08'); // 春节调休,应该是工作日
console.log(`${formatDate(testDate, 'yyyy-MM-dd')}是工作日吗?${calculator.isWorkday(testDate)}`);

通过这些实战案例,我们可以看到,JavaScript的日期处理虽然有一些坑,但只要掌握了正确的方法,就能写出健壮、高效的代码。关键是要理解Date对象的工作原理,特别是时区和自动校准的特性,然后根据具体业务需求封装合适的工具函数。在实际项目中,我建议将常用的日期处理函数封装成工具类,这样既能提高开发效率,也能保证代码的一致性。

MobileNet V3 图像分类 MobileNet V3 阅读详情

相关推荐

宿舍网速跑不满?可能是PPPoE的锅!实测OpenWrt切换DHCP+深澜认证,轻松跑满校园百兆宽带

本文探讨了校园网PPPoE协议的性能瓶颈问题,并提供了通过OpenWrt路由器切换DHCP+深澜认证的技术方案,实测可显著提升网速至百兆宽带满速。文章详细介绍了配置步骤、性能对比及常见问题解决方案,帮助用户优化校园网体验。

weixin_31310913的博客 383

JavaScript如何格式化字符串

使用模板字面量是最现代和灵活的方法,它允许你在字符串中直接插入表达式和变量。创建的字符串可以包含表达式,这些表达式会被相应的值替换。对于更复杂的数字和日期格式化,你可以使用。运算符将字符串和变量连接在一起。方法结合占位符来格式化字符串。

m0_57236802的博客 4989

钢铁雄心4存档修改全攻略:从基础设置到高级玩法(附国家代码表)

本文详细解析《钢铁雄心4》存档修改技巧,从基础设置到高级玩法全覆盖。包括存档文件位置、格式转换、国家实力调整、国际关系修改等实用内容,并附赠国家代码表。掌握存档修改技能,解锁游戏全部潜力,实现精细参数控制,超越控制台指令的局限。

weixin_29233857的博客 1431

关于使用JS获取当前时间并格式化输出

js获取当前时间,格式化输出当前时间,一步提取当前时间的年月日和时分秒

weixin_43426379的博客 5003

SIwave学习记录:电热耦合仿真

SIwave电热耦合仿真

m0_68174736的博客 2335

ubuntu18.04安装R——无法连接上 dl.google.com:80 (6.6.6.6),连接超时&&E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。

安装R的时候,sudo apt update 的时候出现错误: 查资料要修改/etc/apt/sources.list.d/google-chrome.list 文件 原来是: deb http://dl.google.com/linux/chrome/deb/ stable main 改为: deb [arch=amd64] http://dl.google.com/linux/chrom...

许希律的博客 4348

封闭函数

封闭函数 函数只会执行一次 想把多行代码保存到一个函数中,便于维护管理 程序一启动就会执行 避免函数名冲突 工作后接收老项目 有时候出现函数名冲突的解决方案 语法1: <script> window.onload = function() { (function() { alert('hello'); ...

磨镜台的博客 760

YOLO11全网首发:CVPR2026 MixerCSeg | DEGConv方向引导边缘门控,破解细长裂缝检测难题

DEGConv通过方向先验嵌入、边缘增强卷积和动态门控三大机制,针对性解决了YOLO在处理裂缝类细长、多方向、低对比度目标时的特征提取不足问题,显著提升了裂缝检测的精度与鲁棒性。

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 561

JS获取时间

JS获取时间 一、JS获取当前时间 var myDate = new Date(); myDate.getYear(); //获取当前年份(2位) myDate.getFullYear(); //获取完整的年份(4位,1970-????) myDate.getMonth(); //获取当前月份(0-11,0代表1月) myDate.getDate(); //获取当前日(1-31) myDate.getDay(); //获取当前星期X(0-6,0代

沐沐Six的博客 2万+

sim800c插卡后指示灯还是一秒一闪无法接收发送短信,如何解决?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1167

“华为杯”研究生数学建模竞赛2020年-【华为杯】B题:汽油辛烷值优化建模(附优秀论文)

摘 要:本文通过机器学习算法和数据挖掘技术,建立了汽油辛烷值(RON)损失的预测模型,在保证汽油产品脱硫效果的前提下,以降低汽油辛烷值损失在30%以上为目标,给出每个样本的优化操作条件。针对第一问,对285号和313号原始数据用给定的数据处理规范进行预处理,对每一列变量求期望,得到最终的数据补充到相应的样本编号。针对第二问,首先对数据样本按照最大最小限幅以及拉依达法则进行列变量清洗,通过两轮特征筛选确定变量:结合处理后的数据,针对产品中硫含量、RON损失值用。

getusushu的博客 1251

彻底搞懂vue-vben-admin时间处理:从格式化到计算全攻略

你是否还在为后台系统中的日期时间格式混乱而头疼?报表导出时时间显示错误、数据筛选时时间范围计算繁琐、用户操作日志时间戳难以阅读?本文将带你系统掌握vue-vben-admin框架的时间处理方案,从基础格式化到高级计算,结合实际业务场景,让时间处理效率提升80%。读完本文你将学会: - 3种核心日期格式化方法及场景适配 - 5类常用时间计算函数的实战应用 - 如何在表格/表单组件中集成时间处理 -...

gitblog_00942的博客 760

Python实战项目2:b站评论采集

本文不对数据采集的过程做探讨,直接上代码。

lyccomcn的博客 3199

ngx-moment常见问题解答:从安装到部署全攻略

ngx-moment是一个专为Angular应用打造的时间处理库,它基于moment.js提供了丰富的管道(Pipes)功能,帮助开发者轻松实现时间格式化、相对时间显示、时区转换等常见需求。无论是新手还是有经验的开发者,在使用ngx-moment过程中都可能遇到各种问题,本文将从安装配置到高级用法,全面解答常见疑问,助你快速掌握这个实用工具。 ## 基础安装与环境配置 ### 一键安装步骤

gitblog_00348的博客 506

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

诸神缄默不语-个人技术博文与视频目录

诸神缄默不语的优快云博文目录

诸神缄默不语的博客 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

js 时间函数 及相关运算大全

var myDate = new Date(); myDate.getYear(); //获取当前年份(2位) myDate.getFullYear(); //获取完整的年份(4位,1970-????) myDate.getMonth(); //获取当前月份(0-11,0代表1月) myDate.getDate(); //获取当前日(1-31)...

iteye_5220的博客 186

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

JavaScript Json格式化输出

bodyStr为要展示的string obj为对应的json对象 if (isJson(bodyStr)){ // 是json,格式化输出 let obj = JSON.parse(bodyStr) this.body = JSON.stringify(obj, null, 4) }

zhanghe_zht的博客 1046

JS格式化输出常用格式

JS常用的格式化输出;字符串每隔几位添加符号、格式化金额、隐藏手机号中间四位、隐藏身份证号中间几位

weixin_42928781的博客 1750

JavaScript日期相关计算

js日期相关计算

qq_37899622的博客 3560

javascript格式化输出的实现(MuJS)

MuJS是一款支持嵌入式开发的轻量级javascript解释器。 javascript主要是针对web开发的脚本语言,所以主要借助浏览器来调试;但mujs针对的是嵌入式开发,调试时的交互主要靠输入输出终端,javascript本身是不支持的,所以需要调用c语言的打印函数来实现。 下面给出两个例子,一个是普通输出,符合脚本语言的解释性语言的特点,不考虑变量的类型;另一个是简单的格式化输出。前一个...

学习中~~~~ 3319

JavaScript 日期计算

计算当前日期的后一天 var now = new Date(); now.setDate(now.getDate() + 1); console.log(now);//VM153:3 Thu Sep 28 2017 09:26:05 GMT+0800 (中国标准时间) 已知开始日期和天数,计算结束日期 var str = '2016-06-27'; //定义开始日期 var startDate = new Date(Date.parse(str .replace(/-/g, '/')));

逍遥游 1422

js时间计算

总之,JavaScript中的Date对象可以用来计算时间,它提供了一系列的方法,可以用来计算时间差、日期和时间等。JavaScript是一种强大的编程语言,它可以用来实现各种功能,其中之一就是计算时间。上面的代码中,我们首先获取了当前时间的毫秒数,然后假设一段时间后,再计算两个时间之间的时间差,最后将时间差转换为小时。上面的代码中,我们首先获取了当前时间,然后计算一天中的第三个小时,最后将结果输出到控制台。上面的代码中,我们首先获取了当前日期,然后计算一周后的日期,最后将结果输出到控制台。

zhanchulan的博客 3698

JavaScript--关于时间的计算

关于时间的计算

weixin_45406850的博客 991

JavaScript零基础入门 7:JavaScript基础函数

一、日期 1、Date JavaScript中用Date()函数获取时间。 var date = new Date(); 2、创建一个指定的时间对象 需要在构造函数中传递一个表示时间的字符串作为参数,日期的格式月份/日/年时:分:秒 var date2 = new Date("11/2/2021 18:15:00"); 3、getDate() 获取当前日期对象是几日。 4、getDay() 获取当前日期对象时周几,会返回一个0-6的值,0表示周日,1 表示周一。。。 5、g...

学Java,找哪吒 1万+

javaScript中时间的加减

首先在javascript说起是间就离不开Date()这个对象 创建Date对象的语法: var Time=new Date(); //Date返回当日的时间和日期 然后的例getDate,getDay(),getMonth()这些方法都是从Date对象返回值的; 不过在这里我不是要写这些方法 我是要写关于对于时间,日期之间的运算我所了解的部分; 首先从时间的加减说起吧; 由于js中没有指定的日期加减方法所以只能通过Date对象获取当前日期加减以此达到操作日期的目的; 具体方法如下; .

weixin_49758946的博客 9874

javascript的一些时间的运算

在用到时间验证时,后台验证会浪费服务器资源,用户也会有等待,因此想到用自定义验证控件,下面放入了一些经常用的时间运算由于客户端验证控件不允许前台修改错误信息,所以用个td来代替。为了防止空格和无内容时也会抖动,td在无错误时加入文字同时,此例中的送花日期TextBox6用到了梅花雪的日历控件。因为日期型数据默认读取的日期为yyyy-M-d型,梅花雪控件日期形式为yyyy-MM-dd型,所以都需

zss1100(打你鸟鸟头)的专栏 2285
上一篇: Dify知识库元数据实战:3分钟学会用标签管理海量文档(附避坑指南)
下一篇: PatchCore实战解析:如何通过补丁特征记忆库提升工业图像异常检测效率
postgres8guard
博客等级 码龄4年 62粉丝 1443原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值