最近在做微信小程序项目,其中涉及到日程安排。而所使用的框架中,又没有让我满意的日历组件,思来想去,自己写了一个,具体实现请往下看吧。

思路分析
首先最主要的一点,就是要计算出某年某月有多少天,其中涉及到大小月,闰、平年二月。
其次,弄清楚每一天对应的是周几。
然后,有时为填充完整,还需显示上月残余天数以及下月开始几天,这些又该如何展示。
最后,根据自己项目需求实现其它细枝末节。

首先是计算每月天数
按照一般思路,[1,3,5,7,8,10,12]这几个月是31天,[2,3,6,9,11]这几个月是30天,闰年2月29天,平年2月28天。但是每次需要计算天数时,都得如此判断一番。很繁琐,换一种思路,用时间戳来计算。本月1号0时的时间戳,与下月1号0时的时间戳之差,就是当前月份的毫秒数,计算一下,就能得出天数了,如下。

    function getMonthLength(year, month) {
        let actualMonth = month - 1;
        let timeLength = new Date(year, month) - new Date(year, actualMonth);
        return timeLength / (1000 * 60 * 60 * 24);
    }

看到上述代码,你可能会想,是不是还缺少当月为12月时的特殊判断,毕竟这涉及到跨年问题。但是你无需担心,根据MDN中关于Date的表述,JS早就想到了这一点,它会为我们处理好的。
当Date作为构造函数调用并传入多个参数时,如果数值大于合理范围时(如月份为13或者分钟数为70),相邻的数值会被调整。比如 new Date(2013, 13, 1)等于new Date(2014, 1, 1),它们都表示日期2014-02-01(注意月份是从0开始的)


接下来是弄清楚每一天对应的是周几

    function getWeek(y, m, d) {
        let weeks = ['日', '一', '二', '三', '四', '五', '六'];
        return weeks[new Date(y, m - 1, d).getDay()];
    }

每个月的数据如何展示

很多时候,除了需要显示本月的信息,还会需要显示上下月的部分信息。而日历展示时,一般都是7 X 6 = 42格,当月天数已知,上月残余天数,我们可以用当月1号是周几来推断出来,例如当月1号是周六,那么需要显示上月最后(6-1)天的数据。而下月需要显示的天数,正好用42 – 当月天数 -(6-1)。

获取需要显示的上个月的天数:

    function getNeedDisplayLastMonthDays(year, month) {
        let week = new Date(year, month - 1, 1).getDay();
        if (week == 0) return 6;
        else return week - 1;
    }

获取本月需要显示的信息:

function getCurrentMonth(year, month) {
	let currentMonthDays = getMonthLength(year, month); //获取本月天数
	let currentMonthInfo = new Array(); //储存本月信息
	let today = new Date().toLocaleDateString(); //获取当天的日期
	if (currentMonthDays > 0) {
		for (let i = 1; i <= currentMonthDays; i++) {
			currentMonthInfo.push({
			tag: 'current',
			year: year,
			month: month,
			day: i,
			week: getWeek(year, month, i),
			isToday: today == new Date(year, month - 1,  i).toLocaleDateString()
			})
		}
	}
	return currentMonthInfo
}

获取上个月、下个月需要显示的信息:

//获取上个月的信息
function getLastMonth(year, month) {
	let lastMonthDays = getNeedDisplayLastMonthDays(year, month); //获取需要显示的上个月的天数
	let lastMonthInfo = new Array(); //储存需要显示的上个月的信息
	let lmYear = month == 1 ? --year : year; //上个月所属年份,当传入的月份为1时,则年份-1,否则不动
	let lmMonth = month == 1 ? 12 : --month; //上个月所属月份,当传入的月份为1时,则月份变为12,否则-1
	let lmDate = getMonthLength(lmYear, lmMonth) // 获取上月最后一天的日期
	for (let i = 0; i < lastMonthDays; i++) {
		lastMonthInfo.unshift({
		tag: 'last',
		year: year,
		month: month,
		day: lmDate,
		week: getWeek(lmYear, lmMonth, lmDate),
		isToday: false
		})
	lmDate--
	}
return lastMonthInfo
}

//获取下个月的信息
function getNextMonth(year, month) {
	let currentMonthDays = getMonthLength(year, month);
	let lastMonthDays = getNeedDisplayLastMonthDays(year, month);
	let nextMonthDays = 42 - currentMonthDays - lastMonthDays; //获取需要显示的下个月的天数
	let nextMonthInfo = new Array(); //储存需要显示的下个月的信息
	let nmYear = month == 12 ? ++year : year; //上个月所属年份,当传入的月份为1时,则年份-1,否则不动
	let nmMonth = month == 12 ? 1 : ++month; //上个月所属月份,当传入的月份为1时,则月份变为12,否则-1
	if (nextMonthDays > 0) {
		for (let i = 1; i <= nextMonthDays; i++) {
			nextMonthInfo.push({
				tag: 'next',
				year: nmYear,
				month: nmMonth,
				day: i,
				week: getWeek(nmYear, nmMonth, i),
				isToday: false
			})
		}
	}
	return nextMonthInfo
}

最后,把三个数组合并一下

arr = […lastMonthInfo, …currentMonthInfo, …nextMonthInfo]

最后的数据结构大概是这样

    [
        {tag: "last", year: 2020, month: 7, day: 30, week: "四", …}
        {tag: "last", year: 2020, month: 7, day: 31, week: "五", …},
        {tag: "current", year: 2020, month: 8, day: 1, week: "六", …},
        ...
        {tag: "current", year: 2020, month: 8, day: 31, week: "一", …},
        {tag: "next", year: 2020, month: 9, day: 1, week: "二", …},
        {tag: "next", year: 2020, month: 9, day: 2, week: "三", …}
    ]

至于样式和数据的如何展示,就按照自己的需求写吧~

               

作者