从一个签到功能(日历)到 js Date 类型的全了解

2,180 阅读7分钟

最近工作工作遇到一个签到功能,网上找了很多日历插件,发现都不是很适合,或者说不能很好的实现产品的需求,结果还是自己撸了一个,撸的过程也是对js Date 类型重新学习了一遍,对 Date 类型的方法也是有了一个全面的熟悉.

1.需求:

  • 每日签到功能

  • 日历补签功能

  • 特殊日特殊奖励提醒(不能补签)

  • 签到活跃度领取加上一个活跃进度条与礼包领取

2.需求分析

这里只对日历功能进行实现,如果有机会再写一下分享一下进度条功能吧

这是W10自带的日历功能,去除月份切换,把阴历节日特别显示换成特殊日奖励提醒就是一个签到日历了,从日历图上我们可以看出我们需要显示的信息有:

  • 当天年月信息
  • 本月1号是周几
  • 本月有多少天
  • 上个月有多少天(由于不需要月份切换,故而可以不显示上月和下个月的日历)

2.1 创建时间对象

要创建一个日期对象, 使用 new 操作符和 Date 构造函数即可,如下所示

let now =  new Date()

在调用 Date 构造函数而不传递参数的情况下, 新创建的对象自动获得当前日期和时间. 如果想根据特定的日期和时间创建时间对象,必须传入表示该日期的毫秒数( 即从 UTC 时间 1970 年 1 月 1 日我i也起至该日期止经过的毫秒数 ). 为了简化这一计算过程, ECMAScript 提供了两个方法: Date.parse() 和 Dare.UTC()

2.1.1 Date.parse() 方法

其中, Date.parse() 方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回的日期毫秒数, ECMA-262 没有定义Date.parse() 应该支持哪种日期格式,因此这个方法的行为因实现地区而异.国内支持的日期格式为

  • "年/月/日", 如 2019/3/17

例如, 要为 2019 年 3 月 8 号 创建一个日期对象,可以使用下面的代码:

let someDate = new Date(Date.parse("2019/3/8"))

如果传入的 Date.parse() 方法的字符串不能表示日期,那么它会返回 NaN , 实际上,如果直接将表示日期的字符串传递给 Date 构造函数,也会在后台调用 Date.parse() 方法.也就是下面的代码和上面的等价:

let someDate = new Date("2019/3/8")

2.1.2 Date.UTC() 方法

Date.UTC() 方法同样也返回表示日期的毫秒数,但它与 Date.parse() 在构建值时使用不同的信息. Date.UTC() 的参数分别是年份、基于0的月份(一月是 0 ,二月是 1 , 一次类推)、月中的哪一天( 1 到 31 )、小时数( 0 到 23 )、分钟、秒以及毫秒数.在这些参数中,只有前两个参数(年月)是必须的,如果没有提供月中的天数,则假设天数为1; 如果省略其他参数,则统统设置为 0 . 以下是例子:

// GMT 时间 2000 年 1 月 1 日 午夜零时,换算成中国标准时间就成了 2000 年 1 月 1 日 早晨 8 点整,中国时区为 GMT+0800(东八区)
let yk1 = new Date(Date.UTC(2000, 0))
// GMT 时间 2005 年 5 月 5 日 下午 5:55:55 换成中国标准时间就是 6 号 凌晨一点了
let allFives = new Date(Date.UTC(2005, 4 , 5, 17, 55, 55))

如同模仿 Date.parse() 一样, Date 构造函数也会模仿 Date.UTC() ,但有一点明显不同: 日期和时间都是基于本地而非 GMT 来创建. 而 Date 构造函数接受的参数仍然与 Date.UTC() 相同.因此, 如果第一个参数是数值, Date 构造函数就会假设该数值是日期中的年份, 而第二个参数是月份,以此类推.据此,可以将前面的例子重写如下

// 本地时间 2000 年 1 月 1 日
let yk2 = new Date(2000, 0) 
// 本地时间 2005 年 5 月 5 日 下午 5:55:55
let allFives1 = new Date(2005, 4 , 5, 17, 55, 55)

2.1.3 Date.now() 方法

ECMAScript 5 添加了 Date.now() 方法,返回表示调用这个方法时的日期和时间的毫秒数,在不支持它的浏览器可以使用 + 操作符获取 Date 对象的时间戳,也可以达到同样的目的

2.1.4 日期格式化方法

Date 类型还有一些专门用于将日期格式化为字符串的方法,这些方法如下:

  • toDateString() --- 以特定于实现的格式显示星期几、月、日和年
  • toTimeString() --- 以特定于实现的格式显示时、分、秒和时区
  • toLocaleDateString() --- 以特定于地区的格式显示星期几、月、日和年
  • toLocaleTimeString() --- 以特定于实现的格式显示时、分、秒

这些方法的输出字符串格式因浏览器而已

console.log(allFives1.toDateString()) // "Thu May 05 2005"
console.log(allFives1.toTimeString()) // "17:55:55 GMT+0800 (中国标准时间)"
console.log(allFives1.toLocaleDateString()) // "2005/5/5"
console.log(allFives1. toLocaleTimeString()) // "下午5:55:55"

2.1.5 日期/时间组件方法

