uniapp 自定义日历

1,175 阅读38分钟

场景

实现日历功能我们一般是直接使用UI库的日历组件就能满足需求,但是如果项目对UI有特殊要求:除了展示基本的日期天数,还需要在日历上展示其他东西,这种情况如果改UI库的日历组件就比较麻烦了,如下案例

image.png

在这个项目中,不仅需要展示日历基本的日期时间,还需要展示农历信息和易经八卦(有可能不对,不太懂这个)的信息,常规的UI组件库的日期组件肯定无法满足需求了,自定义改起来也麻烦。

实现效果

单选模式:

image.png

多选模式:

image.png

选择范围模式:

image.png

组件参数

参数名描述示例
date初始化选中的日期
selectType日历选择模式字符串:single 单选 multiple 多选 range 范围
isLunar是否显示农历信息Boolean
range限制日期选择范围Array:['2025/03/24','2025/10/01']

参数介绍

date:

  • selectType为single时传入:字符串:'2025/01/01'、'156416156156'(毫秒时间戳)
  • selectType为multiple时传入:数组:Array:['2025/03/24','2025/10/01','2025/11/08'] (选择的所有日期)
  • selectType为range时传入:数组:Array:['2025/03/24','2025/10/01'] (只传入开始日期和结束日期即可)

封装功能

注意点:

  1. 使用了dayjs日期时间处理库(懒得自己封装处理日期的函数了🤷‍♀️)

目前实现的效果就是案例的效果

  1. 主要功能就是实现日期数据的获取,日期数据是常规的6*7的格式
  2. 切换月份时有左右位移的动画效果
  3. 支持手指左右滑动日历来切换月份
  4. 支持单选日期、多选日期、选择范围日期

日期数据获取正确后,只需要关心样式调整即可

封装代码

<template>
	<div class="main-box">
		<div class="operation-box">
			<div class="operation-box-btn" @click="changeMonth(-1)" style="margin-right: 20rpx"> 《 </div>
			<div
				class="operation-box-time"
				style="margin-right: 20rpx"
				@click="openDateTimePicker('year')"
				>{{ year }}年</div
			>
			<div class="operation-box-time" @click="openDateTimePicker('month')">{{ month }}月</div>
			<div class="operation-box-btn" @click="changeMonth(1)" style="margin-left: 20rpx"> 》 </div>
			<div class="operation-box-today" @click="goToday" v-if="selectType == 'single'">今日</div>
		</div>
		<div class="week-box">
			<div v-for="(item, index) in weekList" :key="index" class="week-item">{{ item }}</div>
		</div>
		<div
			class="day-box"
			:class="slideDirection"
			@touchstart="handleTouchStart"
			@touchend="handleTouchEnd"
		>
			<div
				:class="[
					'day-item',
					{
						'current-month': day.isCurrentMonth == 'current',
						'current-day':
							props.selectType === 'single'
								? currentDay == day.date
								: props.selectType === 'multiple'
								? selectedDays.includes(day.date)
								: dayjs(day.date).isSame(dayjs(rangeStartDay)) ||
								  dayjs(day.date).isSame(dayjs(rangeEndDay)),
						'range-day':
							props.selectType === 'range' &&
							rangeStartDay &&
							rangeEndDay &&
							dayjs(day.date).isAfter(dayjs(rangeStartDay)) &&
							dayjs(day.date).isBefore(dayjs(rangeEndDay)),
						'range-start':
							props.selectType === 'range' && dayjs(day.date).isSame(dayjs(rangeStartDay)),
						'range-end': props.selectType === 'range' && dayjs(day.date).isSame(dayjs(rangeEndDay)),
						'range-disabled': day.rangeDisabled,
					},
				]"
				v-for="(day, index) in dayList"
				:key="index"
				@click="clickDay(day)"
			>
				<div class="day-item-label">
					<text>{{ day.label }}</text>
				</div>
				<div class="day-item-lunar" v-if="props.isLunar">
					<text>{{ day.lunar.IDayCn }}</text>
				</div>
			</div>
		</div>
		<myDateTimePicker
			v-model:timeShow="dateTimePickerShow"
			:currentTime="`${year}/${month}/01`"
			:type="timeType == 'year' ? '7' : '8'"
			@confirm="confirmDateTimePicker"
		>
		</myDateTimePicker>
	</div>
</template>

