日历或者时间选择组件数据源 支持节假日

73 阅读4分钟
// 节假日数据本地化读取
// Holidays and Festivals http://timor.tech/api/holiday/year/2023/
// ./holidaysAndFestivalsData/2025.json (从以上网址按年份命名放置在holidaysAndFestivalsData文件夹)
const importAll = (r) => {
  const subChildrens = {};
  r.keys().forEach((key) => {
    subChildrens[key.replace(/[^0-9]/g, '')] = r(key).default || r(key);
  });
  return subChildrens;
};
export default importAll(require.context('./holidaysAndFestivalsData', false, /(.js)|(.json)$/));
// 节假日数据读入
import holidaysAndFestivalsData from './readFile.js';


// 日历或日期选择插件数据来源以及操作映射
export const chunk = (arr, size) => {
  return arr.length
    ? arr.reduce(
      (res, cur) => (res[res.length - 1].length < size ? res[res.length - 1].push(cur) : res.push([cur]), res),
      [[]]
    )
    : [];
};
export const classNamesPrefix = (preFix, classNames) => {
  return classNames.split(' ').map((item) => `${preFix}${item}`);
};
export const classNames = (...args) => {
  const classes = [];
  const seen = new Set();
  // 反向遍历确保后者优先
  args.reverse().forEach(arg => {
    if (!arg) return;

    if (typeof arg === 'string' || typeof arg === 'number') {
      arg.toString().split(' ').forEach(c => {
        if (c && !seen.has(c)) {
          seen.add(c);
          classes.unshift(c);  // 添加到开头保持顺序
        }
      });
    } 
    else if (Array.isArray(arg)) {
      const result = classNames(...arg);
      result.split(' ').forEach(c => {
        if (c && !seen.has(c)) {
          seen.add(c);
          classes.unshift(c);
        }
      });
    }
    else if (typeof arg === 'object') {
      for (const key in arg) {
        if (arg[key] && !seen.has(key)) {
          seen.add(key);
          classes.unshift(key);
        }
      }
    }
  });
  return classes.join(' ');
};
// 农历数据源
export const lunarCalendar = {
  /* 源数据说明:
   *   lunarYear数据来自香港天文台提供的源数据反向推导得出,其中201项数据分别对应1900-2100年。
   *   示例: 2021年 -- 0x06aa0
   *   ╭-------┰-------┰-------┰-------┰--------╮
   *   ┆ 0000  ┆ 0110  ┆ 1010  ┆ 1010  ┆ 0000   ┆
   *   ┠-------╊-------╊-------╊-------╊--------┨
   *   ┆ 20-17 ┆ 16-12 ┆ 12-9  ┆  8-5  ┆  4-1   ┆
   *   ╰-------┸-------┸-------┸-------┸--------╯
   *   1-4: 表示当年有无闰年,有的话,为闰月的月份,没有的话,为0。 2021年无闰月
   *   5-16:为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。从1月到12月对应的是第16位到第5位,2021年各月天数[29,30,30,29,30,29,30,29,30,29,30,29]
   *   17-20: 表示闰月是大月还是小月,仅当存在闰月的情况下有意义。(0/1,即闰大/小月)
   */

  lunarYears: [
    0x04bd8,
    // 1901-2000
    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, 0x16a95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550, 0x15355,
    0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260,
    0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0,
    0x195a6, 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970,
    0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960,
    // 2001-2100
    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, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, 0x14b63, 0x09370, 0x049f8,
    0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, 0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50,
    0x05d55, 0x056a0, 0x0a6d0, 0x055d4, 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0,
    0x052b0, 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, 0x0e968, 0x0d520,
    0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, 0x0d520,
  ],
  // ['月','正','一','二','三','四','五','六','七','八','九','十','冬','腊'];
  ChinaMonths: [
    '\u6708',
    '\u6b63',
    '\u4e8c',
    '\u4e09',
    '\u56db',
    '\u4e94',
    '\u516d',
    '\u4e03',
    '\u516b',
    '\u4e5d',
    '\u5341',
    '\u51ac',
    '\u814a',
  ],
  // ['日','一','二','三','四','五','六','七','八','九','十']
  ChinaDay: [
    '\u65e5',
    '\u4e00',
    '\u4e8c',
    '\u4e09',
    '\u56db',
    '\u4e94',
    '\u516d',
    '\u4e03',
    '\u516b',
    '\u4e5d',
    '\u5341',
  ],
  // ['初','十','廿','卅','闰']
  ChinaElement: ['\u521d', '\u5341', '\u5eff', '\u5345', '\u95f0'],

  // 农历日中文显示,参数日期day
  toChinaDay(day) {
    let str = '';
    switch (day) {
      case 10:
        str = '\u521d\u5341';
        break; // "初十"
      case 20:
        str = '\u5eff\u5341';
        break; // "廿十"
      case 30:
        str = '\u5345\u5341';
        break; // "卅十"
      default:
        str = this.ChinaElement[Math.floor(day / 10)] + this.ChinaDay[day % 10];
    }
    return str;
  },
  // 农历月初一中文月显示(如农历二月初一 -> 二月,农历闰四月初一 ->闰四月)
  toChinaMonth(month, isleap) {
    isleap = isleap || false;
    return isleap
      ? this.ChinaElement[4] + this.ChinaMonths[month] + this.ChinaMonths[0]
      : this.ChinaMonths[month] + this.ChinaMonths[0];
  },
  nowInfo() {
    const now = new Date();
    return {
      y: now.getFullYear(),
      m: now.getMonth() + 1,
      d: now.getDate(),
    };
  },
  // 某年农历闰月月份
  leapMonth(year) {
    year = year || this.nowInfo().y;
    return this.lunarYears[year - 1900] & 0xf;
  },
  // 某年农历闰月天数
  leapDays(year) {
    year = year || this.nowInfo().y;
    if (this.leapMonth(year)) {
      return this.lunarYears[year - 1900] & 0x10000 ? 30 : 29;
    }
    return 0;
  },
  // 某年份农历各月天数
  lunarMonthDays(year) {
    year = year || this.nowInfo().y;
    const lunarYear = this.lunarYears[year - 1900];
    const monthDays = [];
    for (let i = 4; i < 16; i++) {
      const monthDay = (lunarYear >> i) & 0x1 ? 30 : 29;
      monthDays.push(monthDay);
    }
    monthDays.reverse();
    // 添加闰月
    const leapM = this.leapMonth(year);
    if (leapM) monthDays.splice(leapM, 0, this.leapDays(year));
    return monthDays;
  },
  // 某年农历天数
  lunarYearDays(year) {
    year = year || this.nowInfo().y;
    let num = 0;
    this.lunarMonthDays(year).forEach((item) => {
      num += item;
    });
    return num;
  },
  solarToLunar(y, m, d) {
    if (y < 1901 || y > 2100) return -1;
    let date;
    if (!y) {
      date = new Date();
    } else {
      date = new Date(y, m - 1, d);
    }

    // 参照日期 1901-02-19 正月初一
    let offset = (Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - Date.UTC(1901, 1, 19)) / 86400000;
    let temp = 0;
    let i;
    for (i = 1901; i < 2101 && offset > 0; i++) {
      temp = this.lunarYearDays(i);
      offset -= temp;
    }
    if (offset < 0) {
      offset += temp;
      i--;
    }

    // 农历年、月、日
    let isLeap = false;
    let j = 1;
    const monthDays = this.lunarMonthDays(i);
    const leapM = this.leapMonth(i);

    if (offset > 0) {
      for (j = 0; j < monthDays.length && offset > 0; j++) {
        temp = monthDays[j];
        offset -= temp;
      }
      if (offset == 0) {
        j++;
      }
      if (offset < 0) {
        offset += temp;
      }
    } else {
      // 补偿公历1901年2月的农历信息
      if (offset == -23) {
        return {
          holidaysAndFestivalsData:
            holidaysAndFestivalsData[y]?.holiday[`${calendar.format(`${y}-${m}=${d}`, 'MM-DD')}`],
          lunarY: i,
          lunarM: 12,
          lunarMCn: this.toChinaMonth(j, false),
          lunarD: 8,
          lableLunarD: this.toChinaDay(8),
          lunarDCn: this.toChinaDay(8),
          isLeap: false,
        };
      }
    }

    // 矫正闰年月
    if (leapM) {
      if (j == leapM + 1) {
        isLeap = true;
      }
      if (j >= leapM + 1) {
        j--;
      }
    }
    const curD = ++offset;
    return {
      holidaysAndFestivalsData: holidaysAndFestivalsData[y]?.holiday[`${calendar.format(`${y}-${m}=${d}`, 'MM-DD')}`],
      lunarY: i,
      lunarM: j,
      lunarMCn: this.toChinaMonth(j, isLeap),
      lunarD: curD,
      lableLunarD: curD === 1 ? this.toChinaMonth(j, isLeap) : this.toChinaDay(curD),
      lunarDCn: this.toChinaDay(curD),
      isLeap,
    };
  },
};
// 日历或日期选择插件数据来源工具类
export const calendar = {
  monthsCn: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
  weekDayCn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
  weekDaySimplifiedCn: ['日', '一', '二', '三', '四', '五', '六'],
  getweek(date) {
    // 获取星期几
    date = date ? new Date(date) : new Date();
    return ['一', '二', '三', '四', '五', '六', '日'][date.getDay() - 1]; // week 星期
  },
  getDayWeekCn(index) {
    // 获取星期几
    return this.weekDayCn[index];
  },
  getDayWeekCnSimplified(index) {
    // 获取星期几
    return this.weekDaySimplifiedCn[index];
  },
  getYearMonthDatas(year) {
    return [31, this.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  },
  getMonthCn(month) {
    return this.monthsCn[month - 1];
  },
  getFragmentYear(startYear, endYear, fragmentLength = 10) {
    // fragmentLength固定为10
    // 生成 4*3 12格数据 10年期
    endYear = endYear || startYear + 10;
    const startS = startYear - 1;
    const startE = startYear + fragmentLength;
    return [
      {
        ...getSplitValueData(startS, 'year'),
        label: startS,
        alt: startS,
        curType: 'pre',
        cur: false,
      },
      ...new Array(endYear - startYear).fill(0).map((_, index) => {
        const startC = startYear + index;
        return {
          ...getSplitValueData(startC, 'year'),
          label: startC,
          alt: startC,
          cur: true,
        };
      }),
      {
        ...getSplitValueData(startE, 'year'),
        label: startE,
        alt: startE,
        curType: 'next',
        cur: false,
      },
    ];
  },
  getFragmentYears(startYear, fragmentLength = 10, preFragment = 1) {
    // fragmentLength最大10
    // 生成 4*3 12格数据 100年期 10年纪
    let startYearWith = this.getYearStartFragmentYear(startYear) - fragmentLength * preFragment;
    const map = {};
    const list = this.monthsCn.slice(0, fragmentLength + 2).map((_, index) => {
      map[startYearWith] = {
        cur: index > 0 && index < fragmentLength + 1,
        curType: index === 0 ? 'pre' : index === fragmentLength + 1 ? 'next' : '',
        start: startYearWith,
        end: startYearWith + fragmentLength - 1,
        alt: `${startYearWith}-${startYearWith + fragmentLength - 1}`,
        label: `${startYearWith}-${startYearWith + fragmentLength - 1}`,
      };
      const res = map[startYearWith];
      startYearWith += fragmentLength;
      return res;
    });
    return {
      list,
      map,
    };
  },
  getYearStartFragmentYear(year) {
    // 获取year所在的以整十开始的年份
    return Math.floor(year / 10) * 10;
  },
  getToday(toDay) {
    // 获取toDay的数据
    const today = toDay || new Date();
    return {
      year: this.format(today, 'YYYY', true),
      month: this.format(today, 'MM', true),
      day: this.format(today, 'DD', true),
      date: this.format(today, 'YYYY-MM-DD hh:mm:ss'),
    };
  },
  getYearsData(startYear, endYear) {
    const map = {};
    const list = new Array(endYear - startYear + 1).fill(0).map((_, index) => {
      map[startYear + index] = this.getYearData(startYear + index);
      return map[startYear + index];
    });
    return {
      map,
      list,
    };
  },
  getYearData(year) {
    const map = {};
    const list = this.monthsCn.map((_, index) => {
      map[index + 1] = this.getYearMonthData(year, index + 1);
      return map[index + 1];
    });
    return {
      map,
      list,
    };
  },
  getYearMonthData(year, month) {
    // 获取year month 渲染dom所有数据
    month = parseInt(month);
    if (!year || year.toString().length !== 4) {
      const now = new Date();
      year = now.getFullYear();
    }
    if (!month) {
      const now = new Date();
      month = now.getMonth() + 1;
    }
    return this.getMonthData(year, month, this.getMonthHaveDays(year, month));
  },
  getMonthData(year, month, days) {
    // 获取year month 渲染dom数据
    const self = this;
    const firstdayweek = self.getDayWeek(new Date(`${year.toString()}-${month}-${1}`));
    const monthobj = {
      year,
      month,
      monthCn: self.getMonthCn(month),
      monthdata: [],
      monthalldata: [],
      firstdayweek,
      firstdayweekcn: self.getDayWeekCn(firstdayweek),
      firstdayweekcnSimplified: self.getDayWeekCnSimplified(firstdayweek),
    };
    for (let i = 1; i <= days; i++) {
      monthobj.monthdata.push(self.getDayData(year, month, i, true));
    } // 获取year month 月数据
    monthobj.monthalldata = self.getMonthAllData(year, month, JSON.parse(JSON.stringify(monthobj.monthdata)));
    monthobj.renderMonthalldata = chunk(monthobj.monthalldata, 7);
    return monthobj;
  },
  getMonthAllData(year, month, monthdata) {
    // 获取year month 渲染dom 所有数据,5*7 35或6*7格||或7*7格数据
    const self = this;
    const count = self.getCount(monthdata);
    if (monthdata[0].week === 0) {
      // 本月1号为周日,补充下月数据,凑够5*7 35格数据
      const curlength = monthdata.length;
      for (let i = 1; i < count - curlength + 1; i++) {
        monthdata.push(
          self.getDayData(month + 1 > 12 ? year + 1 : year, month + 1 > 12 ? 1 : month + 1, i, false, 'next')
        );
      }
    } else {
      // 本月1号不是周日,补充上月和下月数据,凑够**7 格数据
      const prevmonthdays = self.getMonthHaveDays(month - 1 <= 0 ? year - 1 : year, month - 1 <= 0 ? 12 : month - 1);
      const firstweek = monthdata[0].week;
      for (let i = prevmonthdays; i > prevmonthdays - firstweek; i--) {
        monthdata.unshift(
          self.getDayData(month - 1 <= 0 ? year - 1 : year, month - 1 <= 0 ? 12 : month - 1, i, false, 'pre')
        );
      }
      const curandprevdays = monthdata.length;
      for (let j = 1; j <= count - curandprevdays; j++) {
        monthdata.push(
          self.getDayData(month + 1 > 12 ? year + 1 : year, month + 1 > 12 ? 1 : month + 1, j, false, 'next')
        );
      }
    }
    return monthdata;
  },
  getCount(monthdata) {
    return Math.ceil((monthdata[0].week + monthdata.length) / 7) * 7;
  },
  getDayData(year, month, day, cur, curType) {
    // 获取某天数据
    const self = this;
    const week = self.getDayWeek(new Date(`${year.toString()}-${month}-${day}`));
    const date = `${year}-${month}-${day}`;
    const daydata = {
      date: self.format(date, 'YYYY-MM-DD'),
      dates: self.format(date, 'YYYY-MM-DD hh:mm:ss'),
      quarter: self.format(date, 'Q', true),
      year,
      month,
      day,
      weekcn: self.getDayWeekCn(week),
      weekcnSimplified: self.getDayWeekCnSimplified(week),
      week,
      cur,
      curType,
      label: day,
      alt: self.format(date, 'YYYY年M月D日'),
      ...lunarCalendar.solarToLunar(year, month, day),
    };
    return daydata;
  },
  getMonthHaveDays(year, month) {
    // 获取某月有多少天
    return this.getYearMonthDatas(year)[+month - 1];
  },
  getDayWeek(date) {
    // 获取某一天是星期几 data:new Date("2015-7-12")
    return date.getDay();
  },
  isLeapYear(year) {
    // 是否为闰年
    const cond1 = year % 4 === 0; // 条件1:年份必须要能被4整除
    const cond2 = year % 100 !== 0; // 条件2:年份不能是整百数
    const cond3 = year % 400 === 0; // 条件3:年份是400的倍数
    // 当条件1和条件2同时成立时,就肯定是闰年,所以条件1和条件2之间为“与”的关系。
    // 如果条件1和条件2不能同时成立,但如果条件3能成立,则仍然是闰年。所以条件3与前2项为“或”的关系。
    // 所以得出判断闰年的表达式:
    const cond = (cond1 && cond2) || cond3;
    if (cond) {
      return true;
    }
    return false;
  },
  format(dateS, format, formatToNumber) {
    // example  new Date().format( "当前日期为:YYYY-MM-DD,星期W,为第Q季度,时间为:hh:mm:ss:c")
    const date = new Date(String(dateS) || '');
    const o = {
      'Y+': `${date.getFullYear()}`,
      'M+': date.getMonth() + 1, // month  MM
      'D+': date.getDate(), // day  DD
      'h+': date.getHours(), // hour  hh
      'm+': date.getMinutes(), // minute mm
      's+': date.getSeconds(), // second ss
      'Q+': Math.floor(date.getMonth() / 3) + 1, // quarter 季度 q
      'c+': date.getMilliseconds(), // millisecond 毫秒 c
      W: ['一', '二', '三', '四', '五', '六', '日'][date.getDay() - 1], // week 星期
    };
    for (const k in o) {
      if (new RegExp(`(${k})`).test(format)) {
        format = format.replace(
          RegExp.$1,
          RegExp.$1.length == 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length >= 2 ? 2 : `${o[k]}`.length)
        );
      }
    }
    return formatToNumber ? Number(format) : format;
  },
  distanceTime(someTime, otherTime) {
    //  获取someTime到otherTime或此刻的时间间隔
    let me = this;
    let startDate = new Date(someTime); // 开始时间
    let endDate = otherTime ? new Date(otherTime) : new Date(); // 结束时间
    let type = '-';
    let t = endDate.getTime() - startDate.getTime(); // 时间差
    if (t < 0) {
      t = startDate.getTime() - endDate.getTime();
      type = '';
    }
    let d = 0;
    let h = 0;
    let m = 0;
    let s = 0;
    d = Math.floor(t / 1000 / 3600 / 24);
    h = Math.floor((t / 1000 / 60 / 60) % 24);
    m = Math.floor((t / 1000 / 60) % 60);
    s = Math.floor((t / 1000) % 60);
    const res = {
      startDate: me.format(startDate, 'YYYY-MM-DD hh:mm:ss'),
      endDate: me.format(endDate, 'YYYY-MM-DD hh:mm:ss'),
      type,
      days: d,
      hours: h,
      minutes: m,
      seconds: s,
    };
    res.result = `${d}${h}小时${m}分钟${s}秒`;
    return res;
  },
  getBetweenDates(startDate, engDate) {
    // 获取两个日期之间的datelist
    const me = this;
    const startDateInfo = this.getToday(startDate);
    const endDateInfo = this.getToday(engDate);
    const dateSource = {
      startYear: startDateInfo.year,
      startMonth: startDateInfo.month,
      startDay: startDateInfo.day,
      endYear: endDateInfo.year,
      endMonth: endDateInfo.month,
      endDay: endDateInfo.day,
      startYearMonth: me.getYearMonthDatas(startDateInfo.year),
      endYearMonth: me.getYearMonthDatas(endDateInfo.year),
    };
    const list = [];
    new Array(dateSource.endYear - dateSource.startYear + 1)
      .fill(0)
      .map((_, index) => {
        return +dateSource.startYear + index + 1;
      })
      .forEach((year) => {
        this.monthsCn.reduce((res, _, index) => {
          list.push(...this.getYearMonthData(year, index + 1).monthdata);
          return res;
        }, {});
      });
    // 掐头去尾
    const removeStartLength = dateSource.startYearMonth
      .slice(0, Number(dateSource.startMonth))
      .reduce((res, next, index) => {
        if (index + 1 !== Number(dateSource.startMonth)) {
          res = res + next;
        } else {
          res = res + dateSource.startDay - 1;
        }
        return res;
      }, 0);
    const removeEndLength = dateSource.endYearMonth
      .slice(Number(dateSource.endMonth - 1), 12)
      .reduce((res, next, index) => {
        if (index) {
          res = res + next;
        } else {
          res = res + next - dateSource.endDay;
        }
        return res;
      }, 0);

    return list.splice(removeStartLength, list.length - removeEndLength - removeStartLength);
  },
  getYearOfDay(time) {
    /* 获取某年有多少天 */
    let firstDayYear = this.getFirstDayOfYear(time);
    let lastDayYear = this.getLastDayOfYear(time);
    let numSecond = (new Date(lastDayYear).getTime() - new Date(firstDayYear).getTime()) / 1000;
    return Math.ceil(numSecond / (24 * 3600));
  },
  getFirstDayOfYear(time) {
    /* 获取某年的第一天 */
    let year = new Date(time).getFullYear();
    return year + '-01-01 00:00:00';
  },
  getLastDayOfYear(time) {
    /* 获取某年最后一天 */
    let year = new Date(time).getFullYear();
    return year + '-12-31' + ' 23:59:59';
  },
  getDayOfYear(time) {
    /* 获取某个日期是当年中的第几天 */
    return Math.ceil(
      (new Date(time).getTime() - new Date(this.getFirstDayOfYear(time)).getTime()) / 1000 / (24 * 3600)
    );
  },
  getDayOfYearWeek(time) {
    /* 获取某个日期在这一年的第几周 */
    return Math.ceil(this.getDayOfYear(time) / 7);
  },
};
export default calendar;
// 面板类型选择顺序
export const panelTypeSort = ['century', 'year', 'month', 'date', 'hour', 'minute', 'second'];
// 面板类型渲然数据源映射
export const panelTypeMap = {
  century: {
    // 世纪
    mode: 'century',
    title: '选择世纪',
    cols: 3,
    rows: 4,
    preL: '上一世纪',
    nextR: '下一世纪',
    topToMode: 'century',
    bodyToMode: 'year',
    typeToMode: ['century'],
    preLClick({ startYears, startYear, year }) {
      return {
        startYears: startYears - 100,
        endYears: startYears - 1,
        startYear: startYear - 100,
        endYear: startYear - 91,
      };
    },
    nextRClick({ startYear, startYears, year }) {
      return {
        startYears: startYears + 100,
        endYears: startYears + 199,
        startYear: startYear + 100,
        endYear: startYear + 109,
      };
    },
    bodyClick(_, { start, end }) {
      return {
        startYear: start,
        endYear: end,
        year: start,
        curSelectType: 'year',
      };
    },
    getBodyCellRenderProps(curRenderDataSource, obj) {
      const { year } = curRenderDataSource;
      return {
        ariaDisabled: false,
        ariaSelected: Math.floor(year / 100) * 100 === obj.start,
      };
    },
    getCellRenderClass({ isCalendarPanel }, { cur, curType }, { ariaSelected }) {
      return {
        td: classNames(
          'year-panel-cell',
          ariaSelected && cur && 'year-panel-selected-cell',
          curType === 'pre' && 'year-panel-last-decade-cell',
          curType === 'next' && 'year-panel-next-decade-cell',
          isCalendarPanel && 'calendar-cell'
        ),
        cell: 'year-panel-year',
      };
    },
    renderClass: {
      root: 'picker-container picker-century',
      pnelbox: 'ant-calendar',
      paneContent: 'panel',
      mainpanel: 'decade-panel',
      header: 'decade-panel-header',
      preL: 'decade-panel-prev-century-btn',
      nextR: 'decade-panel-next-century-btn',
      headercenter: 'decade-panel-century',
      headercenterC: 'decade-panel-century-content',
      body: 'decade-panel-body',
      table: 'decade-panel-table',
      tbody: 'decade-panel-tbody',
    },
    getRenderData({ startYears }) {
      const { list, map } = calendar.getFragmentYears(startYears);
      return {
        thead: null,
        data: list,
        map,
        tbody: chunk(list, 3),
      };
    },
  },
  year: {
    mode: 'year',
    title: '选择年代',
    cols: 3,
    rows: 4,
    preL: '上一年代',
    nextR: '下一年代',
    topToMode: 'century',
    bodyToModemonth: 'month',
    bodyTodate: 'date',
    bodyToMode: 'date',
    typeToMode: ['century', 'year'],
    format: 'YYYY', // MM
    pattern: /^(2[0-9]{3})|(19[0-9]{2})$/, // 1999-2999
    preLClick({ startYear, endYear }) {
      startYear -= 10;
      const startYears = Math.floor(startYear / 100) * 100;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: endYear - 10,
      };
    },
    nextRClick({ startYear, endYear }) {
      startYear += 10;
      const startYears = Math.floor(startYear / 100) * 100;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: endYear + 10,
      };
    },
    bodyClick(curRenderDataSource, obj, tprops) {
      const { type } = curRenderDataSource;
      const { year } = obj;
      if (panelTypeMap[type].typeToMode.includes('date')) {
        return {
          year,
          curSelectType: 'date',
        };
      }
      if (type === 'month') {
        return {
          year,
          curSelectType: 'month',
        };
      }
      return panelTypeMap.date.bodyClick(curRenderDataSource, obj, tprops);
    },
    getBodyCellRenderProps(curRenderDataSource, obj, { disabledDate }) {
      const { year, type, result } = curRenderDataSource;
      const { date, cur } = obj;
      if (type === 'year') {
        return {
          ariaDisabled: disabledDate(obj, curRenderDataSource) || !cur,
          ariaSelected: result.values.includes(date),
        };
      }
      return {
        ariaDisabled: false,
        ariaSelected: year === obj.year,
      };
    },
    getCellRenderClass({ isCalendarPanel }, obj, { ariaSelected }) {
      const { cur, curType } = obj;
      return {
        td: classNames(
          'year-panel-cell',
          getIsToday(obj, 'year') && 'today',
          curType === 'pre' && 'year-panel-last-decade-cell',
          curType === 'next' && 'year-panel-next-decade-cell',
          ariaSelected && cur && 'year-panel-selected-cell',
          isCalendarPanel && 'calendar-cell'
        ),
        cell: 'year-panel-year',
      };
    },
    renderClass: {
      root: 'picker-container pickercentury',
      pnelbox: 'ant-calendar',
      paneContent: 'panel',
      mainpanel: 'year-panel',
      header: 'year-panel-header',
      preL: 'year-panel-prev-decade-btn',
      nextR: 'year-panel-next-decade-btn',
      headercenter: 'year-panel-decade-select',
      headercenterC: 'year-panel-decade-select-content',
      th: 'column-header',
      thCell: 'column-header-inner',
      body: 'year-panel-body',
      table: 'year-panel-table',
      tbody: 'year-panel-tbody',
    },
    getRenderData({ startYear }) {
      const data = calendar.getFragmentYear(startYear);
      return {
        thead: null,
        data,
        tbody: chunk(data, 3),
      };
    },
  },
  month: {
    mode: 'month',
    title: '选择月份',
    cols: 3,
    rows: 4,
    preL: '上一年份',
    nextR: '下一年份',
    topToMode: 'year',
    bodyToMode: 'date',
    typeToMode: ['century', 'year', 'month'],
    format: 'YYYY-MM', // MM
    pattern: /^((0?[1-9])|(1[0-2]))$/, // 0-12
    preLClick({ year }) {
      year -= 1;
      const startYears = Math.floor(year / 100) * 100;
      const startYear = Math.floor(year / 10) * 10;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: startYear + 9,
        year,
      };
    },
    nextRClick({ year }) {
      year += 1;
      const startYears = Math.floor(year / 100) * 100;
      const startYear = Math.floor(year / 10) * 10;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: startYear + 9,
        year,
      };
    },
    bodyClick(curRenderDataSource, obj, tprops) {
      const { type } = curRenderDataSource;
      const { month } = obj;
      if (panelTypeMap[type].typeToMode.includes('date')) {
        return {
          month,
          curSelectType: 'date',
        };
      }
      return panelTypeMap.date.bodyClick(curRenderDataSource, obj, tprops);
    },
    getBodyCellRenderProps(curRenderDataSource, obj, { disabledDate }) {
      const { month, type, result } = curRenderDataSource;
      const { date } = obj;
      if (type === 'month') {
        return {
          ariaDisabled: disabledDate(obj, curRenderDataSource),
          ariaSelected: result.values.includes(date),
        };
      }
      return {
        ariaDisabled: false,
        ariaSelected: month === obj.month,
      };
    },
    renderClass: {
      root: 'picker-container pickercentury',
      pnelbox: 'ant-calendar',
      paneContent: 'panel',
      mainpanel: 'month-panel',
      header: 'month-panel-header',
      preL: 'month-panel-prev-year-btn',
      nextR: 'month-panel-next-year-btn',
      headercenter: 'year-panel-decade-select',
      headercenterC: 'year-panel-decade-select-content',
      body: 'month-panel-body',
      table: 'month-panel-table',
      tbody: 'month-panel-tbody',
    },
    getCellRenderClass({ isCalendarPanel }, obj, { ariaSelected }) {
      return {
        td: classNames(
          'month-panel-cell',
          getIsToday(obj, 'month') && 'today',
          ariaSelected && 'month-panel-selected-cell',
          isCalendarPanel && 'calendar-cell'
        ),
        cell: 'month-panel-month',
      };
    },
    getRenderData({ year }) {
      const data = calendar.monthsCn.map((item, index) => ({
        label: item,
        alt: `${year}年${index + 1}月`,
        ...getSplitValueData(`${year}-${index + 1}`, 'month'),
      }));
      return {
        thead: null,
        data,
        tbody: chunk(data, 3),
      };
    },
  },
  date: {
    mode: 'date',
    title: '选择日期',
    cols: 7,
    // preL: '上一年 (Control键加左方向键)',
    // pre: '上个月 (翻页上键)',
    // next: '下个月 (翻页下键)',
    // nextR: '下一年 (Control键加右方向键)',
    preL: '上一年',
    pre: '上个月',
    next: '下个月',
    nextR: '下一年',
    topToMode_pre: 'year',
    bodyToMode_next: 'month',
    format: 'YYYY-MM-DD',
    typeToMode: ['century', 'year', 'month', 'date'],
    pattern: /^((2[0-9]{3})|(19[0-9]{2}))-((0[1-9])|(1[0-2]))-((0[1-9])|([1-2][0-9])|(3[0-1]))$/, // "1900-01-01"---"2999-12-31"
    preLClick({ year }) {
      year -= 1;
      const startYears = Math.floor(year / 100) * 100;
      const startYear = Math.floor(year / 10) * 10;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: startYear + 9,
        year,
      };
    },
    nextRClick({ year }) {
      year += 1;
      const startYears = Math.floor(year / 100) * 100;
      const startYear = Math.floor(year / 10) * 10;
      return {
        startYears,
        endYears: startYears + 99,
        startYear,
        endYear: startYear + 9,
        year,
      };
    },
    preClick({ month, year }) {
      return {
        month: month === 1 ? 12 : month - 1,
        year: month === 1 ? year - 1 : year,
      };
    },
    nextClick({ month, year }) {
      return {
        month: month === 12 ? 1 : month + 1,
        year: month === 12 ? year + 1 : year,
      };
    },
    bodyClick(obj, item, { mode, isCalendarPanel }) {
      if (isCalendarPanel) {
        return {};
      }
      const { map, list, values } = obj.result;
      const { date, year, month, day } = item;
      if (mode !== 'multiple' && mode !== 'tags') {
        const resOne = {
          index: 0,
          date,
          year,
          month,
          day,
        };
        obj.result = {
          map: {
            [date]: resOne,
          },
          list: [resOne],
          values: [date],
          str: date,
        };
        return obj;
      }
      if (map[date]) {
        list.splice(
          list.findIndex((item) => item.date === date),
          1
        );
        values.splice(
          values.findIndex((item) => item === date),
          1
        );
        delete map[date];
      } else {
        map[date] = {
          date,
          year,
          month,
          day,
        };
        list.push(map[date]);
        values.push(date);
      }
      obj.result.str = values.join(',');
      return obj;
    },
    getBodyCellRenderProps(curRenderDataSource, obj, { disabledDate }) {
      const { result } = curRenderDataSource;
      const { date, cur } = obj;
      return {
        ariaDisabled: disabledDate(obj, curRenderDataSource) || !cur,
        ariaSelected: result.values.includes(date),
      };
    },
    renderClass: {
      root: 'picker-container picker-century',
      pnelbox: 'ant-calendar',
      paneContent: 'panel',
      mainpanel: 'date-panel',
      header: 'header',
      preL: 'prev-year-btn',
      pre: 'prev-month-btn',
      headercenter: 'ym-select',
      headercenterL: 'year-select',
      headercenterR: 'month-select',
      next: 'next-month-btn',
      nextR: 'next-year-btn',
      body: 'body',
      table: 'table',
      tbody: 'tbody',
    },
    getCellRenderClass({ isCalendarPanel }, obj, { ariaSelected }) {
      const { cur, curType } = obj;
      const res = {
        td: classNames(
          'cell',
          getIsToday(obj, 'date') && 'today',
          ariaSelected && cur && 'selected-date selected-day',
          curType === 'pre' && 'last-month-cell last-day-of-month',
          curType === 'next' && 'last-month-cell next-day-of-month'
        ),
        cell: classNames(isCalendarPanel ? 'calendar' : 'date'),
      };
      return res;
    },
    getRenderData({ year, month, isCalendarPanel }) {
      const res = calendar.getYearMonthData(year, month);
      return {
        thead: calendar.weekDaySimplifiedCn.map((item, index) => ({
          value: index === 0 ? 7 : index,
          label: isCalendarPanel ? calendar.weekDayCn[index] : item,
          month: item,
        })),
        data: res,
        tbody: res.renderMonthalldata,
      };
    },
  },
  hour: {
    mode: 'hour',
    title: '时',
  },
  minute: {
    mode: 'minute',
    title: '分',
  },
  second: {
    mode: 'second',
    title: '秒',
  },
};
// 获取初始value数据
export const getSplitValueData = (value, type) => {
  const { year, month, day } = calendar.getToday(String(value));
  return {
    year,
    month,
    day,
    date: calendar.format(value, panelTypeMap[type].format),
  };
};
export const getDefaultValueMap = (value, mode, type) => {
  value = mode === 'multiple' || mode === 'tags' ? (value && Array.isArray(value) ? value : []) : value ? [value] : [];
  return value.reduce(
    (res, next) => {
      res.map[next] = getSplitValueData(next, type);
      res.map[next].index = res.list.length;
      res.list.push(res.map[next]);
      res.values.push(res.map[next].date);
      res.str = res.str ? `${res.str},${res.map[next].date}` : res.map[next].date;
      return res;
    },
    {
      values: [],
      str: '',
      map: {},
      list: [],
    }
  );
};
export const getIsToday = ({ date }, type) => {
  const today = calendar.getToday();
  const format = (type && panelTypeMap[type]?.format) || 'YYYY-MM-DD';
  return calendar.format(today.date, format) === calendar.format(date, format);
};