SOURCE

const WEEKDAY = 7;
const run = Symbol('run');
const init = Symbol('init');
const DATE_TIMESTAMP = {
	year: {
		chs: '年',
		timestamp: 12 * 30 * 24 * 60 * 60 * 1000
	},
	month: {
		chs: '月',
		timestamp: 30 * 24 * 60 * 60 * 1000
	},
	day: {
		chs: '天',
		timestamp: 24 * 60 * 60 * 1000
	},
	hour: {
		chs: '小时',
		timestamp: 60 * 60 * 1000
	},
	minute: {
		chs: '分钟',
		timestamp: 60 * 1000
	},
	second: {
		chs: '秒',
		timestamp: 1000
	}
};
const weekdayEnum = [
	'周日',
	'周一',
	'周二',
	'周三',
	'周四',
	'周五',
	'周六'
];
Object.freeze(DATE_TIMESTAMP);
Object.freeze(weekdayEnum);

/**
 * @description DateTime 时间类,用于处理日常时间格式,以及获取对应时间
 * @class DateTime
 * @constructor
 * @property {String} today 获取当前时间
 * @property {String} toFormat 获取当前设置的格式化后的时间字符串
 * @property {Date} toDate 获取当前设置的时间格式
 * @property setDate 设置时间,实例化后,可根据需求自行设置此时间
 * @method isDate 是否为时间格式,仅验证是否为Date
 * @method convertDate 尝试转换为时间格式
 * @method timeFrom 计算距今的时间差,多久之前
 * @method format 格式化传入的时间
 * @method addSeconds 目标时间添加秒数
 * @method addMinutes 目标时间添加分钟数
 * @method addHours 目标时间添加小时数
 * @method addDays 目标时间添加分天数
 * @method addMonths 目标时间添加月数
 * @method addYears 目标时间添加年数
 * @method getWeekStart 获取目标时间周一
 * @method getWeekEnd 获取目标时间周日
 * @method getFirstDay 获取目标时间第一天,月初
 * @method getLastDay 获取目标时间的最后一天,月末
 */
class DateTime {
	#dateTime;
	constructor(date = new Date, format = 'yyyy-MM-dd') {
		this.dateFormat = format;
		this.#dateTime = DateTime.convertDate(date);
	}

	/**
	 * @description 获取当前时间
	 * @returns {string} 当前时间
	 */
	get today() {
		return DateTime.format(new Date, this.dateFormat);
	}

	/**
	 * @description 获取当前设置的格式化后的时间字符串
	 * @returns {string}
	 */
	get toFormat() {
		return DateTime.format(this.#dateTime, this.dateFormat);
	}

	/**
	 * @description 获取当前设置的时间格式
	 * @returns {*}
	 */
	get toDate() {
		return this.#dateTime;
	}

	/**
	 * @description 设置实例时间
	 * @param date {String|Number|Date}
	 */
	set setDate(date) {
		this.#dateTime = DateTime.convertDate(date);
	}

	/**
	 * @description 是否为时间格式
	 * @method isDate
	 * @static
	 * @param date {String|Number|Date}
	 * @returns {boolean}
	 */
	static isDate(date) {
		return Object.prototype.toString.call(date) === '[object Date]';
	}

	/**
	 * @description 尝试转换为时间格式
	 * @method convertDate
	 * @static
	 * @throws 转换失败抛出异常,Invalid Date--日期格式不正确
	 * @param date {String|Number|Date} 将要转换的值
	 * @returns {Date|*}
	 */
	static convertDate(date) {
		if (!DateTime.isDate(date)) {
			if (typeof date !== "number") {
				date = String(date).replace(/[^\s\d:]/g, '/');
			}
			date = new Date(date);
			if (date.toString() === 'Invalid Date') {
				console.error('Invalid Date -- 日期格式不正确,仅支持时间戳,“年-月-日 时:分:秒等格式”');
				throw new Error('Invalid Date -- 日期格式不正确,仅支持时间戳,“年-月-日 时:分:秒等格式”');
			}
			return date;
		}
		return date;
	}

	/**
	 * @description 计算距今的时间差,多久之前
	 * @method timeFrom
	 * @static
	 * @param lastTime {String|Number|Date}
	 * @returns {string} 返回值为距离当前时间的差值,格式为: n秒前,n分钟前,n小时前...
	 */
	static timeFrom(lastTime, isDiff = false, fromTime = new Date) {
		if (isDiff) {
			let diff = /^\d+$/g.test(lastTime);
			if (!diff) {
				lastTime = Number(lastTime);
				if (Number.isNaN(lastTime)) {
					console.error('时间格式不正确,请输入正确的时间间隔');
					throw new Error('时间格式不正确,请输入正确的时间间隔');
				}
			}
		} else {
			lastTime = DateTime.convertDate(lastTime).getTime()
		}
		let txt = '刚刚';
		let suffix = '前';
		let dateNow = fromTime.getTime();
		let diffTime = isDiff ? lastTime : (dateNow - lastTime);
		for (let key in DATE_TIMESTAMP) {
			let value = DATE_TIMESTAMP[key];
			if (diffTime >= value.timestamp && DATE_TIMESTAMP.hasOwnProperty(key)) {
				txt = Math.floor(diffTime / value.timestamp) + `${value.chs === '月' ? '个月' : value.chs}` + suffix;
				break;
			}
		}
		return txt;
	}

	/**
	 * @description 格式化传入的时间
	 * @method format
	 * @static
	 * @param date {String|Number|Date}
	 * @param format {String} 将要转换的时间格式
	 * @returns {string} 返回格式化后的时间字符串
	 */
	static format(date, format = 'yyyy/MM/dd hh:mm:ss') {
		date = DateTime.convertDate(date);
		if (/(y+)/.test(format)) {
			format = format.replace(RegExp.$1, String(date.getFullYear()).substr(4 - RegExp.$1.length));
		}
		let o = {
			'M+': date.getMonth() + 1,
			'd+': date.getDate(),
			'wk': weekdayEnum[date.getDay()],
			'h+': date.getHours(),
			'm+': date.getMinutes(),
			's+': date.getSeconds()
		};
		for (let k in o) {
			if (new RegExp(`(${k})`).test(format)) {
				let str = o[k] + '';
				format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : (str.padStart(2, '0')));
			}
		}
		return format;
	}
	/**
	 * @description 回到今天
	 * @return {DateTime}
	 */
	backToday() {
		this.#dateTime = new Date;
		return this;
	}
	/**
	 * @description 添加n秒
	 * @method addSeconds
	 * @param secondNum {String|Number}
	 * @returns {DateTime} 返回实例
	 */
	addSeconds(secondNum) {
		let second = Number.parseInt(secondNum);
		this[run]({
			second
		});
		return this;
	}