<script setup>
	import { ref, onMounted, computed, watch, getCurrentInstance, nextTick } from 'vue';
	import { onLoad } from '@dcloudio/uni-app';
	const { proxy } = getCurrentInstance();

	import myDateTimePicker from './myDateTimePicker.vue';

	import dayjs from 'dayjs';

	const props = defineProps({
		date: {
			required: true,
		},
		// 日历选择模式 single 单选 multiple 多选 range 范围
		selectType: {
			type: String,
			default: 'single',
		},
		// 是否显示农历信息
		isLunar: {
			type: Boolean,
			default: false,
		},
		// 限制日期选择范围
		range: {
			type: Array,
			default: [],
		},
	});
	const emit = defineEmits(['clickDay']);

	import solarLunar from './solarLunar.js';

	onMounted(() => {
		if (props.range.length > 0) {
			startDate.value = props.range[0];
			endDate.value = props.range[1];
		}
		// console.log(props.date);
		let date = '';
		if (props.selectType == 'single') {
			date = props.date || dayjs().format('YYYY/MM/DD');
			currentDay.value = date;
			year.value = dayjs(date).year();
			month.value = dayjs(date).month() + 1;
		} else if (props.selectType == 'multiple') {
			date = props.date || [dayjs().format('YYYY/MM/DD')];
			selectedDays.value = date;
			year.value = dayjs(date[date.length - 1]).year();
			month.value = dayjs(date[date.length - 1]).month() + 1;
		} else if (props.selectType == 'range') {
			date = props.date || [dayjs().format('YYYY/MM/DD')];
			rangeStartDay.value = date[0];
			if (date.length > 1) {
				rangeEndDay.value = date[1];
			}
			year.value = dayjs(date[date.length - 1]).year();
			month.value = dayjs(date[date.length - 1]).month() + 1;
		}

		getDays(year.value, month.value);
	});

	const startDate = ref('');
	const endDate = ref('');

	const weekList = ['一', '二', '三', '四', '五', '六', '日'];

	const year = ref();
	const month = ref();

	const slideDirection = ref(''); // 滑动方向

	// 添加触摸相关的变量
	const touchStartX = ref(0);
	const touchEndX = ref(0);
	const minSwipeDistance = 50; // 最小滑动距离,防止误触
	// 触摸开始
	function handleTouchStart(event) {
		touchStartX.value = event.touches[0].clientX;
	}
	// 触摸结束
	function handleTouchEnd(event) {
		touchEndX.value = event.changedTouches[0].clientX;
		const swipeDistance = touchEndX.value - touchStartX.value;
		// 判断滑动距离是否足够
		if (Math.abs(swipeDistance) > minSwipeDistance) {
			// 向左滑动,月份加1
			if (swipeDistance < 0) {
				changeMonth(1);
			}
			// 向右滑动,月份减1
			else {
				changeMonth(-1);
			}
		}
	}

	const timeType = ref('');
	const dateTimePickerShow = ref(false);
	async function openDateTimePicker(type) {
		timeType.value = type;
		await nextTick();
		dateTimePickerShow.value = true;
	}
	function confirmDateTimePicker(timestamp, formattedDate) {
		// console.log(timestamp, formattedDate);
		if (timeType.value == 'year') {
			year.value = formattedDate * 1;
			month.value = 1;
			getDays(year.value, month.value);
		} else if (timeType.value == 'month') {
			month.value = formattedDate * 1;
			getDays(year.value, month.value);
		}
	}

	// 返回今天
	function goToday() {
		if (dayjs().isSame(dayjs(currentDay.value), 'day') && isCurrentDatePage.value) {
			return;
		}

		const today = dayjs().format('YYYY/MM/DD');

		if (isCurrentDatePage.value) {
			year.value = dayjs().year();
			month.value = dayjs().month() + 1;
			if (props.selectType === 'multiple') {
				if (!selectedDays.value.includes(today)) {
					selectedDays.value.push(today);
				}
				emit('clickDay', selectedDays.value);
			} else if (props.selectType === 'range') {
				rangeStartDay.value = today;
				rangeEndDay.value = '';
				emit('clickDay', {
					startDate: rangeStartDay.value,
					endDate: rangeEndDay.value,
					dates: [rangeStartDay.value],
				});
			} else {
				currentDay.value = today;
				emit('clickDay', today);
			}
		} else {
			const time1 = today;
			const time2 = dayjs(`${year.value}/${month.value}/01`).format('YYYY/MM/DD');
			year.value = dayjs().year();
			month.value = dayjs().month() + 1;

			if (props.selectType === 'multiple') {
				if (!selectedDays.value.includes(today)) {
					selectedDays.value.push(today);
				}
				emit('clickDay', selectedDays.value);
			} else if (props.selectType === 'range') {
				rangeStartDay.value = today;
				rangeEndDay.value = '';
				emit('clickDay', {
					startDate: rangeStartDay.value,
					endDate: rangeEndDay.value,
					dates: [rangeStartDay.value],
				});
			} else {
				currentDay.value = today;
				emit('clickDay', today);
			}

			if (dayjs(time1).isAfter(dayjs(time2))) {
				slideDirection.value = 'slide-left';
			} else {
				slideDirection.value = 'slide-right';
			}
			getDays(year.value, month.value);
			// 动画结束后清除类名
			setTimeout(() => {
				slideDirection.value = '';
			}, 500);
		}
	}

	// 改变月份
	function changeMonth(num) {
		// 设置滑动方向
		slideDirection.value = num > 0 ? 'slide-left' : 'slide-right';
		if (month.value + num > 12) {
			month.value = 1;
			year.value = year.value + 1;
		} else if (month.value + num < 1) {
			month.value = 12;
			year.value = year.value - 1;
		} else if (month.value + num <= 12 && month.value + num > 0) {
			month.value = month.value + num;
		}
		getDays(year.value, month.value);
		// 动画结束后清除类名
		setTimeout(() => {
			slideDirection.value = '';
		}, 500);
	}
	const dayList = ref([]);
	const isCurrentDatePage = ref(false); // 日历是否处于当前日期页面
	// 生成日期列表
	async function getDays(year, month) {
		year = year * 1;
		month = month * 1;

		// 判断日历是否处于当前日期页面
		if (year == dayjs().year() && month == dayjs().month() + 1) {
			isCurrentDatePage.value = true;
		} else {
			isCurrentDatePage.value = false;
		}
		// console.log('日历是否处于当前日期页面', isCurrentDatePage.value);

		// 获取选中月份的第一天是星期几(0-6,周日为0)
		let weekDay = dayjs(`${year}-${month}-01`).day();
		// 为了符合中国人的习惯,将周日改为7
		if (weekDay === 0) {
			weekDay = 7;
		}
		// console.log('选中月份的第一天是星期几', weekDay);

		// 获取上个月要展示的天数
		let lastMonthDayNum = weekDay - 1;
		let lastMonthDays = [];
		if (lastMonthDayNum > 0) {
			lastMonthDays = getAllDaysInMonth(year, month - 1, 'before').splice(-lastMonthDayNum);
		}
		// console.log('获取上个月要展示的天数', lastMonthDays);

		// 获取当前月份要展示的天数
		let currentMonthDays = getAllDaysInMonth(year, month, 'current');
		// console.log('获取当前月份要展示的天数', currentMonthDays);

		// 获取下个月要展示的天数
		let nextMonthDayNum = 42 - lastMonthDays.length - currentMonthDays.length;
		let nextMonthDays = getAllDaysInMonth(year, month + 1, 'after').splice(0, nextMonthDayNum);
		// console.log('获取下个月要展示的天数', nextMonthDays);

		dayList.value = [...lastMonthDays, ...currentMonthDays, ...nextMonthDays];
		// console.log('生成日期列表', dayList.value);
	}
	function getAllDaysInMonth(year, month, isCurrentMonth) {
		// 获取当月的总天数
		const daysInMonth = dayjs(`${year}-${month}`).daysInMonth();
		// 初始化当月的日期数组
		const allDays = [];
		// 遍历当月的所有天数
		for (let i = 1; i <= daysInMonth; i++) {
			const day = dayjs(`${year}-${month}-${i}`).format('YYYY/MM/DD');
			// 推入每一天到数组中,包含日期和星期几等信息
			let info = {
				label: dayjs(day).format('DD'), // 展示的日期(日)
				date: day, // 完整日期
				isCurrentMonth, // 是否属于当月 before:上个月 current:当月 after:下个月
				isToday: dayjs(day).isSame(dayjs(), 'day'), // 是否是当天
				rangeDisabled: isDateDisabled(day), // 日期是否禁用
			};
			if (props.isLunar) {
				info.lunar = solarLunar.solarStringToLunar(day);
			}
			allDays.push(info);
		}
		return allDays;
	}

	// 判断日期是否在可选范围外
	function isDateDisabled(date) {
		if (props.range.length === 0) {
			return false; // 没有设置范围限制,不禁用
		}

		const currentDate = dayjs(date);
		const startLimit = dayjs(startDate.value);
		const endLimit = dayjs(endDate.value);

		// 日期在开始日期之前或结束日期之后,则禁用
		return currentDate.isBefore(startLimit, 'day') || currentDate.isAfter(endLimit, 'day');
	}

	const currentDay = ref('');
	// 点击日期
	function clickDay(day) {
		// 如果日期被禁用,不执行任何操作
		if (day.rangeDisabled) {
			return;
		}

		if (day.isCurrentMonth == 'before') {
			changeMonth(-1);
		} else if (day.isCurrentMonth == 'after') {
			changeMonth(1);
		}

		if (props.selectType === 'multiple') {
			// 多选模式
			const index = selectedDays.value.indexOf(day.date);
			if (index > -1) {
				// 如果日期已经选中,则取消选中
				selectedDays.value.splice(index, 1);
			} else {
				// 如果日期未选中,则添加到选中数组
				selectedDays.value.push(day.date);
			}
			emit('clickDay', selectedDays.value);
		} else if (props.selectType === 'range') {
			// 范围选择模式
			if (!rangeStartDay.value || (rangeStartDay.value && rangeEndDay.value)) {
				// 如果没有开始日期,或者已经选择了一个完整的范围,则重新开始选择
				rangeStartDay.value = day.date;
				rangeEndDay.value = '';
				// 不触发事件,等待选择结束日期
			} else {
				// 如果已有开始日期,则设置结束日期
				if (dayjs(day.date).isBefore(dayjs(rangeStartDay.value))) {
					rangeEndDay.value = rangeStartDay.value;
					rangeStartDay.value = day.date;
				} else {
					rangeEndDay.value = day.date;
				}
				// 获取范围内的所有日期
				const dateRange = getDatesBetween(rangeStartDay.value, rangeEndDay.value);
				// 只有当有结束日期且日期范围大于等于2天时才触发事件
				if (rangeEndDay.value && dateRange.length >= 2) {
					emit('clickDay', {
						startDate: rangeStartDay.value,
						endDate: rangeEndDay.value,
						dates: dateRange,
					});
				}
			}
		} else {
			// 单选模式
			currentDay.value = day.date;
			emit('clickDay', day.date);
		}
	}

	const selectedDays = ref([]); // 存储多选的日期
	const rangeStartDay = ref(''); // 范围开始日期
	const rangeEndDay = ref(''); // 范围结束日期

	// 获取日期范围内的所有日期
	function getDatesBetween(startDate, endDate) {
		const dates = [];
		let currentDate = dayjs(startDate);
		const lastDate = dayjs(endDate);

		while (currentDate.isSame(lastDate, 'day') || currentDate.isBefore(lastDate, 'day')) {
			dates.push(currentDate.format('YYYY/MM/DD'));
			currentDate = currentDate.add(1, 'day');
		}

		return dates;
	}
