基于ant-design-vue的简易农历日历

    最近在用ant-design-vue开发过程中,想使用农历日历,不过现有的Calendar暂时不支持农历日历,于是基于ant-design-vue的组件Calendar,再此基础上进行扩展开发了一个简易的农历日历组件。

    核心思想就是有一个数组记录1900~2049之间每个农历年的信息(这些信息包括每个月的天数、闰月月份、闰月天数),取到公历日期后,计算此日期与公历1900年1月30日0时的差距天数(该天是农历1900年正月初一的前一天),根据差距天数计算出当前日期的农历日期。

    目前仅支持1900~2049年的农历日期,另外当前也比较简易,算法可以继续优化,功能也可以进行扩展,有需要可以再次基础进行扩展。

    下面直接贴出代码,代码就两个文件,一个.vue文件一个.js文件,代码中有注释,逻辑比较清晰。

    js文件:

/**
 * 每一年的农历信息共计存储在5位十六制数中,即20位二进制数中
 * 1111 1111 1111 1111 1111
 * 高3位暂时空置,第4位当该年是闰年时标志该年闰月是大月(30天)还是小月(29天),是大月则标志位为1
 * 低4位用来标志该年闰月是哪一月份
 * 中间12位由高到低依次用来标志该年1~12月份分别是大月(30天)还是小月(29天),是大月则标志位为1
 */
const LUNAR_INFO = [0x04bd8, 0x04ae0, 0x0a570,
  0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
  0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0,
  0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50,
  0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566,
  0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0,
  0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4,
  0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550,
  0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950,
  0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260,
  0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0,
  0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
  0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40,
  0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3,
  0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960,
  0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0,
  0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9,
  0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0,
  0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65,
  0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0,
  0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2,
  0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0];

const LUNAR_MONTH_NUMBER = ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"];
const CHINESE_NUMBER = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
const CHINESE_ZODIAC = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"];
const HEAVENLY_STEMS = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
const EARTHLY_BRANCHES = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
const CHINESE_TEN = ["初", "十", "廿", "卅"];
/**
 * 公历1900年1月30日0时的时间戳,该天是农历1900年正月初一的前一天
 */
const START_DATE_MS = -2206512343000;

/**
 * 最小和最大允许的公历日期范围
 * 农历1900年正与初一和农历1949年腊月廿九
 */
const MIN_LUNAR_DATE_MS = -2206425943000;
const MAX_LUNAR_DATE_MS = 2526480000000;

export default class Lunar {

  constructor(moment) {
    let time = moment.valueOf();
    if (time < MIN_LUNAR_DATE_MS || time >= MAX_LUNAR_DATE_MS) {
      throw new Error("date out of range,the range is 1900-01-31 ~ 2050-01-22");
    }
    //求出和1900年1月30日相差的天数
    let offset = parseInt((time - START_DATE_MS) / 86400000);
    //依次获取每农历年的天数,用offset减去每年天数
    //当offset小于当年总天数时,则当年即为该日期所属年份,剩余offset即为该年的第几天
    let iYear, daysOfYear;
    for (iYear = 1900; iYear < 2050; iYear++) {
      daysOfYear = Lunar.getYearDays(iYear);
      if (offset > daysOfYear) {
        offset -= daysOfYear;
      } else {
        break;
      }
    }
    // 农历年份
    this.year = iYear;

    this.leapMonth = Lunar.getLeapMonth(iYear); // 闰哪个月,1-12
    //用当年的天数offset,逐个减去每月(农历)的天数
    //当offset小于当月总天数时,则当月即为该日期所属月份,求出当天是本月的第几天
    //当该年为闰年是,遍历到闰月月份后,需减去闰月总天数,并标记该月是闰月
    let iMonth, daysOfMonth;
    for (iMonth = 1; iMonth < 13; iMonth++) {
      daysOfMonth = Lunar.getMonthDays(this.year, iMonth);
      if (offset > daysOfMonth) {
        offset -= daysOfMonth;
      } else {
        break;
      }
      //如果当前月份与闰月月份一致时开始处理闰月数据
      if (iMonth === this.leapMonth) {
        daysOfMonth = Lunar.getLeapMonthDays(this.year);
        if (offset > daysOfMonth) {
          offset -= daysOfMonth;
        } else {
          this.isLeapMonth = true;
          break;
        }
      }
    }
    this.month = iMonth;
    this.day = offset;
  }

  /**
   * 获取农历年份
   * @author bawy
   * @date 2019/11/4 22:56
   * @return 农历年份
   */
  getLunarYear() {
    return this.year;
  }

  /**
   * 获取农历月份
   * @author bawy
   * @date 2019/11/4 22:56
   * @return 农历月份
   */
  getLunarMonth() {
    return this.month;
  }

  /**
   * 获取农历天数
   * @author bawy
   * @date 2019/11/4 22:56
   * @return 农历天数
   */
  getLunarDay() {
    return this.day;
  }

