chinese-days
Version:
中国节假日、调休日、工作日、24节气查询,农历阳历互转,支持 TS、CommonJS、UMD 模块化使用,提供 ics 日历格式,可供 Google Calendar、Apple Calendar、Microsoft Outlook 等客户端订阅。
269 lines (229 loc) • 7.65 kB
text/typescript
import dayjs, { type ConfigType } from "../utils/dayjs";
import { type LunarDateDetail, LUNAR_INFO, CHINESE_NUMBER, NUMBER_MONTH, NUMBER_1, NUMBER_2, ZODIACS } from './constants'
/**
* 获取指定农历年的天数
* @param y 年份
* @returns 农历年天数
*/
const lunarYearDays = (y: number): number => {
let sum = 348;
for (let i = 0x8000; i > 0x8; i >>= 1) {
sum += (LUNAR_INFO[y - 1900] & i) !== 0 ? 1 : 0;
}
return sum + yearLeapDays(y);
}
/**
* 获取指定年份的闰月月份
* @param y 年份
* @returns 闰月月份,非闰年返回0
*/
const yearLeapMonth = (y: number): number => LUNAR_INFO[y - 1900] & 0xf;
/**
* 获取指定年份的闰月天数
* @param y 年份
* @returns 闰月天数,非闰年返回0
*/
const yearLeapDays = (y: number): number => yearLeapMonth(y) ? ((LUNAR_INFO[y - 1900] & 0x10000) !== 0 ? 30 : 29) : 0;
/**
* 获取天干地支表示的月份或日期
* @param num 月份或日期的数值
* @returns 天干地支表示
*/
const cyclicalm = (num: number): string => NUMBER_1[num % 10] + NUMBER_2[num % 12];
/**
* 获取指定年月的阴历天数
* @param y 农历年份
* @param m 农历月份
* @returns 月份天数
*/
const monthDays = (y: number, m: number): number => (LUNAR_INFO[y - 1900] & (0x10000 >> m)) === 0 ? 29 : 30;
/**
* 获取指定年份的生肖
* @param y 农历年份
* @returns 生肖
*/
const getYearZodiac = (y: number): string => ZODIACS[(y - 4) % 12];
/**
* 获取指定日期的农历表示
* @param day 日期
* @returns 农历表示
*/
const getDateCN = (day: number): string => {
const prefixes = ["初", "十", "廿", "三十"];
if (day === 10) return "初十";
if (day === 20) return "二十";
if (day === 30) return "三十";
const tensPlace = Math.floor(day / 10);
const unitsPlace = day % 10;
return prefixes[tensPlace] + (unitsPlace ? CHINESE_NUMBER[unitsPlace] : "");
}
/**
* 获取指定农历年份的天干地支表示
* @param lunarYear 农历年份
* @returns 天干地支表示
*/
const getLunarYearText = (lunarYear: number): string => {
return `${NUMBER_1[(lunarYear - 4) % 10]}${NUMBER_2[(lunarYear - 4) % 12]}年`;
}
/**
* 获取指定范围内的所有农历年份 信息
* @param startYear 起始农历年份
* @param endYear 结束农历年份
* @returns 农历年份列表
*/
const getLunarYears = (startYear: number, endYear: number) => {
const years = [];
for (let i = startYear; i <= endYear; i++) {
years.push({
year: i,
lunarYear: getLunarYearText(i),
lunarYearCN: i.toString().split('').map(i => CHINESE_NUMBER[Number(i)]).join('')
});
}
return years;
}
/**
* 获取指定阳历年份的闰月
* @param year 年份
* @returns 农历闰月月份
*/
const getYearLeapMonth = (year: number) => {
const leap = yearLeapMonth(year)
return {
year,
leapMonth: leap || undefined,
leapMonthCN: leap ? `闰${NUMBER_MONTH[leap - 1]}月` : undefined,
days: leap ? (LUNAR_INFO[year - 1900] & 0x10000) !== 0 ? 30 : 29 : 0
};
}
/**
* 计算指定日期的农历元素
* @param date 指定日期
* @returns 农历信息
*/
export const getLunarDate = (date: ConfigType): LunarDateDetail => {
const lunarDate: number[] = new Array(7).fill(0);
let temp = 0;
let leap = 0;
const baseDate = dayjs(new Date(1900, 0, 31));
const objDate = dayjs(date);
let offset = objDate.diff(baseDate, "day");
lunarDate[5] = offset + 40; // 日柱,从1900-01-31开始的天数计算
lunarDate[4] = 14; // 月柱,从1900-01-31开始的月数计算
let i = 1900;
for (; i < 2100 && offset > 0; i++) {
temp = lunarYearDays(i);
offset -= temp;
lunarDate[4] += 12; // 每经过一年增加12个月柱
}
if (offset < 0) {
offset += temp;
i--;
lunarDate[4] -= 12;
}
lunarDate[0] = i; // 农历年份
lunarDate[3] = i - 1864; // 年柱,甲子从1864年开始
leap = yearLeapMonth(i); // 闰哪个月
lunarDate[6] = 0; // 闰月标记,初始为0
for (let j = 1; j < 13 && offset >= 0; j++) {
if (leap > 0 && j === (leap + 1) && lunarDate[6] === 0) {
--j;
lunarDate[6] = 1;
temp = yearLeapDays(i);
} else {
temp = monthDays(i, j);
}
if (lunarDate[6] === 1 && j === (leap + 1)) {
lunarDate[6] = 0;
}
offset -= temp;
if (lunarDate[6] === 0) {
lunarDate[4]++;
}
lunarDate[1] = j; // 农历月份
}
if (offset === 0 && leap > 0 && lunarDate[6] === 1) {
lunarDate[6] = 0;
} else if (offset < 0) {
offset += temp;
lunarDate[1]--;
lunarDate[4]--;
}
lunarDate[2] = offset + 1; // 农历日期
return {
date: objDate.format('YYYY-MM-DD'), // 公历日期
lunarYear: lunarDate[0], // 农历年份
lunarMon: lunarDate[1] + 1, // 农历月份
lunarDay: lunarDate[2], // 农历日期
isLeap: Boolean(lunarDate[6]), // 是否闰月
zodiac: getYearZodiac(lunarDate[0]), // 生肖
yearCyl: cyclicalm(lunarDate[3]), // 年柱
monCyl: cyclicalm(lunarDate[4]), // 月柱
dayCyl: cyclicalm(lunarDate[5]), // 日柱
lunarYearCN: `${lunarDate[0].toString().split('').map(i => CHINESE_NUMBER[Number(i)]).join('')}`, // 农历年份中文表示
lunarMonCN: `${NUMBER_MONTH[lunarDate[1]]}月`, // 农历月份中文表示
lunarDayCN: getDateCN(lunarDate[2]) // 农历日期中文表示
};
}
/**
* 获取范围内所有日期的农历信息
* @param startDate 开始日期
* @param endDate 结束日期
* @returns 范围内所有日期的农历信息
*/
export const getLunarDatesInRange = (startDate: ConfigType, endDate: ConfigType): LunarDateDetail[] => {
const start = dayjs(startDate);
const end = dayjs(endDate);
const lunarDates: LunarDateDetail[] = [];
for (let date = start; date.isBefore(end) || date.isSame(end, 'day'); date = date.add(1, 'day')) {
lunarDates.push(getLunarDate(date));
}
return lunarDates;
}
/**
* 根据阴历日期查询阳历日期
* @param lunarDate 农历日期
* @param isLeapMonth 是否闰月
* @returns 阳历日期
*/
export const getSolarDateFromLunar = (lunarDate: ConfigType): {
date: string;
leapMonthDate?: string;
} => {
const date = dayjs(lunarDate);
const lunarYear = date.year();
const lunarMonth = date.month() + 1;
const lunarDay = date.date();
// 计算从农历年开始到指定农历日期的总天数
let offset = 0;
for (let i = 1900; i < lunarYear; i++) {
offset += lunarYearDays(i);
}
let leapMonth = yearLeapMonth(lunarYear);
for (let i = 1; i < lunarMonth; i++) {
offset += monthDays(lunarYear, i);
if (i === leapMonth) {
offset += yearLeapDays(lunarYear);
}
}
offset += lunarDay - 1;
// 通过在基准日期上添加偏移量来获取阳历日期
const baseDate = dayjs(new Date(1900, 0, 31));
const solarDate = baseDate.add(offset, 'day').format('YYYY-MM-DD');
/* 闰月日期 */
let leapMonthDateOffset = offset;
let solarLeapMonthDate: string | undefined;
if (leapMonth === lunarMonth) {
leapMonthDateOffset += monthDays(lunarYear, lunarMonth);
solarLeapMonthDate = baseDate.add(leapMonthDateOffset, 'day').format('YYYY-MM-DD');
}
return {
date: solarDate,
leapMonthDate: solarLeapMonthDate,
};
}
export default {
getLunarDate,
getLunarDatesInRange,
getSolarDateFromLunar,
}