</script>

<style lang="scss" scoped>
	.main-box {
		background: transparent;
	}
	.operation-box {
		display: flex;
		justify-content: center;
		align-items: center;
		position: relative;
		margin-bottom: 36rpx;

		.operation-box-btn {
			font-weight: 400;
			font-size: 30rpx;
			color: #666666;
		}

		.operation-box-time {
			font-weight: 400;
			font-size: 30rpx;
			color: #666666;
		}

		.operation-box-today {
			position: absolute;
			right: 0;
			top: 50%;
			transform: translateY(-50%);
			background: #ffffff;
			border-radius: 50rpx 0 0 50rpx;
			border: 1rpx solid #707070;
			padding: 10rpx 16rpx;
			display: flex;
			justify-content: center;
			align-items: center;
			font-weight: 400;
			font-size: 30rpx;
			color: #666666;
		}
	}
	.week-box {
		margin-bottom: 30rpx;
		display: grid;
		grid-template-columns: repeat(7, 1fr);
		.week-item {
			display: flex;
			justify-content: center;
			align-items: center;
			font-weight: 800;
			font-size: 30rpx;
			color: #333333;
		}
	}
	.day-box {
		transform: translateX(0);
		transition: all 0.5s ease-in-out;
		touch-action: pan-y pinch-zoom;
		user-select: none;
		&.slide-left {
			animation: slideLeft 0.5s ease-in-out;
		}
		&.slide-right {
			animation: slideRight 0.5s ease-in-out;
		}

		display: grid;
		grid-template-rows: repeat(6, 1fr);
		grid-template-columns: repeat(7, 1fr);
		margin-bottom: 25rpx;
		.day-item {
			padding: 20rpx 10rpx;
			display: flex;
			justify-content: center;
			align-items: center;
			flex-direction: column;
			background: transparent;
			border: 4rpx solid transparent;
			.day-item-label {
				font-weight: 500;
				font-size: 30rpx;
				color: #adadad;
			}
			.day-item-lunar {
				margin-top: 6rpx;
				font-weight: 400;
				font-size: 24rpx;
				color: #adadad;
			}
		}
		.current-month {
			.day-item-label {
				color: #333;
			}
			.day-item-lunar {
				color: #333;
			}
		}
		.current-day {
			background: #e1f0f1;
			border-radius: 16rpx;
			border-color: #4a8c90;
			border-width: 4rpx;
			border-style: solid;

			.day-item-label {
				color: #333;
			}
			.day-item-lunar {
				color: #333;
			}
		}
		.range-day {
			background: #e1f0f1;
			border-radius: 0;

			.day-item-label {
				color: #333;
			}
			.day-item-lunar {
				color: #333;
			}
		}
		.range-start {
			background: #e1f0f1;
			border-radius: 0;

			.day-item-label {
				color: #333;
			}
			.day-item-lunar {
				color: #333;
			}
		}
		.range-end {
			background: #e1f0f1;
			border-radius: 0;

			.day-item-label {
				color: #333;
			}
			.day-item-lunar {
				color: #333;
			}
		}
		.range-disabled {
			opacity: 0.4;
			pointer-events: none;

			.day-item-label {
				color: #999;
				text-decoration: line-through;
			}
			.day-item-lunar {
				color: #999;
			}
		}
	}

	@keyframes slideLeft {
		0% {
			transform: translateX(0);
			opacity: 1;
		}
		50% {
			transform: translateX(-100%);
			opacity: 0;
		}
		51% {
			transform: translateX(100%);
			opacity: 0;
		}
		100% {
			transform: translateX(0);
			opacity: 1;
		}
	}
	@keyframes slideRight {
		0% {
			transform: translateX(0);
			opacity: 1;
		}
		50% {
			transform: translateX(100%);
			opacity: 0;
		}
		51% {
			transform: translateX(-100%);
			opacity: 0;
		}
		100% {
			transform: translateX(0);
			opacity: 1;
		}
	}
