暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

关于 Date.prototype.setDate 方法你可能不知道的事

背井 2021-03-19
824

偶尔会碰到生成一组日期序列的需求。比如,要按天展示用户最近5天的数据,如果某天没数据,则这一天数据显示零。

如果此时,你从数据库中查出的数据是这样的:

2021-03-18 102021-03-17 152021-03-15 202021-03-14 10
复制

发现少了 2021-03-16 这天的数据。所以,你可能会首先生成从  2021-03-142021-03-18 这个日期的日期数组,然后遍历该数组对上面数据进行补漏。

本文要谈的,是怎么生成这个日期数组,以及从中能发现什么技巧。

Java 版本的日期序列生成

通常的经验中,同样的功能用 Java 写明显要繁琐很多,但这次却不一样,一行代码就能完成:

List<LocalDate> dates = Stream        // 从当前时间开始,不停地生成前一天的数据        .iterate(LocalDate.now(), b -> b.minusDays(1))        // 只取最近5天的        .limit(5)        // 从远到近排序        .sorted()        .collect(Collectors.toList());
复制

得易于 Java 8 引入的重新设计的日期类,Java 日期处理从来没像今天这么简单。

调试面板中查看上面的日期序列

但本文的重点是怎么用 JavaScript 来写同样的功能。

JavaScript 的版本

早期的 JavaScript ,很多 API 设计都是参考自 Java 语言,这就导致 JavaScript 中的 DateJava 中的一样难用,以致于 JS 生态中出现了层出不穷的第三方日期库。

不过这里,我们要尝试使用原生 JavaScript 来生成日期序列,而这主要用到的就是 Date 对象的 setDate() 方法,它用来设置 年月日 中的 (即,我们常说的几号)。

假设今天是 2021-03-18,如果要生成最近 5 天的日期数据,你可能会尝试这样写:

// 给定日期,因为月份是从 0 开始,所以要减 1const now = new Date(20213 - 118);// 日期序列const dates = [];// 当前日期,即 18const date = now.getDate();// 生成几天的数据let n = 5;while (n--) {    now.setDate(date - n);    dates.push(format(now));}console.log(dates);
复制

运行上面代码输出如下:

好像没有错 😆 !

但如果把当前日期从 2021-03-18 换为 2021-03-02 就会发现有蹊跷:

生成的数据全乱了!

原因是 setDate() 这个方法,它和你初看产生的理解略有差别。

setDate() 用法

假设当前日期是 2021-03-02,对于 setDate(n)

  1. 如果 n > 0,则有2种情况
    1. 如果 n 小于当月最大天数,则日期设置到当月的第 n 天。即 setDate(4) 是当月 4 号,setDate(31) 是当月 31 号。
    2. 如果 n 大于当月最大天数,则月份和日期都往前推。即 setDate(32) 是下月 1 号(4月1号)。
  2. 如果 n = 0,则月份变为上一月,日期变为上一月的最后一天。即 setDate(0) 是上月 28 号 (2月28号)。
  3. 如果 n < 0,则月份先变为上一月,日期从上月最一天往后推 n 天(如果 n+1 超过了上一月的最大天数,则月份也会往前推)。即setDate(-1) 是上月 27 号 (2月27号,上月最后一天往前推一天),而 setDate(-28) 则是1月31号(因为2月就28天)。

上述种种,如果月份推到尽头(1月或12月),则年份也会相应转变。

有了以上基础,就能理解为什么上面的日期会错乱了,因为我们没有考虑到 n <= 0 的情况。

调整后的代码如下:

/** * 生成给定日期之前的 n 个日期序列 * * @param d 给定的日期 * @param n 生成几个之前的日期 */functiondates(d, n = 1{    // 生成日期数量小于1时,直接返回    if (n < 1) {        return;    }    // 创建一个日期参数的拷贝,因为我们不想直接修改入参    const cp = new Date(d);    const day = d.getDate();    // 第一个日期的前一天    cp.setDate(day - n);    while (n--) {        // 每次循环,都将日期设为下一天        cp.setDate(cp.getDate() + 1);        yield format(cp);    }}
复制

重要的部分都给了注释,这次是把它封装成了一个函数,因为我们要复用它。

如果调用这个函数:

console.log([...dates(new Date(), 5)]);
复制

会得到如下结果:

没有问题,尝试把当前日期设为 2021-03-02

console.log([...dates(new Date(2021,3 - 1,2), 5)]);
复制

也没有问题!


解疑:为什么上面的函数要用 generator 来写?

如果你总是要生成 给定数量 的日期序列,直接将函数中的日期入在数组中返回也可。但试想这样一种场景:你想生成一组日期序列,在生成过程中如果达到某种条件时,就提前终止。这时使用 generator 可以达到更好的效果(相到于懒生成,而不是一次性生成所有日期):

var myDates = [];for (const date of dates(new Date(2021,3 - 1,2), 5)) {    myDates.push(date);    // 模拟某种提前结束的条件    if (Math.random() < 0.5) {        break;    }}console.log(myDates);
复制

某次运行的结果如下:

可以想见,当 n 的值非常大时,这种懒生成的方式能达到更好的效果。

附:上面的代码中的 format 函数如下:

/** * 将日期对象转换为 yyyy-MM-dd 格式的字符串 */function format(d{
    return [
        d.getFullYear(), 
        String(d.getMonth() + 1).padStart(20), 
        d.getDate()
    ].join('-');
}

复制

谢谢阅读!

- END -


文章转载自背井,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论