@tongziyang/uni-calendar-plugin
Version:
A comprehensive calendar plugin for uniapp with support for Gregorian and Lunar calendars, date selection, hotel booking, check-in functionality, and more.
597 lines (532 loc) • 19 kB
text/typescript
import { DateInfo } from '../types';
import { solarToLunar, getHolidayInfo } from './lunar';
// 日期选项接口
export interface DateOptions {
firstDayOfWeek?: number;
selectedDate?: Date | null;
rangeStart?: Date | null;
rangeEnd?: Date | null;
priceData?: Record<string, number>;
checkInData?: Record<string, boolean>;
disabledDate?: ((date: Date) => boolean) | null;
}
/**
* 格式化日期为字符串
* @param date 日期对象
* @param format 格式字符串
* @returns 格式化后的日期字符串
*/
export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to formatDate');
return '';
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
return format
.replace(/YYYY/g, year.toString())
.replace(/YY/g, (year % 100).toString().padStart(2, '0'))
.replace(/MM/g, month.toString().padStart(2, '0'))
.replace(/M/g, month.toString())
.replace(/DD/g, day.toString().padStart(2, '0'))
.replace(/D/g, day.toString())
.replace(/HH/g, hours.toString().padStart(2, '0'))
.replace(/H/g, hours.toString())
.replace(/mm/g, minutes.toString().padStart(2, '0'))
.replace(/m/g, minutes.toString())
.replace(/ss/g, seconds.toString().padStart(2, '0'))
.replace(/s/g, seconds.toString());
}
/**
* 解析日期字符串为Date对象
* @param dateStr 日期字符串
* @returns Date对象
*/
export function parseDate(dateStr: string): Date {
if (!dateStr) {
console.warn('Empty date string provided to parseDate');
return new Date();
}
// 尝试解析多种格式的日期字符串
const date = new Date(dateStr);
if (!isNaN(date.getTime())) {
return date;
}
// 尝试解析 YYYY-MM-DD 格式
const parts = dateStr.split(/[-\/]/);
if (parts.length === 3) {
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1;
const day = parseInt(parts[2], 10);
const newDate = new Date(year, month, day);
if (!isNaN(newDate.getTime())) {
return newDate;
}
}
// 无法解析,返回当前日期
console.warn(`Unable to parse date string: ${dateStr}, returning current date`);
return new Date();
}
/**
* 获取两个日期之间的天数
* @param start 开始日期
* @param end 结束日期
* @returns 天数
*/
export function getDaysBetween(start: Date, end: Date): number {
if (!start || !end || !(start instanceof Date) || !(end instanceof Date) ||
isNaN(start.getTime()) || isNaN(end.getTime())) {
console.warn('Invalid date(s) provided to getDaysBetween');
return 0;
}
const startDate = new Date(start.getFullYear(), start.getMonth(), start.getDate());
const endDate = new Date(end.getFullYear(), end.getMonth(), end.getDate());
const diff = endDate.getTime() - startDate.getTime();
return Math.round(diff / (1000 * 60 * 60 * 24));
}
/**
* 判断两个日期是否是同一天
* @param date1 日期1
* @param date2 日期2
* @returns 是否同一天
*/
export function isSameDay(date1: Date | null, date2: Date | null): boolean {
if (!date1 || !date2 || !(date1 instanceof Date) || !(date2 instanceof Date) ||
isNaN(date1.getTime()) || isNaN(date2.getTime())) {
return false;
}
return (
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
);
}
/**
* 判断两个日期是否是同一个月
* @param date1 日期1
* @param date2 日期2
* @returns 是否同一个月
*/
export function isSameMonth(date1: Date | null, date2: Date | null): boolean {
if (!date1 || !date2 || !(date1 instanceof Date) || !(date2 instanceof Date) ||
isNaN(date1.getTime()) || isNaN(date2.getTime())) {
return false;
}
return (
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth()
);
}
/**
* 判断日期是否在日期范围内
* @param date 日期
* @param start 开始日期
* @param end 结束日期
* @returns 是否在范围内
*/
export function isDateInRange(date: Date, start: Date, end: Date): boolean {
if (!date || !start || !end ||
!(date instanceof Date) || !(start instanceof Date) || !(end instanceof Date) ||
isNaN(date.getTime()) || isNaN(start.getTime()) || isNaN(end.getTime())) {
return false;
}
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
const startDate = new Date(start.getFullYear(), start.getMonth(), start.getDate()).getTime();
const endDate = new Date(end.getFullYear(), end.getMonth(), end.getDate()).getTime();
return targetDate >= startDate && targetDate <= endDate;
}
/**
* 获取某月的第一天
* @param year 年
* @param month 月(0-11)
* @returns 第一天的Date对象
*/
export function getFirstDayOfMonth(year: number, month: number): Date {
return new Date(year, month, 1);
}
/**
* 获取某月的最后一天
* @param year 年
* @param month 月(0-11)
* @returns 最后一天的Date对象
*/
export function getLastDayOfMonth(year: number, month: number): Date {
return new Date(year, month + 1, 0);
}
/**
* 获取某月的天数
* @param year 年
* @param month 月(0-11)
* @returns 天数
*/
export function getDaysInMonth(year: number, month: number): number {
return new Date(year, month + 1, 0).getDate();
}
/**
* 获取星期几的名称数组
* @param firstDayOfWeek 一周的第一天(0表示周日,1表示周一)
* @param abbreviated 是否使用缩写
* @returns 星期几的名称数组
*/
export function getWeekDayNames(firstDayOfWeek: number = 0, abbreviated: boolean = true): string[] {
const weekDays = abbreviated
? ['日', '一', '二', '三', '四', '五', '六']
: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const result = [...weekDays];
for (let i = 0; i < firstDayOfWeek; i++) {
result.push(result.shift()!);
}
return result;
}
/**
* 获取某个日期的详细信息
* @param date 日期
* @param options 选项
* @returns 日期信息
*/
export function getDayInfo(date: Date, options: DateOptions = {}): DateInfo {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to getDayInfo');
return {} as DateInfo;
}
const {
selectedDate = null,
priceData = {},
checkInData = {},
disabledDate = null
} = options;
const today = new Date();
const dateStr = formatDate(date, 'YYYY-MM-DD');
const lunarInfo = solarToLunar(date);
const holidayInfo = getHolidayInfo(date);
return {
date,
day: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
isCurrentMonth: true, // 默认为当前月
isToday: isSameDay(date, today),
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
isInRange: false, // 默认不在范围内
isRangeStart: false,
isRangeEnd: false,
isWeekend: date.getDay() === 0 || date.getDay() === 6,
isHoliday: holidayInfo.isHoliday !== undefined ? holidayInfo.isHoliday : !!holidayInfo.name,
isRestDay: !!holidayInfo.isRestDay,
isHolidayPeriod: !!holidayInfo.isHolidayPeriod,
isCheckedIn: !!checkInData[dateStr],
isDisabled: disabledDate ? disabledDate(date) : false,
price: priceData[dateStr],
lunarDay: lunarInfo?.lunarDay,
lunarMonth: lunarInfo?.lunarMonth,
lunarYear: lunarInfo?.lunarYear,
lunarFestival: lunarInfo?.lunarFestival,
solarFestival: lunarInfo?.solarFestival,
solarTerm: lunarInfo?.solarTerm,
};
}
/**
* 获取某月的所有日期信息
* @param year 年
* @param month 月(0-11)
* @param options 选项
* @returns 日期信息数组
*/
export function getMonthDays(year: number, month: number, options: DateOptions = {}): DateInfo[] {
const {
firstDayOfWeek = 0,
selectedDate = null,
rangeStart = null,
rangeEnd = null,
priceData = {},
checkInData = {},
disabledDate = null
} = options;
const today = new Date();
const firstDay = getFirstDayOfMonth(year, month);
const lastDay = getLastDayOfMonth(year, month);
const daysInMonth = getDaysInMonth(year, month);
// 计算上个月需要显示的天数
let prevMonthDays = firstDay.getDay() - firstDayOfWeek;
if (prevMonthDays < 0) prevMonthDays += 7;
// 计算下个月需要显示的天数
const totalDaysToShow = Math.ceil((daysInMonth + prevMonthDays) / 7) * 7;
const nextMonthDays = totalDaysToShow - daysInMonth - prevMonthDays;
// 获取上个月的最后几天
const prevMonth = month === 0 ? 11 : month - 1;
const prevMonthYear = month === 0 ? year - 1 : year;
const daysInPrevMonth = getDaysInMonth(prevMonthYear, prevMonth);
// 获取下个月的前几天
const nextMonth = month === 11 ? 0 : month + 1;
const nextMonthYear = month === 11 ? year + 1 : year;
const days: DateInfo[] = [];
// 添加上个月的日期
for (let i = 0; i < prevMonthDays; i++) {
const day = daysInPrevMonth - prevMonthDays + i + 1;
const date = new Date(prevMonthYear, prevMonth, day);
const dateStr = formatDate(date, 'YYYY-MM-DD');
const lunarInfo = solarToLunar(date);
const holidayInfo = getHolidayInfo(date);
days.push({
date,
day,
month: prevMonth,
year: prevMonthYear,
isCurrentMonth: false,
isToday: isSameDay(date, today),
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
isInRange: rangeStart && rangeEnd ? isDateInRange(date, rangeStart, rangeEnd) : false,
isRangeStart: rangeStart ? isSameDay(date, rangeStart) : false,
isRangeEnd: rangeEnd ? isSameDay(date, rangeEnd) : false,
isWeekend: date.getDay() === 0 || date.getDay() === 6,
isHoliday: holidayInfo.isHoliday !== undefined ? holidayInfo.isHoliday : !!holidayInfo.name,
isRestDay: !!holidayInfo.isRestDay,
isHolidayPeriod: !!holidayInfo.isHolidayPeriod,
isCheckedIn: !!checkInData[dateStr],
isDisabled: disabledDate ? disabledDate(date) : false,
price: priceData[dateStr],
lunarDay: lunarInfo?.lunarDay,
lunarMonth: lunarInfo?.lunarMonth,
lunarYear: lunarInfo?.lunarYear,
lunarFestival: lunarInfo?.lunarFestival,
solarFestival: lunarInfo?.solarFestival,
solarTerm: lunarInfo?.solarTerm,
});
}
// 添加当前月的日期
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dateStr = formatDate(date, 'YYYY-MM-DD');
const lunarInfo = solarToLunar(date);
const holidayInfo = getHolidayInfo(date);
days.push({
date,
day: i,
month,
year,
isCurrentMonth: true,
isToday: isSameDay(date, today),
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
isInRange: rangeStart && rangeEnd ? isDateInRange(date, rangeStart, rangeEnd) : false,
isRangeStart: rangeStart ? isSameDay(date, rangeStart) : false,
isRangeEnd: rangeEnd ? isSameDay(date, rangeEnd) : false,
isWeekend: date.getDay() === 0 || date.getDay() === 6,
isHoliday: holidayInfo.isHoliday !== undefined ? holidayInfo.isHoliday : !!holidayInfo.name,
isRestDay: !!holidayInfo.isRestDay,
isHolidayPeriod: !!holidayInfo.isHolidayPeriod,
isCheckedIn: !!checkInData[dateStr],
isDisabled: disabledDate ? disabledDate(date) : false,
price: priceData[dateStr],
lunarDay: lunarInfo?.lunarDay,
lunarMonth: lunarInfo?.lunarMonth,
lunarYear: lunarInfo?.lunarYear,
lunarFestival: lunarInfo?.lunarFestival,
solarFestival: lunarInfo?.solarFestival,
solarTerm: lunarInfo?.solarTerm,
});
}
// 添加下个月的日期
for (let i = 1; i <= nextMonthDays; i++) {
const date = new Date(nextMonthYear, nextMonth, i);
const dateStr = formatDate(date, 'YYYY-MM-DD');
const lunarInfo = solarToLunar(date);
const holidayInfo = getHolidayInfo(date);
days.push({
date,
day: i,
month: nextMonth,
year: nextMonthYear,
isCurrentMonth: false,
isToday: isSameDay(date, today),
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
isInRange: rangeStart && rangeEnd ? isDateInRange(date, rangeStart, rangeEnd) : false,
isRangeStart: rangeStart ? isSameDay(date, rangeStart) : false,
isRangeEnd: rangeEnd ? isSameDay(date, rangeEnd) : false,
isWeekend: date.getDay() === 0 || date.getDay() === 6,
isHoliday: holidayInfo.isHoliday !== undefined ? holidayInfo.isHoliday : !!holidayInfo.name,
isRestDay: !!holidayInfo.isRestDay,
isHolidayPeriod: !!holidayInfo.isHolidayPeriod,
isCheckedIn: !!checkInData[dateStr],
isDisabled: disabledDate ? disabledDate(date) : false,
price: priceData[dateStr],
lunarDay: lunarInfo?.lunarDay,
lunarMonth: lunarInfo?.lunarMonth,
lunarYear: lunarInfo?.lunarYear,
lunarFestival: lunarInfo?.lunarFestival,
solarFestival: lunarInfo?.solarFestival,
solarTerm: lunarInfo?.solarTerm,
});
}
return days;
}
/**
* 获取某周的所有日期信息
* @param date 周内的某一天
* @param options 选项
* @returns 日期信息数组
*/
export function getWeekDays(date: Date, options: DateOptions = {}): DateInfo[] {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to getWeekDays');
return [];
}
const {
firstDayOfWeek = 0,
selectedDate = null,
rangeStart = null,
rangeEnd = null,
priceData = {},
checkInData = {},
disabledDate = null
} = options;
const today = new Date();
const currentDay = date.getDay();
const diff = currentDay - firstDayOfWeek;
const adjustedDiff = diff < 0 ? diff + 7 : diff;
const firstDayOfWeek_ = new Date(date);
firstDayOfWeek_.setDate(date.getDate() - adjustedDiff);
const days: DateInfo[] = [];
for (let i = 0; i < 7; i++) {
const currentDate = new Date(firstDayOfWeek_);
currentDate.setDate(firstDayOfWeek_.getDate() + i);
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
const day = currentDate.getDate();
const dateStr = formatDate(currentDate, 'YYYY-MM-DD');
const lunarInfo = solarToLunar(currentDate);
const holidayInfo = getHolidayInfo(currentDate);
days.push({
date: currentDate,
day,
month,
year,
isCurrentMonth: currentDate.getMonth() === date.getMonth(),
isToday: isSameDay(currentDate, today),
isSelected: selectedDate ? isSameDay(currentDate, selectedDate) : false,
isInRange: rangeStart && rangeEnd ? isDateInRange(currentDate, rangeStart, rangeEnd) : false,
isRangeStart: rangeStart ? isSameDay(currentDate, rangeStart) : false,
isRangeEnd: rangeEnd ? isSameDay(currentDate, rangeEnd) : false,
isWeekend: currentDate.getDay() === 0 || currentDate.getDay() === 6,
isHoliday: holidayInfo.isHoliday !== undefined ? holidayInfo.isHoliday : !!holidayInfo.name,
isRestDay: !!holidayInfo.isRestDay,
isHolidayPeriod: !!holidayInfo.isHolidayPeriod,
isCheckedIn: !!checkInData[dateStr],
isDisabled: disabledDate ? disabledDate(currentDate) : false,
price: priceData[dateStr],
lunarDay: lunarInfo?.lunarDay,
lunarMonth: lunarInfo?.lunarMonth,
lunarYear: lunarInfo?.lunarYear,
lunarFestival: lunarInfo?.lunarFestival,
solarFestival: lunarInfo?.solarFestival,
solarTerm: lunarInfo?.solarTerm,
});
}
return days;
}
/**
* 获取某年的所有月份信息
* @param year 年份
* @returns 月份信息数组
*/
export function getYearMonths(year: number): { year: number; month: number; name: string }[] {
const months = [];
for (let i = 0; i < 12; i++) {
months.push({
year,
month: i,
name: `${i + 1}月`
});
}
return months;
}
/**
* 获取日期范围内的所有日期
* @param start 开始日期
* @param end 结束日期
* @returns 日期数组
*/
export function getDateRange(start: Date, end: Date): Date[] {
if (!start || !end || !(start instanceof Date) || !(end instanceof Date) ||
isNaN(start.getTime()) || isNaN(end.getTime())) {
console.warn('Invalid date(s) provided to getDateRange');
return [];
}
const dates: Date[] = [];
const current = new Date(start);
while (current <= end) {
dates.push(new Date(current));
current.setDate(current.getDate() + 1);
}
return dates;
}
/**
* 添加天数到日期
* @param date 日期
* @param days 天数
* @returns 新日期
*/
export function addDays(date: Date, days: number): Date {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to addDays');
return new Date();
}
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
/**
* 添加月数到日期
* @param date 日期
* @param months 月数
* @returns 新日期
*/
export function addMonths(date: Date, months: number): Date {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to addMonths');
return new Date();
}
const result = new Date(date);
result.setMonth(result.getMonth() + months);
return result;
}
/**
* 添加年数到日期
* @param date 日期
* @param years 年数
* @returns 新日期
*/
export function addYears(date: Date, years: number): Date {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to addYears');
return new Date();
}
const result = new Date(date);
result.setFullYear(result.getFullYear() + years);
return result;
}
/**
* 获取日期是一年中的第几周
* @param date 日期
* @param firstDayOfWeek 一周的第一天(0表示周日,1表示周一)
* @returns 周数
*/
export function getWeekNumber(date: Date, firstDayOfWeek: number = 0): number {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
console.warn('Invalid date provided to getWeekNumber');
return 0;
}
// 复制日期,避免修改原始日期
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 7 - firstDayOfWeek) % 7;
// 设置为一周的第一天
target.setDate(target.getDate() - dayNr);
// 获取一月四日所在的周,这是ISO 8601标准定义的第一周
const jan4 = new Date(target.getFullYear(), 0, 4);
const dayDiff = (target.getTime() - jan4.getTime()) / 86400000;
// 计算周数
return Math.ceil((dayDiff + jan4.getDay() + 1) / 7);
}