</style>

注意:

组件选择年月的功能使用了一个自定义组件:myDateTimePicker ,这个组件是我另一个组件:juejin.cn/post/738659…

页面使用

<myCalendar :date="date" @clickDay="clickDay" :selectType="selectType"></myCalendar>

import myCalendar from './myCalendar.vue';
const date = ref('');
// const selectType = ref('single');
// const selectType = ref('multiple');
const selectType = ref('range');
function clickDay(day) {
	console.log(day);
}

农历相关

如果isLunar为true,需要新加一个solarLunar.js文件

solarLunar.js文件来源:

solarLunar.js文件是solarlunar插件的源代码

我将代码copy下来后做了一点小改动

  1. 移除了自执行函数 (function() { ... })()
  2. 将其改为支持使用ES6导入
  3. 源码中调用函数需要传入多个参数:年、月、日,我新增了两个函数用于代替原本的公农历转换函数

tip:讲解一下改动后的js文件

1.调用公历转农历函数由原来的solar2lunar函数改成了solarStringToLunar,参数支持支持 2025/01/07 或 2025-01-07 格式

2.调用农历转公历函数由原来的lunar2solar函数改成了lunarStringToSolar,参数支持支持 2025/01/07 或 2025-01-07 格式

改造后的solarLunar.js文件

// 首先定义所有常量
const lunarInfo = [
	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, 0x16a95, 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, 0x05ac0, 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
	0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
	//2030-2039
	0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
	//2040-2049
	/**Add By JJonline@JJonline.Cn**/
	0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
	//2050-2059
	0x092e0, 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
	0x0d520,
]; //2100

const solarMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

const Gan = [
	'\u7532',
	'\u4E59',
	'\u4E19',
	'\u4E01',
	'\u620A',
	'\u5DF1',
	'\u5E9A',
	'\u8F9B',
	'\u58EC',
	'\u7678',
];
const Zhi = [
	'\u5B50',
	'\u4E11',
	'\u5BC5',
	'\u536F',
	'\u8FB0',
	'\u5DF3',
	'\u5348',
	'\u672A',
	'\u7533',
	'\u9149',
	'\u620C',
	'\u4EA5',
];

const ChineseZodiac = [
	'\u9F20',
	'\u725B',
	'\u864E',
	'\u5154',
	'\u9F99',
	'\u86C7',
	'\u9A6C',
	'\u7F8A',
	'\u7334',
	'\u9E21',
	'\u72D7',
	'\u732A',
];

const festival = {
	'1-1': {
		title: '元旦节',
	},
	'2-14': {
		title: '情人节',
	},
	'5-1': {
		title: '劳动节',
	},
	'5-4': {
		title: '青年节',
	},
	'6-1': {
		title: '儿童节',
	},
	'9-10': {
		title: '教师节',
	},
	'10-1': {
		title: '国庆节',
	},
	'12-25': {
		title: '圣诞节',
	},
	'3-8': {
		title: '妇女节',
	},
	'3-12': {
		title: '植树节',
	},
	'4-1': {
		title: '愚人节',
	},
	'5-12': {
		title: '护士节',
	},
	'7-1': {
		title: '建党节',
	},
	'8-1': {
		title: '建军节',
	},
	'12-24': {
		title: '平安夜',
	},
};
const lFestival = {
	'12-30': {
		title: '除夕',
	},
	'1-1': {
		title: '春节',
	},
	'1-15': {
		title: '元宵节',
	},
	'2-2': {
		title: '龙抬头',
	},
	'5-5': {
		title: '端午节',
	},
	'7-7': {
		title: '七夕节',
	},
	'7-15': {
		title: '中元节',
	},
	'8-15': {
		title: '中秋节',
	},
	'9-9': {
		title: '重阳节',
	},
	'10-1': {
		title: '寒衣节',
	},
	'10-15': {
		title: '下元节',
	},
	'12-8': {
		title: '腊八节',
	},
	'12-23': {
		title: '北方小年',
	},
	'12-24': {
		title: '南方小年',
	},
};