  /**
   * 获取中文农历日期
   * @author bawy
   * @date 2019/11/7 0:51
   * @return 中文农历日期
   */
  getChineseLunarDay() {
    if (this.day === 1) {
      return (this.isLeapMonth === true ? '闰' :'') + LUNAR_MONTH_NUMBER[this.month - 1] + '月';
    }
    return Lunar.getChinaDayString(this.day);
  }

  /**
   * 是否闰年
   * @author bawy
   * @date 2019/11/4 22:55
   * @return 是闰年返回true
   */
  isLeap() {
    return this.leapMonth > 0;
  }

  /**
   * 当前日期所属月份是否为闰月
   * @author bawy
   * @date 2019/11/4 22:55
   * @return 是则返回true
   */
  isLeapMonth() {
    return this.isLeapMonth === true;
  }

  /**
   * 获取当前日期所属年份生肖
   * @author bawy
   * @date 2019/11/4 22:55
   * @return 生肖
   */
  getChinesZodiac() {
    return Lunar.getChinesZodiac(this.year);
  }

  /**
   * 获取当前农历日期的天干地支
   * @author bawy
   * @date 2019/11/4 22:56
   * @return 当前农历日期的天干地支
   */
  getChineseEra() {
    return Lunar.getChineseEra(this.year);
  }

  /**
   * 获取农历 y 年的总天数
   * 基础天数为12*29=348天
   * 遍历该年份每个月是大月还是小月,大月则在基础天数上增加一天
   * 获取该年闰月天数,加上闰月天数得到该年总天数
   * @author bawy
   * @date 2019/11/4 22:56
   * @param y 年份
   * @return 总天数
   */
  static getYearDays(y) {
    let i, sum = 348;
    for (i = 0x8000; i > 0x8; i >>= 1) {
      if ((LUNAR_INFO[y - 1900] & i) !== 0) {
        sum += 1;
      }
    }
    return (sum + Lunar.getLeapMonthDays(y));
  }

  /**
   * 获取农历 y 年闰月的天数(29或30天),没有闰月返回0
   * 判断该年份是否存在闰月
   * 如果存在闰月,提取闰月标志位,如果为1(大月)则返回30,反之返回29
   * @author bawy
   * @date 2019/11/4 22:56
   * @param y 年份
   * @return 闰月的天数
   */
  static getLeapMonthDays(y) {
    if (Lunar.getLeapMonth(y) === 0) {
      return 0;
    } else {
      if ((LUNAR_INFO[y - 1900] & 0x10000) === 0) {
        return 29;
      } else {
        return 30;
      }
    }
  }

  /**
   * 获取农历 y 年闰哪个月1-12,没闰月返回0
   * 取出指定年份信息的后四位,对应的值即为闰月的月份
   * @author bawy
   * @date 2019/11/4 22:57
   * @param y 年份
   * @return 闰月月份
   */
  static getLeapMonth(y) {
    return LUNAR_INFO[y - 1900] & 0xf;
  }

  /**
   * 获取指定农历 y 年 m 月的总天数(29或30天)
   * 提取指定年份信息中指定月份的标志位,标志位为1则返回30否则返回29
   * @author bawy
   * @date 2019/11/4 22:57
   * @param y 年份
   * @param m 月份
   * @return 该年该月总天数
   */
  static getMonthDays(y, m) {
    if ((LUNAR_INFO[y - 1900] & (0x10000 >> m)) === 0) {
      return 29;
    } else {
      return 30;
    }
  }

  /**
   * 获取指定农历年份对应的生肖
   * @author bawy
   * @date 2019/11/4 22:57
   * @param y 农历年份
   * @return 生肖
   */
  static getChinesZodiac(y) {
    return CHINESE_ZODIAC[(y - 4) % 12];
  }

  /**
   * 获取指定农历年份的天干地支
   * 1864年是甲子年,作为0坐标,根据偏移量计算天干地支
   * @author bawy
   * @date 2019/11/4 22:57
   * @param y 农历年份
   * @return 天干地支
   */
  static getChineseEra(y) {
    let num = y - 1864;
    return (HEAVENLY_STEMS[num % 10] + EARTHLY_BRANCHES[num % 12]);
  }

  /**
   * 指定日期转为中文
   * @author bawy
   * @date 2019/11/4 22:57
   * @param day 日期天数
   * @return 中文日期
   */
  static getChinaDayString(day) {
    if (day > 30) {
      throw new Error("the lunar month most has 30 days");
    }
    if (day === 10) {
      return "初十";
    } else {
      let n = day % 10 === 0 ? 9 : day % 10 - 1;
      return CHINESE_TEN[parseInt(day / 10)] + CHINESE_NUMBER[n];
    }
  }
}

    vue文件:





 

你可能感兴趣的:(基于ant-design-vue的简易农历日历)