	/**
	 * @description 添加 n 分钟
	 * @method addMinutes
	 * @param minuteNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addMinutes(minuteNum) {
		let minute = Number.parseInt(minuteNum);
		this[run]({
			minute
		});
		return this;
	}

	/**
	 * @description 添加 n 小时
	 * @method addHours
	 * @param hourNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addHours(hourNum) {
		let hour = Number.parseInt(hourNum);
		this[run]({
			hour
		});
		return this;
	}

	/**
	 * @description 添加 n 天
	 * @method addDays
	 * @param dayNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addDays(dayNum) {
		let day = Number.parseInt(dayNum);
		this[run]({
			day
		});
		return this;
	}

	/**
	 * @description 添加 n 星期
	 * @method addWeeks
	 * @param weekNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addWeeks(weekNum) {
		let week = Number.parseInt(weekNum);
		this[run]({
			week
		});
		return this;
	}

	/**
	 * @description 添加 n 个月
	 * @method addMonths
	 * @param monthNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addMonths(monthNum) {
		let month = Number.parseInt(monthNum);
		this[run]({
			month
		});
		return this;
	}

	/**
	 * @description 添加 n 年
	 * @method addYears
	 * @param yearNum {String|Number}
	 * @returns {DateTime} 实例
	 */
	addYears(yearNum) {
		let year = Number.parseInt(yearNum);
		this[run]({
			year
		});
		return this;
	}

	/**
	 * @description 获取周一
	 * @method getWeekStart
	 * @param isCurWeek 是否获取当前时间的周一,true:当前时间, false:实例时间
	 * @param format 时间格式
	 * @returns {DateTime} 实例
	 */
	getWeekStart(isCurWeek = true, format = this.dateFormat) {
		this[init](isCurWeek, format);
		let wk = this.#dateTime.getDay() || WEEKDAY;
		let toFormat = this[run]({
			day: 1 - wk
		}).toFormat;
		this.backToday();
		return toFormat;
	}

	/**
	 * @description 获取周日
	 * @method getWeekEnd
	 * @param isCurWeek {Boolean|String} 是否获取当前时间的周日,true:当前时间, false:实例时间
	 * @param format {String} 时间格式
	 * @returns {DateTime} 实例
	 */
	getWeekEnd(isCurWeek = true, format = this.dateFormat) {
		this[init](isCurWeek, format);
		let wk = this.#dateTime.getDay() || WEEKDAY;
		let toFormat = this[run]({
			day: WEEKDAY - wk
		}).toFormat;
		this.backToday();
		return toFormat;
	}

	/**
	 * @description 获取月份第一天
	 * @method getFirstDay
	 * @param isCurMonth {Boolean|String} 是否获取当前时间的月初,true:当前时间, false:实例时间
	 * @param format {String} 时间格式
	 * @returns {DateTime} 实例
	 */
	getFirstDay(isCurMonth = true, format = this.dateFormat) {
		this[init](isCurMonth, format);
		let d = this.#dateTime.getDate();
		let toFormat = this[run]({
			day: 1 - d
		}).toFormat;
		this.backToday();
		return toFormat;
	}

	/**
	 * @description 获取月份最后一天
	 * @method getLastDay
	 * @param isCurMonth {Boolean|String} 是否获取当前时间的月末,true:当前时间, false:实例时间
	 * @param format {String} 时间格式
	 * @returns {DateTime} 实例
	 */
	getLastDay(isCurMonth = true, format = this.dateFormat) {
		this[init](isCurMonth, format);
		let d = this.#dateTime.getDate();
		let toFormat = this.addMonths(1).addDays(0 - d).toFormat;
		this.backToday();
		return toFormat;
	}

	[run]({year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, week = 0}) {
		let date = this.#dateTime;
		let y = date.getFullYear() + year;
		let M = date.getMonth() + month;
		let d = date.getDate() + day;
		d += week * WEEKDAY;
		let h = date.getHours() + hour;
		let m = date.getMinutes() + minute;
		let s = date.getSeconds() + second;
		this.#dateTime = new Date(y, M, d, h, m, s);
		return this;
	}

	[init](isCurrent, format) {
		if (isCurrent) {
			this.#dateTime = new Date();
		}
		if (typeof isCurrent === 'string') {
			this.dateFormat = isCurrent;
		} else {
			this.dateFormat = format;
		}
	}
}
let date = new DateTime()
date.addDays(2)

console.log(date.addMonths(2).toFormat)
console 命令行工具 X clear

                    
>
console