const solarTerm = [
	'\u5C0F\u5BD2',
	'\u5927\u5BD2',
	'\u7ACB\u6625',
	'\u96E8\u6C34',
	'\u60CA\u86F0',
	'\u6625\u5206',
	'\u6E05\u660E',
	'\u8C37\u96E8',
	'\u7ACB\u590F',
	'\u5C0F\u6EE1',
	'\u8292\u79CD',
	'\u590F\u81F3',
	'\u5C0F\u6691',
	'\u5927\u6691',
	'\u7ACB\u79CB',
	'\u5904\u6691',
	'\u767D\u9732',
	'\u79CB\u5206',
	'\u5BD2\u9732',
	'\u971C\u964D',
	'\u7ACB\u51AC',
	'\u5C0F\u96EA',
	'\u5927\u96EA',
	'\u51AC\u81F3',
];
const sTermInfo = [
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf97c3598082c95f8c965cc920f',
	'97bd0b06bdb0722c965ce1cfcc920f',
	'b027097bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf97c359801ec95f8c965cc920f',
	'97bd0b06bdb0722c965ce1cfcc920f',
	'b027097bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf97c359801ec95f8c965cc920f',
	'97bd0b06bdb0722c965ce1cfcc920f',
	'b027097bd097c36b0b6fc9274c91aa',
	'9778397bd19801ec9210c965cc920e',
	'97b6b97bd19801ec95f8c965cc920f',
	'97bd09801d98082c95f8e1cfcc920f',
	'97bd097bd097c36b0b6fc9210c8dc2',
	'9778397bd197c36c9210c9274c91aa',
	'97b6b97bd19801ec95f8c965cc920e',
	'97bd09801d98082c95f8e1cfcc920f',
	'97bd097bd097c36b0b6fc9210c8dc2',
	'9778397bd097c36c9210c9274c91aa',
	'97b6b97bd19801ec95f8c965cc920e',
	'97bcf97c3598082c95f8e1cfcc920f',
	'97bd097bd097c36b0b6fc9210c8dc2',
	'9778397bd097c36c9210c9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf97c3598082c95f8c965cc920f',
	'97bd097bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf97c3598082c95f8c965cc920f',
	'97bd097bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf7f1487f595b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9210c8dc2',
	'97b6b97bd19801ec95f8c965cc920f',
	'97bd07f5307f595b0b0bc920fb0722',
	'7f0e397bd097c36b0b6fc9210c8dc2',
	'9778397bd097c36c9210c9274c920e',
	'97b6b97bd19801ec95f8c965cc920f',
	'97bd07f5307f595b0b0bc920fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf7f1487f595b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf7f1487f531b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c965cc920e',
	'97bcf7f1487f531b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b97bd19801ec9210c9274c920e',
	'97bcf7f0e47f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'9778397bd097c36b0b6fc9210c8dc2',
	'97b6b97bd197c36c9210c9274c920e',
	'97bcf7f0e47f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'9778397bd097c36b0b6fc9210c8dc2',
	'9778397bd097c36c9210c9274c920e',
	'97b6b7f0e47f531b0723b0b6fb0722',
	'7f0e37f5307f595b0b0bc920fb0722',
	'7f0e397bd097c36b0b6fc9210c8dc2',
	'9778397bd097c36b0b70c9274c91aa',
	'97b6b7f0e47f531b0723b0b6fb0721',
	'7f0e37f1487f595b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc9210c8dc2',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f595b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'9778397bd097c36b0b6fc9274c91aa',
	'97b6b7f0e47f531b0723b0787b0721',
	'7f0e27f0e47f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'9778397bd097c36b0b6fc9210c91aa',
	'97b6b7f0e47f149b0723b0787b0721',
	'7f0e27f0e47f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'9778397bd097c36b0b6fc9210c8dc2',
	'977837f0e37f149b0723b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0722',
	'7f0e37f5307f595b0b0bc920fb0722',
	'7f0e397bd097c35b0b6fc9210c8dc2',
	'977837f0e37f14998082b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e37f1487f595b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc9210c8dc2',
	'977837f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e397bd097c35b0b6fc920fb0722',
	'977837f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'977837f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e397bd07f595b0b0bc920fb0722',
	'977837f0e37f14898082b0723b02d5',
	'7ec967f0e37f14998082b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0722',
	'7f0e37f1487f595b0b0bb0b6fb0722',
	'7f0e37f0e37f14898082b0723b02d5',
	'7ec967f0e37f14998082b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0722',
	'7f0e37f1487f531b0b0bb0b6fb0722',
	'7f0e37f0e37f14898082b0723b02d5',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e37f1487f531b0b0bb0b6fb0722',
	'7f0e37f0e37f14898082b072297c35',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e37f0e37f14898082b072297c35',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e37f0e366aa89801eb072297c35',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f149b0723b0787b0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
	'7f0e37f0e366aa89801eb072297c35',
	'7ec967f0e37f14998082b0723b06bd',
	'7f07e7f0e47f149b0723b0787b0721',
	'7f0e27f0e47f531b0723b0b6fb0722',
	'7f0e37f0e366aa89801eb072297c35',
	'7ec967f0e37f14998082b0723b06bd',
	'7f07e7f0e37f14998083b0787b0721',
	'7f0e27f0e47f531b0723b0b6fb0722',
	'7f0e37f0e366aa89801eb072297c35',
	'7ec967f0e37f14898082b0723b02d5',
	'7f07e7f0e37f14998082b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0722',
	'7f0e36665b66aa89801e9808297c35',
	'665f67f0e37f14898082b0723b02d5',
	'7ec967f0e37f14998082b0787b0721',
	'7f07e7f0e47f531b0723b0b6fb0722',
	'7f0e36665b66a449801e9808297c35',
	'665f67f0e37f14898082b0723b02d5',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e36665b66a449801e9808297c35',
	'665f67f0e37f14898082b072297c35',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e26665b66a449801e9808297c35',
	'665f67f0e37f1489801eb072297c35',
	'7ec967f0e37f14998082b0787b06bd',
	'7f07e7f0e47f531b0723b0b6fb0721',
	'7f0e27f1487f531b0b0bb0b6fb0722',
];

const nStr1 = [
	'\u65E5',
	'\u4E00',
	'\u4E8C',
	'\u4E09',
	'\u56DB',
	'\u4E94',
	'\u516D',
	'\u4E03',
	'\u516B',
	'\u4E5D',
	'\u5341',
];
const nStr2 = ['\u521D', '\u5341', '\u5EFF', '\u5345'];
const nStr3 = [
	'\u6B63',
	'\u4E8C',
	'\u4E09',
	'\u56DB',
	'\u4E94',
	'\u516D',
	'\u4E03',
	'\u516B',
	'\u4E5D',
	'\u5341',
	'\u51AC',
	'\u814A',
];

