js根据开始结束日期进行月度分段?
比如用户选择开始日期2022-01-15和结束日期2022-10-20,然后按月度分段,则会返回分段后的日期范围:
[
{startDate:2022-01-15, endDate:2022-01-31},
{startDate:2022-02-01, endDate:2022-02-28},
{startDate:2022-03-01, endDate:2022-03-31},
// ......
{startDate:2022-10-01, endDate:2022-10-20},
]
回复
1个回答
test
2024-07-10
// 把日期(按月)转换成整数,方便递增计算
function toNumber(date) {
const d = new Date(date);
return d.getFullYear() * 12 + d.getMonth();
}
// 把日期格式成 YYYY-MM-DD 格式
function format(date) {
return [
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
].map(n => `${n}`.padStart(2, "0")).join("-");
}
function getDateSegments(begin, end) {
const mBegin = toNumber(begin); // 起月的整数表示
const mEnd = toNumber(end); // 止月的整数表示
// 第 1 种情况,两个日期在同一个月,只有一段
if (mBegin === mEnd) {
return [{ startDate: begin, endDate: end }];
}
// 其它情况可以认为有 3 段
// 起始日期所在月
// (可能为空)其他月
// 结束日期所在月
// 先算起始日期所在月 prefix
const prefix = (d => {
const e = new Date(d.getTime());
e.setDate(0);
e.setMonth(e.getMonth() + 1);
return { startDate: format(d), endDate: format(e) };
})(new Date(begin));
// 再算结束日期所在月 suffix
const suffix = (d => {
const b = new Date(d.getTime());
b.setDate(1);
return { startDate: format(b), endDate: format(d) };
})(new Date(end));
// 最后中间那部分(有可能是空的)
const baseMonth = mBegin + 1;
// 下面这句包含 from 中的 mapper 一共有 3 个 map 操作,可以合并
// 这里为了清晰逻辑,分开写的,性能会略差一些
const middle = Array
.from(
{ length: mEnd - baseMonth },
(_, i) => baseMonth + i
)
.map(m => ({ year: ~~(m / 12), month: m % 12 }))
.map(({ year, month }) => ({
startDate: format(new Date(year, month, 1)),
endDate: format(new Date(year, month + 1, 0))
}));
// 三部分拼起来就是结果
return [prefix, ...middle, suffix];
}
console.log(getDateSegments("2022-01-31", "2022-02-01"));
console.log("-------------");
console.log(getDateSegments("2022-02-01", "2022-02-01"));
console.log("-------------");
console.log(getDateSegments("2022-01-15", "2022-10-20"));
处理了一下,看起来要代码要短一些
function getDateSegments(begin, end) {
Date.prototype.format = function () {
return [this.getFullYear(), this.getMonth() + 1, this.getDate()]
.map(n => `${n}`.padStart(2, "0")).join("-");
};
const mBegin = (d => d.getFullYear() * 12 + d.getMonth())(new Date(begin));
const mEnd = (d => d.getFullYear() * 12 + d.getMonth())(new Date(end));
// 第 1 种情况,两个日期在同一个月,只有一段
if (mBegin === mEnd) {
return [{
startDate: new Date(begin).format(),
endDate: new Date(end).format()
}];
}
const prefix = (b => ({
startDate: b.format(),
endDate: new Date(b.getFullYear(), b.getMonth() + 1, 0).format()
}))(new Date(begin));
const suffix = (e => ({
startDate: new Date(e.getFullYear(), e.getMonth(), 1).format(),
endDate: e.format()
}))(new Date(end));
// 最后中间那部分(有可能是空的)
function* middles() {
for (let i = mBegin + 1; i < mEnd; i++) {
const [y, m] = [~~(i / 12), i % 12];
yield {
startDate: new Date(y, m, 1).format(),
endDate: new Date(y, m + 1, 0).format()
};
}
}
return [prefix, ...middles(), suffix];
}
之所以不用 toISOString().slice(0, 10)
要自己写 format
,是因为下图的原因
2023-02-13 补充
用迭代的办法确实代码要短好多
function getDateSegments(begin, end) {
Date.prototype.format = function () {
return [this.getFullYear(), this.getMonth() + 1, this.getDate()]
.map(n => `${n}`.padStart(2, "0")).join("-");
};
function* iterate(begin, end) {
// mBegin 要和 end 比较,要保证他们是在同一个时区下进行比较,所以重新生成
// 原因见下图
end = new Date(end.getFullYear(), end.getMonth(), end.getDate());
let mBegin = new Date(begin.getFullYear(), begin.getMonth(), begin.getDate());
while (mBegin <= end) {
const mEnd = new Date(mBegin.getFullYear(), mBegin.getMonth() + 1, 0);
if (mEnd >= end) {
yield { startDate: mBegin.format(), endDate: end.format() };
break;
}
yield { startDate: mBegin.format(), endDate: mEnd.format() };
mBegin = new Date(mBegin.getFullYear(), mBegin.getMonth() + 1, 1);
}
}
return [...iterate(new Date(begin), new Date(end))];
}
iterate 的 mBegin 和 end 之所以重新生成,一个是下图时区的原因。另一个,需要去除输入 Date 对象的时间部分(万一有呢)
回复
适合作为回答的
- 经过验证的有效解决办法
- 自己的经验指引,对解决问题有帮助
- 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
- 询问内容细节或回复楼层
- 与题目无关的内容
- “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容