Files
estate/src/utils/time.ts

264 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 计算两个日期之间相差的天数
* @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);
}