// 直接定义并导出日历对象
const solarLunar = {
	lunarInfo,
	solarMonth,
	Gan,
	Zhi,
	Animals: ChineseZodiac,
	festival,
	lFestival,
	solarTerm,
	sTermInfo,
	nStr1,
	nStr2,
	nStr3,

	getFestival() {
		return this.festival;
	},

	getLunarFestival() {
		return this.lFestival;
	},

	setFestival(param = {}) {
		this.festival = param;
	},

	setLunarFestival(param = {}) {
		this.lFestival = param;
	},

	lYearDays(y) {
		var i,
			sum = 348;
		for (i = 0x8000; i > 0x8; i >>= 1) {
			sum += this.lunarInfo[y - 1900] & i ? 1 : 0;
		}
		return sum + this.leapDays(y);
	},

	leapMonth(y) {
		//闰字编码 \u95f0
		return this.lunarInfo[y - 1900] & 0xf;
	},

	leapDays(y) {
		if (this.leapMonth(y)) {
			return this.lunarInfo[y - 1900] & 0x10000 ? 30 : 29;
		}
		return 0;
	},

	monthDays(y, m) {
		if (m > 12 || m < 1) {
			return -1;
		} //月份参数从1至12,参数错误返回-1
		return this.lunarInfo[y - 1900] & (0x10000 >> m) ? 30 : 29;
	},

	solarDays(y, m) {
		if (m > 12 || m < 1) {
			return -1;
		} //若参数错误 返回-1
		var ms = m - 1;
		if (ms === 1) {
			//2月份的闰平规律测算后确认返回28或29
			return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0 ? 29 : 28;
		} else {
			return this.solarMonth[ms];
		}
	},

	toGanZhiYear(lYear) {
		var ganKey = (lYear - 3) % 10;
		var zhiKey = (lYear - 3) % 12;
		if (ganKey === 0) ganKey = 10; //如果余数为0则为最后一个天干
		if (zhiKey === 0) zhiKey = 12; //如果余数为0则为最后一个地支
		return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1];
	},

	toAstro(cMonth, cDay) {
		var s =
			'\u6469\u7FAF\u6C34\u74F6\u53CC\u9C7C\u767D\u7F8A\u91D1\u725B\u53CC\u5B50\u5DE8\u87F9\u72EE\u5B50\u5904\u5973\u5929\u79E4\u5929\u874E\u5C04\u624B\u6469\u7FAF';
		var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
		return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5EA7'; //座
	},

	toGanZhi(offset) {
		return this.Gan[offset % 10] + this.Zhi[offset % 12];
	},

	getTerm(y, n) {
		if (y < 1900 || y > 2100 || n < 1 || n > 24) {
			return -1;
		}
		var _table = this.sTermInfo[y - 1900];
		var _calcDay = [];
		for (var index = 0; index < _table.length; index += 5) {
			var chunk = parseInt('0x' + _table.substr(index, 5)).toString();
			_calcDay.push(chunk[0], chunk.substr(1, 2), chunk[3], chunk.substr(4, 2));
		}
		return parseInt(_calcDay[n - 1]);
	},

	toChinaMonth(m) {
		// 月 => \u6708
		if (m > 12 || m < 1) {
			return -1;
		} //若参数错误 返回-1
		var s = this.nStr3[m - 1];
		s += '\u6708'; //加上月字
		return s;
	},

	toChinaDay(d) {
		//日 => \u65e5
		var s;
		switch (d) {
			case 10:
				s = '\u521D\u5341';
				break;
			case 20:
				s = '\u4E8C\u5341';
				break;
			case 30:
				s = '\u4E09\u5341';
				break;
			default:
				s = this.nStr2[Math.floor(d / 10)];
				s += this.nStr1[d % 10];
		}
		return s;
	},

	getAnimal(y) {
		return this.Animals[(y - 4) % 12];
	},

	solar2lunar(yPara, mPara, dPara) {
		var y = parseInt(yPara);
		var m = parseInt(mPara);
		var d = parseInt(dPara);
		//年份限定、上限
		if (y < 1900 || y > 2100) {
			return -1; // undefined转换为数字变为NaN
		}
		//公历传参最下限
		if (y === 1900 && m === 1 && d < 31) {
			return -1;
		}

		//未传参  获得当天
		var objDate;
		if (!y) {
			objDate = new Date();
		} else {
			objDate = new Date(y, parseInt(m) - 1, d);
		}
		var i,
			leap = 0,
			temp = 0;
		//修正ymd参数
		y = objDate.getFullYear();
		m = objDate.getMonth() + 1;
		d = objDate.getDate();
		var offset =
			(Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) -
				Date.UTC(1900, 0, 31)) /
			86400000;
		for (i = 1900; i < 2101 && offset > 0; i++) {
			temp = this.lYearDays(i);
			offset -= temp;
		}
		if (offset < 0) {
			offset += temp;
			i--;
		}

		//是否今天
		var isTodayObj = new Date(),
			isToday = false;
		if (
			isTodayObj.getFullYear() === y &&
			isTodayObj.getMonth() + 1 === m &&
			isTodayObj.getDate() === d
		) {
			isToday = true;
		}
		//星期几
		var nWeek = objDate.getDay(),
			cWeek = this.nStr1[nWeek];
		//数字表示周几顺应天朝周一开始的惯例
		if (nWeek === 0) {
			nWeek = 7;
		}
		//农历年
		var year = i;
		leap = this.leapMonth(i); //闰哪个月
		var isLeap = false;

		//效验闰月
		for (i = 1; i < 13 && offset > 0; i++) {
			//闰月
			if (leap > 0 && i === leap + 1 && isLeap === false) {
				--i;
				isLeap = true;
				temp = this.leapDays(year); //计算农历闰月天数
			} else {
				temp = this.monthDays(year, i); //计算农历普通月天数
			}
			//解除闰月
			if (isLeap === true && i === leap + 1) {
				isLeap = false;
			}
			offset -= temp;
		}
		// 闰月导致数组下标重叠取反
		if (offset === 0 && leap > 0 && i === leap + 1) {
			if (isLeap) {
				isLeap = false;
			} else {
				isLeap = true;
				--i;
			}
		}
		if (offset < 0) {
			offset += temp;
			--i;
		}
		//农历月
		var month = i;
		//农历日
		var day = offset + 1;
		//天干地支处理
		var sm = m - 1;
		var gzY = this.toGanZhiYear(year);

		// 当月的两个节气
		// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
		var firstNode = this.getTerm(y, m * 2 - 1); //返回当月「节」为几日开始
		var secondNode = this.getTerm(y, m * 2); //返回当月「节」为几日开始

		// 依据12节气修正干支月
		var gzM = this.toGanZhi((y - 1900) * 12 + m + 11);
		if (d >= firstNode) {
			gzM = this.toGanZhi((y - 1900) * 12 + m + 12);
		}

		//传入的日期的节气与否
		var isTerm = false;
		var Term = null;
		if (firstNode === d) {
			isTerm = true;
			Term = this.solarTerm[m * 2 - 2];
		}
		if (secondNode === d) {
			isTerm = true;
			Term = this.solarTerm[m * 2 - 1];
		}
		//日柱 当月一日与 1900/1/1 相差天数
		var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10;
		var gzD = this.toGanZhi(dayCyclical + d - 1);
		//该日期所属的星座
		var astro = this.toAstro(m, d);
		var solarDate = y + '/' + m + '/' + d;
		var lunarDate = year + '/' + month + '/' + day;
		var festival = this.festival;
		var lFestival = this.lFestival;
		var festivalDate = m + '/' + d;
		var lunarFestivalDate = month + '/' + day;

		// bugfix https://github.com/jjonline/calendar.js/issues/29
		// 农历节日修正:农历12月小月则29号除夕,大月则30号除夕
		// 此处取巧修正:当前为农历12月29号时增加一次判断并且把lunarFestivalDate设置为12-30以正确取得除夕
		// 天朝农历节日遇闰月过前不过后的原则,此处取农历12月天数不考虑闰月
		// 农历润12月在本工具支持的200年区间内仅1574年出现
		if (month === 12 && day === 29 && this.monthDays(year, month) === 29) {
			lunarFestivalDate = '12/30';
		}
		return {
			date: solarDate,
			lunarDate: lunarDate,
			festival: festival[festivalDate] ? festival[festivalDate].title : null,
			lunarFestival: lFestival[lunarFestivalDate] ? lFestival[lunarFestivalDate].title : null,
			lYear: year,
			lMonth: month,
			lDay: day,
			Animal: this.getAnimal(year),
			IMonthCn: (isLeap ? '\u95F0' : '') + this.toChinaMonth(month),
			IDayCn: this.toChinaDay(day),
			cYear: y,
			cMonth: m,
			cDay: d,
			gzYear: gzY,
			gzMonth: gzM,
			gzDay: gzD,
			isToday: isToday,
			isLeap: isLeap,
			nWeek: nWeek,
			ncWeek: '\u661F\u671F' + cWeek,
			isTerm: isTerm,
			Term: Term,
			astro: astro,
		};
	},

	lunar2solar(y, m, d, isLeapMonth) {
		y = parseInt(y);
		m = parseInt(m);
		d = parseInt(d);
		isLeapMonth = !!isLeapMonth;
		var leapMonth = this.leapMonth(y);
		this.leapDays(y);
		if (isLeapMonth && leapMonth !== m) {
			return -1;
		} //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
		if ((y === 2100 && m === 12 && d > 1) || (y === 1900 && m === 1 && d < 31)) {
			return -1;
		} //超出了最大极限值
		var day = this.monthDays(y, m);
		var _day = day;
		//bugFix 2016-9-25
		//if month is leap, _day use leapDays method
		if (isLeapMonth) {
			_day = this.leapDays(y, m);
		}
		if (y < 1900 || y > 2100 || d > _day) {
			return -1;
		} //参数合法性效验

		//计算农历的时间差
		var offset = 0;
		var i;
		for (i = 1900; i < y; i++) {
			offset += this.lYearDays(i);
		}
		var leap = 0,
			isAdd = false;
		for (i = 1; i < m; i++) {
			leap = this.leapMonth(y);
			if (!isAdd) {
				//处理闰月
				if (leap <= i && leap > 0) {
					offset += this.leapDays(y);
					isAdd = true;
				}
			}
			offset += this.monthDays(y, i);
		}
		//转换闰月农历 需补充该年闰月的前一个月的时差
		if (isLeapMonth) {
			offset += day;
		}
		//1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
		var strap = Date.UTC(1900, 1, 30, 0, 0, 0);
		var calObj = new Date((offset + d - 31) * 86400000 + strap);
		var cY = calObj.getUTCFullYear();
		var cM = calObj.getUTCMonth() + 1;
		var cD = calObj.getUTCDate();
		return this.solar2lunar(cY, cM, cD);
	},

	/**
	 * 传入时间字符串转换为农历
	 * @param dateStr 时间字符串,支持 2025/01/07 或 2025-01-07 格式
	 * @returns 农历信息对象
	 */
	solarStringToLunar(dateStr) {
		if (!dateStr) return null;
		// 处理日期字符串,支持 / 或 - 分隔
		const parts = dateStr.split(/[-/]/);
		if (parts.length !== 3) return null;

		const [year, month, day] = parts.map((part) => parseInt(part));
		return this.solar2lunar(year, month, day);
	},

	/**
	 * 传入农历时间字符串转换为公历
	 * @param dateStr 时间字符串,支持 2025/01/07 或 2025-01-07 格式
	 * @param isLeapMonth 是否闰月
	 * @returns 公历信息对象
	 */
	lunarStringToSolar(dateStr, isLeapMonth = false) {
		if (!dateStr) return null;
		// 处理日期字符串,支持 / 或 - 分隔
		const parts = dateStr.split(/[-/]/);
		if (parts.length !== 3) return null;

		const [year, month, day] = parts.map((part) => parseInt(part));
		return this.lunar2solar(year, month, day, isLeapMonth);
	},
};

export default solarLunar;

页面使用:

import solarLunar from './solarLunar.js';
console.log(solarLunar.solarStringToLunar('2025/01/07'));
console.log(solarLunar.lunarStringToSolar('2025/01/07'));