@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.
506 lines (439 loc) • 17.3 kB
text/typescript
/**
* 农历日期计算工具
* 基于寿星万年历算法
*/
// 农历数据表
export const LUNAR_INFO = [
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 (2023年闰二月)
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
];
// 天干
const CELESTIAL_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
// 地支
const TERRESTRIAL_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
// 生肖
const ZODIAC = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
// 农历月份
const LUNAR_MONTHS = [
'正月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '冬月', '腊月'
];
// 农历日期
const LUNAR_DAYS = [
'初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'
];
// 农历节日
const LUNAR_FESTIVALS: Record<string, string> = {
'正月初一': '春节',
'正月十五': '元宵节',
'二月初二': '龙抬头',
'五月初五': '端午节',
'七月初七': '七夕',
'七月十五': '中元节',
'八月十五': '中秋节',
'九月初九': '重阳节',
'腊月初八': '腊八节',
'腊月廿三': '小年',
'腊月三十': '除夕'
};
// 公历节日
const SOLAR_FESTIVALS: Record<string, string> = {
'01-01': '元旦',
'02-14': '情人节',
'03-08': '妇女节',
'03-12': '植树节',
'04-01': '愚人节',
'04-05': '清明节',
'05-01': '劳动节',
'05-04': '青年节',
'06-01': '儿童节',
'07-01': '建党节',
'08-01': '建军节',
'09-10': '教师节',
'10-01': '国庆节',
'12-24': '平安夜',
'12-25': '圣诞节'
};
// 节气
const SOLAR_TERMS = [
'小寒', '大寒', '立春', '雨水', '惊蛰', '春分',
'清明', '谷雨', '立夏', '小满', '芒种', '夏至',
'小暑', '大暑', '立秋', '处暑', '白露', '秋分',
'寒露', '霜降', '立冬', '小雪', '大雪', '冬至'
];
// 节气对应的公历日期(从1900年开始,每年的节气日期)
const SOLAR_TERM_INFO = [
'0106', '0120', '0204', '0219', '0306', '0321', '0405', '0420', '0506', '0521', '0606', '0621',
'0707', '0723', '0807', '0823', '0908', '0923', '1008', '1023', '1107', '1122', '1207', '1222'
];
/**
* 计算二十四节气
* @param date 公历日期
* @returns 节气名称,如果不是节气日则返回空字符串
*/
function getSolarTerm(date: Date): string {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
// 简化处理:使用固定的节气日期(实际上节气日期每年略有不同,这里使用近似值)
// 实际应用中可以使用更精确的天文算法或查表法
const monthDay = `${month.toString().padStart(2, '0')}${day.toString().padStart(2, '0')}`;
// 查找当前日期是否为节气
const termIndex = SOLAR_TERM_INFO.findIndex(term => term === monthDay);
if (termIndex !== -1) {
// 计算节气对应的月份(每个月有两个节气)
const expectedMonth = Math.floor(termIndex / 2) + 1;
// 只有当节气对应的月份与当前月份匹配时,才返回节气名称
// 这样可以确保在闰月情况下,节气仍然正确对应到公历月份
if (expectedMonth === month) {
return SOLAR_TERMS[termIndex];
}
}
return '';
}
/**
* 获取农历年的总天数
* @param year 农历年
* @returns 总天数
*/
function getLunarYearDays(year: number): number {
let sum = 348;
for (let i = 0x8000; i > 0x8; i >>= 1) {
sum += (LUNAR_INFO[year - 1900] & i) ? 1 : 0;
}
return sum + getLeapMonthDays(year);
}
/**
* 获取农历年闰月的天数
* @param year 农历年
* @returns 闰月天数(没有闰月返回0)
*/
function getLeapMonthDays(year: number): number {
if (getLeapMonth(year)) {
return (LUNAR_INFO[year - 1900] & 0x10000) ? 30 : 29;
}
return 0;
}
/**
* 获取农历年闰月月份
* @param year 农历年
* @returns 闰月月份(没有闰月返回0)
*/
function getLeapMonth(year: number): number {
return LUNAR_INFO[year - 1900] & 0xf;
}
/**
* 获取农历年某月的天数
* @param year 农历年
* @param month 农历月
* @returns 该月天数
*/
function getLunarMonthDays(year: number, month: number): number {
return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
/**
* 公历日期转农历日期
* @param date 公历日期
* @returns 农历日期信息
*/
export function solarToLunar(date: Date): {
lunarYear: string;
lunarMonth: string;
lunarDay: string;
lunarFestival?: string;
solarFestival?: string;
solarTerm?: string;
} {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
// 参数检查
if (year < 1900 || year > 2100) {
return {
lunarYear: '',
lunarMonth: '',
lunarDay: '',
};
}
// 计算距离1900年1月31日的天数
const baseDate = new Date(1900, 0, 31);
let offset = Math.floor((date.getTime() - baseDate.getTime()) / 86400000);
// 计算农历年
let lunarYear = 1900;
let temp = 0;
for (let i = 1900; i < 2100 && offset > 0; i++) {
temp = getLunarYearDays(i);
offset -= temp;
lunarYear++;
}
if (offset < 0) {
offset += temp;
lunarYear--;
}
// 计算农历月
let isLeap = false;
let lunarMonth = 1;
const leapMonth = getLeapMonth(lunarYear);
// 重新计算月份和日期
// 先计算当年正常月份的天数和闰月天数
const monthDays = [];
let leapDays = 0;
for (let i = 1; i <= 12; i++) {
monthDays.push(getLunarMonthDays(lunarYear, i));
}
if (leapMonth > 0) {
leapDays = getLeapMonthDays(lunarYear);
}
// 计算当前日期是农历哪一天
let days = offset + 1;
let currentMonth = 1;
let isLeapMonth = false;
// 遍历月份,找到对应的农历月和日
for (let i = 1; i <= 12; i++) {
// 处理正常月份
let monthDay = monthDays[i - 1];
if (days <= monthDay) {
currentMonth = i;
break;
}
days -= monthDay;
// 处理闰月
if (i === leapMonth) {
if (days <= leapDays) {
currentMonth = i;
isLeapMonth = true;
break;
}
days -= leapDays;
}
}
// 更新月份和闰月标志
lunarMonth = currentMonth;
isLeap = isLeapMonth;
// 计算农历日
const lunarDay = days;
// 生成农历年份(干支纪年)
const cyclicalYear = (lunarYear - 1900 + 36) % 60;
const cyclicalYearStr = CELESTIAL_STEMS[cyclicalYear % 10] + TERRESTRIAL_BRANCHES[cyclicalYear % 12];
// 生成农历月份
let lunarMonthStr = LUNAR_MONTHS[lunarMonth - 1];
// 如果是闰月,在月份前加上"闰"字
if (isLeap) {
lunarMonthStr = '闰' + LUNAR_MONTHS[lunarMonth - 1];
}
// 生成农历日期
const lunarDayStr = LUNAR_DAYS[lunarDay - 1];
// 获取农历节日
// 对于闰月,需要去掉"闰"字再查找节日
const festivalKey = `${LUNAR_MONTHS[lunarMonth - 1]}${lunarDayStr}`;
const lunarFestival = LUNAR_FESTIVALS[festivalKey];
// 获取公历节日
const monthDayStr = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
const solarFestival = SOLAR_FESTIVALS[monthDayStr];
// 获取节气
const solarTerm = getSolarTerm(date);
return {
lunarYear: `${cyclicalYearStr}年 ${ZODIAC[(lunarYear - 4) % 12]}年`,
lunarMonth: lunarMonthStr,
lunarDay: lunarDayStr,
lunarFestival,
solarFestival,
solarTerm,
};
}
/**
* 获取节假日信息
* @param date 公历日期
* @returns 节假日信息
*/
// 节假日数据接口(模拟数据)
export interface HolidayPeriod {
name: string;
startDate: string; // YYYY-MM-DD
endDate: string; // YYYY-MM-DD
isRestDay: boolean;
}
// 模拟节假日数据
const HOLIDAY_PERIODS: HolidayPeriod[] = [
// 2024年节假日
{ name: '春节', startDate: '2024-02-10', endDate: '2024-02-17', isRestDay: true },
{ name: '清明节', startDate: '2024-04-04', endDate: '2024-04-06', isRestDay: true },
{ name: '劳动节', startDate: '2024-05-01', endDate: '2024-05-05', isRestDay: true },
{ name: '端午节', startDate: '2024-06-08', endDate: '2024-06-10', isRestDay: true },
{ name: '中秋节', startDate: '2024-09-15', endDate: '2024-09-17', isRestDay: true },
{ name: '国庆节', startDate: '2024-10-01', endDate: '2024-10-07', isRestDay: true },
// 2024年调班工作日
{ name: '春节调班', startDate: '2024-02-04', endDate: '2024-02-04', isRestDay: false },
{ name: '春节调班', startDate: '2024-02-18', endDate: '2024-02-18', isRestDay: false },
{ name: '清明调班', startDate: '2024-04-07', endDate: '2024-04-07', isRestDay: false },
{ name: '劳动节调班', startDate: '2024-04-28', endDate: '2024-04-28', isRestDay: false },
{ name: '劳动节调班', startDate: '2024-05-11', endDate: '2024-05-11', isRestDay: false },
{ name: '中秋调班', startDate: '2024-09-14', endDate: '2024-09-14', isRestDay: false },
{ name: '国庆调班', startDate: '2024-09-29', endDate: '2024-09-29', isRestDay: false },
{ name: '国庆调班', startDate: '2024-10-12', endDate: '2024-10-12', isRestDay: false },
// 2025年节假日
{ name: '元旦', startDate: '2025-01-01', endDate: '2025-01-01', isRestDay: true },
{ name: '春节', startDate: '2025-01-28', endDate: '2025-02-04', isRestDay: true },
{ name: '清明节', startDate: '2025-04-04', endDate: '2025-04-06', isRestDay: true },
{ name: '劳动节', startDate: '2025-05-01', endDate: '2025-05-05', isRestDay: true },
{ name: '端午节', startDate: '2025-05-31', endDate: '2025-06-02', isRestDay: true },
{ name: '中秋节、国庆节', startDate: '2025-10-01', endDate: '2025-10-08', isRestDay: true },
// 2025年调班工作日
{ name: '春节调班', startDate: '2025-01-26', endDate: '2025-01-26', isRestDay: false },
{ name: '春节调班', startDate: '2025-02-08', endDate: '2025-02-08', isRestDay: false },
{ name: '劳动节调班', startDate: '2025-04-27', endDate: '2025-04-27', isRestDay: false },
{ name: '国庆、中秋调班', startDate: '2025-09-28', endDate: '2025-09-28', isRestDay: false },
{ name: '国庆、中秋调班', startDate: '2025-10-11', endDate: '2025-10-11', isRestDay: false },
];
/**
* 判断日期是否在指定范围内
* @param date 日期
* @param startDate 开始日期
* @param endDate 结束日期
* @returns 是否在范围内
*/
function isDateInRange(date: Date, startDate: string, endDate: string): boolean {
const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
return dateStr >= startDate && dateStr <= endDate;
}
/**
* 从本地缓存获取节假日数据
* @returns 节假日数据数组
*/
export function getHolidayPeriodsFromCache(): HolidayPeriod[] {
try {
const cachedData = localStorage.getItem('holidayPeriods');
if (cachedData) {
return JSON.parse(cachedData);
}
} catch (error) {
console.error('Failed to get holiday periods from cache:', error);
}
return HOLIDAY_PERIODS; // 如果没有缓存或解析失败,返回默认数据
}
/**
* 保存节假日数据到本地缓存
* @param periods 节假日数据数组
*/
export function saveHolidayPeriodsToCache(periods: HolidayPeriod[]): void {
try {
localStorage.setItem('holidayPeriods', JSON.stringify(periods));
} catch (error) {
console.error('Failed to save holiday periods to cache:', error);
}
}
/**
* 从外部API更新节假日数据
* @param apiUrl API地址
* @returns 是否更新成功的Promise
*/
export async function updateHolidayPeriodsFromApi(apiUrl: string): Promise<boolean> {
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// 验证数据格式
if (!Array.isArray(data)) {
throw new Error('Invalid data format: expected an array');
}
// 验证每个节假日对象的格式
const isValidHolidayPeriod = (item: any): item is HolidayPeriod => {
return typeof item.name === 'string' &&
typeof item.startDate === 'string' &&
typeof item.endDate === 'string' &&
typeof item.isRestDay === 'boolean';
};
if (!data.every(isValidHolidayPeriod)) {
throw new Error('Invalid data format: some items do not match the HolidayPeriod interface');
}
// 保存到缓存
saveHolidayPeriodsToCache(data);
return true;
} catch (error) {
console.error('Failed to update holiday periods from API:', error);
return false;
}
}
/**
* 获取节假日信息
* @param date 日期
* @returns 节假日信息
*/
export function getHolidayInfo(date: Date): { name?: string; isHoliday?: boolean; isRestDay: boolean; isHolidayPeriod?: boolean } {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const weekday = date.getDay();
// 周末判断
const isWeekend = weekday === 0 || weekday === 6;
// 格式化日期字符串
const monthDayStr = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
const fullDateStr = `${year}-${monthDayStr}`;
// 获取公历节日
const solarFestival = SOLAR_FESTIVALS[monthDayStr];
// 获取农历信息和节日
const lunarInfo = solarToLunar(date);
// 检查是否有农历节日、公历节日或节气
const hasFestival = !!(lunarInfo.lunarFestival || lunarInfo.solarFestival || lunarInfo.solarTerm);
// 从缓存获取节假日数据
const holidayPeriods = getHolidayPeriodsFromCache();
// 查找当前日期是否在节假日期间
const holidayPeriod = holidayPeriods.find(period => isDateInRange(date, period.startDate, period.endDate));
// 如果在节假日期间
if (holidayPeriod) {
// 只显示调班信息,不显示普通节日的名称
if (holidayPeriod.name.includes('调班')) {
return {
name: holidayPeriod.name,
isHoliday: true,
isRestDay: holidayPeriod.isRestDay,
isHolidayPeriod:true,
};
} else {
// 对于普通节假日,返回空名称但保留isHoliday标记
return {
name: '', // 返回空字符串
isHoliday: true, // 显式设置isHoliday为true
isRestDay: holidayPeriod.isRestDay,
isHolidayPeriod:true,
};
}
}
// 如果不在节假日期间,检查是否有农历节日或公历节日
if (hasFestival) {
return {
name: lunarInfo.lunarFestival || lunarInfo.solarFestival || lunarInfo.solarTerm,
isHoliday: true,
isRestDay: isWeekend,
isHolidayPeriod:false,
};
}
// 如果既不是节假日也没有节日,按照周末判断
return {
isHoliday: false,
isRestDay: isWeekend,
isHolidayPeriod: false
};
}