需求
为了写一个自定义的日历组件,所以需要生成一个日历数据
思路
dayjs 是一个便利的日期库,和 moment 的 API 一样,首先可以用它来获得一些基础的日历信息,那些我并不想自己来计算。
为了生成日历数据,要先得到一个日期 date,然后为了方便进行日期计算,所以将 date 先转换成 dayjs 的对象,这里传入的 date 已经是 dayjs 的对象
获得基础信息
javascript
function generateCalendar(date, options = {
weekStartsOnMonday: true,
}) {
// 最终所需的data
const calendar = [];
// 今天的日期
const today = dayjs();
// 传入日期的年份
const year = date.year();
// 传入日期的月份(0-11,所以要+1)
const month = date.month() + 1;
// 传入日期的那个月有多少天
const daysInMonth = date.daysInMonth();
// 传入日期的那个月的第一天
const monthFirstDay = date.startOf("month");
// 传入日期的那个月的最后一天
const monthEndDay = date.endOf("month");
// 周起始日是周一还是周日
const weekStartDay = options.weekStartsOnMonday ? 1 : 0;
}
星期的起始
众所周知,日历可以配置星期起始于周一或者周日,所以在生成时要考虑到。dayjs 的.day()方法可以拿到该日期是星期几,值为0-6,0代表星期日,所以为了方便我自己理解,写一个获得.day()对应的周索引的函数,1是一周的第一天,7是一周的最后一天
javascript
function getWeekIndex(day, startDay) {
return (day - startDay + 7) % 7 + 1
}
例如获得到了一个日期是星期三,周起始日是周日,那么 weekIndex 就是 4,代表在这一周星期三位于第四个位置
javascript
// 当前月份第一天在周中是第几天
const monthFirstDayWeekIndex = getWeekIndex(monthFirstDay.day(), weekStartDay);
// 当前月份最后一天在周中是第几天
const monthEndDayWeekIndex = getWeekIndex(monthEndDay.day(), weekStartDay);
生成前一个月的数据
有了上面的基本信息,就可以开始生成日历数据了
首先是生成前一个月的数据,当月份第一天并非一周的开始日时,就会有上个月的日期数据
javascript
// 上个月的数据
const prevMonthDays = [];
if (monthFirstDayWeekIndex !== 1) {
// 将这个月第一天减去1天,就拿到了上个月最后一天
let prevMonthLastDate = monthFirstDay.subtract(1, 'day').date();
const prevMonthYear = date.subtract(1, 'month').year();
const prevMonthMonth = date.subtract(1, 'month').month() + 1;
// 根据当前月份第一天在第几位,计算上个月份的天数
for (let prevMonthWeekIndex = monthFirstDayWeekIndex - 1; prevMonthWeekIndex > 0; prevMonthWeekIndex--) {
prevMonthDays.push({
year: prevMonthYear,
month: prevMonthMonth,
date: prevMonthLastDate,
day: getWeekDay(prevMonthWeekIndex, weekStartDay),
isCurrentMonth: false,
isPrevMonth: true,
fullDate: `${prevMonthYear}-${padDate(prevMonthMonth)}-${padDate(prevMonthLastDate)}`
});
prevMonthLastDate--;
}
prevMonthDays.reverse();
}
假设这个月第一天是第一周的第三天,代表有两天的上个月的数据,所以在计算上个月数据时,是倒着添加进数组中的,所以在全部添加完毕后,需要将数组倒转
此处有两个函数,一个是getWeekDay,作用是根据周索引和周起始日来获取这一天是周几
javascript
const daysOfWeek = [7, 1, 2, 3, 4, 5, 6];
function getWeekDay(index, startDay) {
return daysOfWeek[startDay + index - 1];
}
第二个是padDate,很简单就是为不足10的日期添加一个0
javascript
function padDate(date) {
return String(date).padStart(2, '0');
}
生成下一个月的数据
因为日历数据中每周都有7天,所以会有当本月结束后,下个月的数据填补的情况
javascript
// 当前日历的周数
const calendarLength = Math.ceil((monthFirstDayWeekIndex - 1 + daysInMonth) / 7);
// 下个月的数据
const nextMonthDays = [];
// 当前日历去掉上月数据和当月数据的剩余天数
const nextMonthDaysLength = calendarLength * 7 - daysInMonth - prevMonthDays.length;
if (nextMonthDaysLength > 0) {
const nextMonthYear = date.add(1, 'month').year();
const nextMonthMonth = date.add(1, 'month').month() + 1;
// 根据当前月份还剩下的天数,计算下个月份的天数
for (let nextMonthDate = 1; nextMonthDate <= nextMonthDaysLength; nextMonthDate++) {
nextMonthDays.push({
year: nextMonthYear,
month: nextMonthMonth,
date: nextMonthDate,
day: getWeekDay(monthEndDayWeekIndex + nextMonthDate, weekStartDay),
isCurrentMonth: false,
isNextMonth: true,
fullDate: `${nextMonthYear}-${padDate(nextMonthMonth)}-${padDate(nextMonthDate)}`
});
}
}
生成日历数据
javascript
let currentMonthDate = 1;
for (let w = 1; w <= calendarLength; w++) {
const week = [];
// 在第一周时将上个月的数据添加进来
if (w === 1) {
week.push(...prevMonthDays);
}
for (let wi = week.length + 1; wi <= 7; wi++) {
// 本月数据全部生成后就可以停止了
if (currentMonthDate > daysInMonth) {
break;
}
const day = {
year,
month,
date: currentMonthDate,
day: getWeekDay(wi, weekStartDay),
isCurrentMonth: true,
fullDate: `${year}-${padDate(month)}-${padDate(currentMonthDate)}`,
}
day.isToday = today.isSame(day.fullDate, 'day')
week.push(day);
currentMonthDate++;
}
calendar.push(week);
}
// 将下个月的数据添加进最后一周
calendar[calendar.length - 1].push(...nextMonthDays);
这样,日历的数据就全部得到了