Date 类型提供了以下方法可以获取Date 类型的详细信息

  • getFullYear() --- 取得四位数的年份
  • getMonth() --- 返回日期中的月份, 其中0表示一月
  • getDate() --- 返回日期月份中的天数(1到31)
  • getDay() --- 返回日期中星期的星期几(0表示星期日,6表示星期六)

以下方法可以设置Date 类型的的信息

  • setDate() --- 设置日期月份中的天数,如果传入的值大于该月中应有的天数,则增加月份

详细API可查阅 MDN

2.1.6 获取数据

当天年月信息

let date = new Date() //新建Date() 类型对象
let year = date.getFullYear() // 年
let month = date.getMonth() // 月
let day = date.getDate() // 日

上个月有多少天

有上面对 Date 类型的详细介绍(2.1.2)应该知道如果 new Date(year, month, day) 如果年月信息是必须填入的,而后面的信息不填,则天数默认为 1 号,那么如果我们填入 0 的话则为上个月月末(~小技巧~) 那么要知道本月末,则只需要设置月份的时候多加个 1 就好了,例如:

// 月份通过 UTC 方式设置需要减一
let newDate1 = new Date(2019, 12)  //  "2020/1/1"
//我们需要得到二月份天数的话
let newDate1 = new Date(2019, 3, 0) // "2019/2/28"
let MonthEndDate = new Date(year, month + 1, 0) // 本月末
let MonthEndDay = thisMonthEnd.getDate() // 月末号即这个月的天数

一号是周几

date.setDate(1) // 重置时间对象为本月一号
let week = date.getDay() // 这个月一号是周几

3.开发代码

这里就用 Vue 框架来实现一个日历功能

效果展示

3.1 HTML代码

<template>
  <div id="app">
    <div class="calendar">
      <div class="header">
        <p class="data-time">{{dataTime}}</p>
        <p class="data-day">{{dataDay}}</p>
      </div>
      <div class="data-month">
        <p>{{dataMonth}}</p>
      </div>
      <div class="calendar-content data-week">
        <div class="content-item" v-for="(item,index) in week" :key="index">
          <div class="content-item-value">{{item}}</div>
        </div>
      </div>
      <div class="calendar-content">
        <div class="content-item" v-for="(item,index) in day" :key="index">
          <div class="content-item-value">{{item.day}}</div>
          <div class="content-item-state">{{item.state}}</div>
        </div>
      </div>
    </div>
  </div>
</template>

3.2 JS代码

<script>
export default {
  name: "app",
  data() {
    return {
      dataTime: "00:00:00",
      dataDay: "2000年01月01日",
      dataMonth: "2019年三月",
      week: ["日", "一", "二", "三", "四", "五", "六"],
      day: [],
      timer: null
    };
  },
  created() {
    let date = new Date();
    let year = date.getFullYear();
    let month = date.getMonth();
    let today = date.getDate();
    let thisMonthEnd = new Date(year, month + 1, 0); // 本月末
    let thisDay = thisMonthEnd.getDate(); // 月末号即这个月的天数
    month = ++month < 10 ? "0" + month : month;
    today = today < 10 ? "0" + today : today;
    this.dataDay = year + "年" + month + "月" + today + "日";
    this.dataMonth = year + "年" + month + "月";
    this.setTimer();
    this.timer = setInterval(this.setTimer, 1000);
    date.setDate(1); // 重置时间对象为本月一号
    let week = date.getDay(); // 这个月一号是周几
    for (let i = 1; i <= 40; i++) {
      if (i <= week) {
        this.day.push({
          day: ""
        }); // 前几天都是上个月,置空
      } else {
        let day = i - week;
        this.day.push({
          day: day, // 日历号码
          state: day === 8 ? "妇女节" : ""
        });
        if (day === thisDay) {
          break; // 如果填满这个月天数就结束
        }
      }
    }
  },
  methods: {
    setTimer() {
      let date = new Date();
      let hours = date.getHours();
      let mintus = date.getMinutes();
      let seconds = date.getSeconds();
      hours = hours < 10 ? "0" + hours : hours;
      mintus = mintus < 10 ? "0" + mintus : mintus;
      seconds = seconds < 10 ? "0" + seconds : seconds;
      this.dataTime = hours + ":" + mintus + ":" + seconds;
    }
  },
  beforeDestroy() {
    clearInterval(this.timer);
  }
};
</script>

3.3 CSS代码

<style lang="scss">
* {
  margin: 0;
  padding: 0;
}
html,
body,
#app {
  height: 100%;
}
#app {
  width: 455px;
  height: 100%;
  font-size: 16px;
  margin: 0 auto;
  color: #ffffff;
  background: #39a54d;
}
.calendar {
  .header {
    padding: 20px;
    border-bottom: 1px solid #ffffff;
    .data-time {
      font-size: 36px;
    }
    .data-day {
      color: aqua;
    }
  }
  .data-month {
    padding: 20px;
  }
  .calendar-content {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
    padding: 0 3%;
    .content-item {
      width: 13.4%;
      height: 40px;
      margin: 4px 0;
      color: #fff;
      text-align: center;
      .content-item-state {
        color: coral;
      }
    }
  }
}
</style>