264 lines
9.0 KiB
TypeScript
264 lines
9.0 KiB
TypeScript
/**
|
||
* 计算两个日期之间相差的天数
|
||
* @param date1 开始日期
|
||
* @param date2 结束日期
|
||
* @returns 相差天数(绝对值)
|
||
*/
|
||
export function diffDays(date1: string | Date = new Date(), date2: string | Date = new Date()): number {
|
||
const d1 = new Date(date1);
|
||
const d2 = new Date(date2);
|
||
// 转为时间戳并取绝对值
|
||
const diff = Math.abs(d1.getTime() - d2.getTime());
|
||
// 一天的毫秒数
|
||
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
||
}
|
||
/**
|
||
* 计算两个日期之间的自然日相差天数
|
||
* 规则:同一天 = 0,昨天 = 1,前天 = 2,以此类推
|
||
* 忽略时分秒,纯日期对比
|
||
* @param date1 日期1,默认为当前日期
|
||
* @param date2 日期2,默认为当前日期
|
||
* @returns 自然日相差天数(非负整数)
|
||
* @example getNaturalDayDiff(今日, 今日) => 0
|
||
* @example getNaturalDayDiff(今日, 昨日) => 1
|
||
*/
|
||
export function getNaturalDayDiff(date1: string | Date = new Date(), date2: string | Date = new Date()): number {
|
||
const getDateTimestamp = (date: string | Date) => {
|
||
const d = new Date(date);
|
||
d.setHours(0, 0, 0, 0);
|
||
return d.getTime();
|
||
};
|
||
|
||
const time1 = getDateTimestamp(date1);
|
||
const time2 = getDateTimestamp(date2);
|
||
|
||
return Math.floor(Math.abs(time1 - time2) / (1000 * 60 * 60 * 24));
|
||
}
|
||
|
||
/**
|
||
* 格式化两个时间的差值,自动显示 年/月/天/时/分/秒
|
||
* @param startTime 开始时间(Date / 时间戳 / 字符串)
|
||
* @param endTime 结束时间,默认当前时间
|
||
* @returns 如:2年3个月、15天、2小时30分、50秒
|
||
*/
|
||
export function formatTimeDiff(startTime: string | number | Date, endTime: string | number | Date = new Date()): string {
|
||
const start = new Date(startTime).getTime();
|
||
const end = new Date(endTime).getTime();
|
||
|
||
// 毫秒差值(绝对值)
|
||
let diff = Math.abs(end - start);
|
||
if (diff < 1000) return '0秒';
|
||
|
||
// 单位定义
|
||
const second = 1000;
|
||
const minute = 60 * second;
|
||
const hour = 60 * minute;
|
||
const day = 24 * hour;
|
||
const month = 30 * day; // 粗略按月计算
|
||
const year = 365 * day;
|
||
|
||
// 计算
|
||
const years = Math.floor(diff / year);
|
||
diff -= years * year;
|
||
|
||
const months = Math.floor(diff / month);
|
||
diff -= months * month;
|
||
|
||
const days = Math.floor(diff / day);
|
||
diff -= days * day;
|
||
|
||
const hours = Math.floor(diff / hour);
|
||
diff -= hours * hour;
|
||
|
||
const minutes = Math.floor(diff / minute);
|
||
diff -= minutes * minute;
|
||
|
||
const seconds = Math.floor(diff / second);
|
||
|
||
// 拼接显示
|
||
const res: string[] = [];
|
||
if (years > 0) res.push(years + '年');
|
||
if (months > 0) res.push(months + '个月');
|
||
if (days > 0) res.push(days + '天');
|
||
if (hours > 0) res.push(hours + '小时');
|
||
if (minutes > 0) res.push(minutes + '分钟');
|
||
if (seconds > 0 && res.length === 0) res.push(seconds + '秒');
|
||
|
||
return res.join('');
|
||
}
|
||
|
||
/**
|
||
* 生成指定日期所在周的日期数组(周一到周日)
|
||
* @param date 基准日期,默认今天
|
||
* @param format 返回格式
|
||
* @returns 一周日期数组
|
||
*/
|
||
export function generateWeekDays(date: Date | string | number = new Date(), format: 'name' | 'date' | 'full' = 'name'): string[] {
|
||
const weekNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
||
const baseDate = new Date(date);
|
||
|
||
// 计算本周一的日期(getDay() 0=周日,1=周一)
|
||
const day = baseDate.getDay();
|
||
const monday = new Date(baseDate);
|
||
monday.setDate(baseDate.getDate() - (day === 0 ? 6 : day - 1));
|
||
|
||
// 生成周一到周日7天
|
||
const week = [];
|
||
for (let i = 0; i < 7; i++) {
|
||
const current = new Date(monday);
|
||
current.setDate(monday.getDate() + i);
|
||
|
||
if (format === 'name') {
|
||
week.push(weekNames[i]);
|
||
} else if (format === 'date') {
|
||
// YYYY-MM-DD 格式
|
||
const year = current.getFullYear();
|
||
const month = String(current.getMonth() + 1).padStart(2, '0');
|
||
const day = String(current.getDate()).padStart(2, '0');
|
||
week.push(`${year}-${month}-${day}`);
|
||
} else {
|
||
// 完整格式:周一 (2026-04-20)
|
||
const year = current.getFullYear();
|
||
const month = String(current.getMonth() + 1).padStart(2, '0');
|
||
const day = String(current.getDate()).padStart(2, '0');
|
||
week.push(`${weekNames[i]} (${year}-${month}-${day})`);
|
||
}
|
||
}
|
||
|
||
return week;
|
||
}
|
||
|
||
/**
|
||
* 根据数字 1-7 生成对应星期
|
||
* @param num 1=周一,2=周二...7=周日
|
||
* @returns 周一 / 周二 ... / 周日
|
||
*/
|
||
export function numToWeek(num: number): string {
|
||
const weekMap = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
||
return weekMap[num - 1] || '';
|
||
}
|
||
|
||
/**
|
||
* 根据数字 1-7 生成对应星期
|
||
* @param num 1=周一,2=周二...7=周日
|
||
* @returns 周一 / 周二 ... / 周日
|
||
*/
|
||
export function numToWeek2(num: number): string {
|
||
const weekMap = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
|
||
return weekMap[num - 1] || '';
|
||
}
|
||
|
||
// 生成过去7天的日期
|
||
export const getLast7Days = (days = 7) => {
|
||
const dates = [];
|
||
for (let i = days - 1; i >= 0; i--) {
|
||
dates.push(new Date(new Date().getTime() - i * 24 * 60 * 60 * 1000).toLocaleDateString());
|
||
}
|
||
return dates;
|
||
};
|
||
|
||
/**
|
||
* 高性能生成从指定结束日期向前追溯 N 天的连续日期数组
|
||
* 规则:0=今天,1=昨天,2=前天,以此类推
|
||
* 支持自定义日期格式(YYYY/MM/DD/HH/mm/ss)或传入格式化函数
|
||
* 优化:正则模板仅编译1次、时间戳计算、数组预分配,无性能损耗
|
||
* @category 日期工具
|
||
* @param days 向前追溯天数,0=今天,1=昨天,默认 30 天
|
||
* @param endDate 结束日期,支持 Date 对象 / 日期字符串,默认当前日期
|
||
* @param format 日期格式化规则,支持模板字符串或自定义格式化函数,默认 'YYYY-MM-DD'
|
||
* 支持模板占位符:YYYY(年)、MM(月)、DD(日)、HH(时)、mm(分)、ss(秒)
|
||
* @returns 格式化后的日期字符串数组(按时间正序排列)
|
||
* @example
|
||
* // 获取今天
|
||
* getLastNDays(0)
|
||
* @example
|
||
* // 获取昨天
|
||
* getLastNDays(1)
|
||
* @example
|
||
* // 最近7天(含今天)
|
||
* getLastNDays(7)
|
||
*/
|
||
export function getLastNDays(
|
||
days: number = 30,
|
||
endDate: Date | string = new Date(),
|
||
format: string | ((date: Date) => string) = 'YYYY-MM-DD'
|
||
): string[] {
|
||
// 非法天数校验
|
||
if (!Number.isInteger(days) || days < 0) {
|
||
console.warn('getLastNDays: days 必须是大于等于 0 的整数');
|
||
return [];
|
||
}
|
||
|
||
// 安全解析结束日期,并【重置时分秒为 00:00:00】— 关键修复
|
||
const end = new Date(endDate);
|
||
if (isNaN(end.getTime())) {
|
||
console.warn('getLastNDays: 无效日期,已自动使用当前时间');
|
||
end.setTime(Date.now());
|
||
}
|
||
// ✅ 修复:统一把时间设为 00:00:00,避免跨天计算错误
|
||
end.setHours(0, 0, 0, 0);
|
||
const endTime = end.getTime();
|
||
const oneDay = 86400000;
|
||
|
||
// 提前编译格式化函数(核心性能优化:正则只编译一次)
|
||
let formatter: (date: Date) => string;
|
||
if (typeof format === 'function') {
|
||
formatter = format;
|
||
} else {
|
||
formatter = (date: Date) => {
|
||
const YYYY = date.getFullYear().toString();
|
||
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
||
const DD = String(date.getDate()).padStart(2, '0');
|
||
const HH = String(date.getHours()).padStart(2, '0');
|
||
const mm = String(date.getMinutes()).padStart(2, '0');
|
||
const ss = String(date.getSeconds()).padStart(2, '0');
|
||
|
||
return format.replace(/YYYY/g, YYYY).replace(/MM/g, MM).replace(/DD/g, DD).replace(/HH/g, HH).replace(/mm/g, mm).replace(/ss/g, ss);
|
||
};
|
||
}
|
||
|
||
// 生成日期
|
||
const result: string[] = [];
|
||
for (let i = 0; i <= days; i++) {
|
||
const currentDate = new Date(endTime - i * oneDay);
|
||
result.push(formatter(currentDate));
|
||
}
|
||
|
||
// 正序返回
|
||
return result.reverse();
|
||
}
|
||
/**
|
||
* 格式化 Date 对象为指定格式的字符串
|
||
* 高性能正则解析,支持任意分隔符,仅编译一次模板
|
||
* @param date 要格式化的日期,默认:当前时间 new Date()
|
||
* @param format 格式化模板,默认:YYYY-MM-DD
|
||
* 支持占位符:YYYY(年)、MM(月)、DD(日)、HH(时)、mm(分)、ss(秒)
|
||
* @returns 格式化后的日期字符串
|
||
* @category 日期工具
|
||
* @example
|
||
* formatDate() // 今天 → 2025-12-20
|
||
* @example
|
||
* formatDate(new Date(), 'YYYY年MM月DD日')
|
||
* @example
|
||
* formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||
*/
|
||
export function formatDate(date: Date | string = new Date(), format: string = 'YYYY-MM-DD'): string {
|
||
// 解析并校验日期
|
||
const d = new Date(date);
|
||
if (isNaN(d.getTime())) {
|
||
console.warn('formatDate: 无效日期,返回空字符串');
|
||
return '';
|
||
}
|
||
|
||
// 获取并补零
|
||
const YYYY = d.getFullYear().toString();
|
||
const MM = String(d.getMonth() + 1).padStart(2, '0');
|
||
const DD = String(d.getDate()).padStart(2, '0');
|
||
const HH = String(d.getHours()).padStart(2, '0');
|
||
const mm = String(d.getMinutes()).padStart(2, '0');
|
||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||
|
||
// 正则替换(通用、无需穷举)
|
||
return format.replace(/YYYY/g, YYYY).replace(/MM/g, MM).replace(/DD/g, DD).replace(/HH/g, HH).replace(/mm/g, mm).replace(/ss/g, ss);
|
||
}
|