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},
]
阅读 3.5k
6 个回答
// 把日期(按月)转换成整数,方便递增计算
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,是因为下图的原因

image.png


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 对象的时间部分(万一有呢)

image.png

import dayjs from 'dayjs'

const getDateRange = (start, end) => {
    start = dayjs(start)
    end = dayjs(end)
    if (! start.isBefore(end)) [start, end] = [end, start] // 确保 start 在 end 前面

    const range = []
    let startDate = start, endDate
    while (! (startDate.isSame(end, 'month') && startDate.isSame(end, 'year'))) { // 到和最后一个日期同月份的时候就可以结束了
        endDate = startDate.endOf('month') // 到月末结束
        range.push({ startDate, endDate })
        startDate = endDate.add(1, 'day') // 月末的下一天是月初
    }
    range.push({ startDate, endDate: end }) // 把最后一段加上

    return range
}

image.png

function getDateRangeByMonth(start,end) {
  const s = new Date(start);
  const e = new Date(end);
  const range = [];
  while(s <= e) {
    const startDate = s.toISOString().slice(0,10)
    // 到结束日期月份,直接结束循环
    if(s.getFullYear()==e.getFullYear()&&s.getMonth()==e.getMonth()) {
        range.push({startDate, endDate: end})
        break;
    }
    // 获取月份最后一天分两步,一:月份加1;二:setDate参数为0则为上一月最后一天
    // 由于月份天数不一致,当当前月份的日期超过下一个月的日期,月份加1会溢出等同于加2,如当前2022-01-31,setMonth加1时会变成3月,所以这里先setDate至1
    s.setDate(1)
    s.setMonth(s.getMonth()+1)
    s.setDate(0)
    range.push({
        startDate, 
        endDate: s.toISOString().slice(0,10)
    })
    // push后,天数加1则回到下一月的头一天
    s.setDate(s.getDate()+1)
  }
  return range;
}

getDateRangeByMonth('2022-01-15','2022-10-20')

针对评论所说的情况做了修改

js 代码

function f(start, end) {
    let ranges = []
    let [i, j] = [start, end].map(i => new Date(i)).sort((a, b) => a - b)
    
    while (i <= j) {
        const startDate = i.toISOString().slice(0, 10)
        i = new Date(i.getFullYear(), i.getMonth() + 1, 0, i.getHours())
        const endDate = new Date(Math.min(+i, +j)).toISOString().slice(0, 10)
        i.setDate(i.getDate() + 1)
        ranges.push({startDate, endDate})
    }

    return ranges
}

使用

f('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-04-01", "endDate": "2022-04-30"},
  {"startDate": "2022-05-01", "endDate": "2022-05-31"},
  {"startDate": "2022-06-01", "endDate": "2022-06-30"},
  {"startDate": "2022-07-01", "endDate": "2022-07-31"},
  {"startDate": "2022-08-01", "endDate": "2022-08-31"},
  {"startDate": "2022-09-01", "endDate": "2022-09-30"},
  {"startDate": "2022-10-01", "endDate": "2022-10-20"}
]

我这边是借助了第三方库dayjs完成。

思路是:取值每一个月份的月底日期,完成一次分段的取值,借助月底日期计算下一次的初始日期
import dayjs from "dayjs"
const getMonthBySub = (start,end) => {
    // 标准输入月份字符串
    let lastMonth = dayjs(start);
    const endMonth = dayjs(end);
    const subMonth = []; // 分段月份存放
    do {
        const startDate = lastMonth.format("YYYY-MM-DD");
        // 计算一个月的月底日期 28 29 30 31
        const endDate = dayjs(lastMonth.endOf('month').format("YYYY-MM-DD"));// endof 会取值到59分59秒 导致while里面比较出错 统一格式
        // 计算下一个lastMoth
        lastMonth = endDate.add(1, "day"); // eg: 2022-01-31 => 2022-02-01
        const endDateMonth = lastMonth <= endMonth ? endDate : endMonth;
        subMonth.push({startDate, endDate: endDateMonth.format("YYYY-MM-DD")});
    }while(lastMonth <= endMonth )
    console.log(subMonth)
    return subMonth
}
getMonthBySub('2022-01-15', '2022-10-20')
getMonthBySub('2022-01-01', '2023-01-03')
getMonthBySub('2022-01-01', '2022-01-03')

function changeDate(val) {
   return +(val.split('-').join('').slice(0, -2))
}
function getDate(val) {
   let y = val.split('-')[0] // 2022
   let m = val.split('-')[1] // 01
   let d = val.split('-')[2] // 15
    return {y, m, d}
}
function date(start, end) {
    let {y, m, d} = getDate(start)
    let list = []
    let obj = {startDate: start, endDate: `${y}-${m}-${new Date(y, m, 0).getDate()}`}
    while ((list.length && changeDate(list[list.length - 1].startDate)) < changeDate(end)) {
        list = [...list, obj]
        let {y: y1, m: m1, d: d1} = getDate(list[list.length - 1].startDate)
        obj = {startDate: `${++m1 <= 12 ? y1 : ++y1}-${(m1 <= 12 ? m1 < 10 ? '0' + m1 : m1 : m1 = 1 && '01')}-01`, endDate: `${y1}-${(m1 < 10 ? '0' + +m1 : m1)}-${new Date(y1, m1, 0).getDate()}`}
    }
    list[list.length - 1].endDate = end
    return list
}
console.log(date('2022-01-15', '2024-02-20'))
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