|
@@ -7,25 +7,46 @@ import ical, {
|
|
import generate from "../src/holidays/generate";
|
|
import generate from "../src/holidays/generate";
|
|
import dayjs, { Dayjs } from "../src/utils/dayjs";
|
|
import dayjs, { Dayjs } from "../src/utils/dayjs";
|
|
import { Holiday } from "../src/holidays/arrangement";
|
|
import { Holiday } from "../src/holidays/arrangement";
|
|
|
|
+import { createHash } from 'crypto';
|
|
|
|
|
|
-const { holidays, workdays, inLieuDays } = generate();
|
|
|
|
|
|
+enum DayType {
|
|
|
|
+ Workday = 1,
|
|
|
|
+ Holiday = 2,
|
|
|
|
+}
|
|
|
|
|
|
|
|
+const { holidays, workdays } = generate();
|
|
const endYear = Number(Object.keys(holidays)[0].slice(0, 4))
|
|
const endYear = Number(Object.keys(holidays)[0].slice(0, 4))
|
|
|
|
|
|
-// 创建一个新的日历
|
|
|
|
-const cal = ical({
|
|
|
|
- name: "中国节假日",
|
|
|
|
- timezone: "Asia/Shanghai",
|
|
|
|
- prodId: { company: "yaavi.me", product: "Chinese Days", language: "CN" },
|
|
|
|
-});
|
|
|
|
-
|
|
|
|
-// 设置日历描述
|
|
|
|
-cal.description(`${endYear - 2}~${endYear}年中国节假日日历`);
|
|
|
|
-
|
|
|
|
-// 添加时区信息
|
|
|
|
-cal.timezone({
|
|
|
|
- name: "Asia/Shanghai",
|
|
|
|
- generator: (tzid) => `
|
|
|
|
|
|
+const buildIcal = (language: 'CN' | 'EN') => {
|
|
|
|
+ const info = language == 'CN' ? {
|
|
|
|
+ name: '中国节假日',
|
|
|
|
+ desc: `${endYear - 2}~${endYear}年中国节假日日历`,
|
|
|
|
+ location: '北京',
|
|
|
|
+ categories: '节假日',
|
|
|
|
+ holiday: '休',
|
|
|
|
+ workday: '班'
|
|
|
|
+ } : {
|
|
|
|
+ name: 'Chinese Public Holidays',
|
|
|
|
+ desc: `Calendar of Chinese Public Holidays for ${endYear - 2}~${endYear} Years`,
|
|
|
|
+ location: 'Beijing',
|
|
|
|
+ categories: 'Holidays',
|
|
|
|
+ holiday: 'Holiday',
|
|
|
|
+ workday: 'Workday',
|
|
|
|
+ }
|
|
|
|
+ // 创建一个新的日历
|
|
|
|
+ const cal = ical({
|
|
|
|
+ name: info.name,
|
|
|
|
+ timezone: "Asia/Shanghai",
|
|
|
|
+ prodId: { company: "yaavi.me", product: "Chinese Days", language },
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 设置日历描述
|
|
|
|
+ cal.description(info.desc);
|
|
|
|
+
|
|
|
|
+ // 添加时区信息
|
|
|
|
+ cal.timezone({
|
|
|
|
+ name: "Asia/Shanghai",
|
|
|
|
+ generator: (tzid) => `
|
|
BEGIN:VTIMEZONE
|
|
BEGIN:VTIMEZONE
|
|
TZID:${tzid}
|
|
TZID:${tzid}
|
|
X-LIC-LOCATION:${tzid}
|
|
X-LIC-LOCATION:${tzid}
|
|
@@ -36,92 +57,103 @@ TZNAME:CST
|
|
DTSTART:19700101T000000
|
|
DTSTART:19700101T000000
|
|
END:STANDARD
|
|
END:STANDARD
|
|
END:VTIMEZONE`,
|
|
END:VTIMEZONE`,
|
|
-});
|
|
|
|
-
|
|
|
|
-const buildHolidays = (
|
|
|
|
- year: number,
|
|
|
|
- days: Record<string, Holiday>,
|
|
|
|
- mark: "(休)" | "(班)" | string
|
|
|
|
-) => {
|
|
|
|
- // 合并相同节日的日期
|
|
|
|
- const mergedHolidays: Record<
|
|
|
|
- string,
|
|
|
|
- {
|
|
|
|
- chineseName: string;
|
|
|
|
- dates: string[];
|
|
|
|
- }
|
|
|
|
- > = {};
|
|
|
|
-
|
|
|
|
- for (const [date, info] of Object.entries(days)) {
|
|
|
|
- if (date.startsWith(String(year))) {
|
|
|
|
- const [name, chineseName] = info.split(",");
|
|
|
|
- if (!mergedHolidays[name]) {
|
|
|
|
- mergedHolidays[name] = {
|
|
|
|
- chineseName,
|
|
|
|
- dates: [],
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
- mergedHolidays[name].dates.push(date);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 检查日期是否连续的函数
|
|
|
|
- const areDatesContinuous = (date1: Dayjs, date2: Dayjs) => {
|
|
|
|
- return dayjs(date2).diff(dayjs(date1), "day") === 1;
|
|
|
|
- };
|
|
|
|
|
|
+ });
|
|
|
|
|
|
- for (const [name, details] of Object.entries(mergedHolidays)) {
|
|
|
|
- const { chineseName, dates } = details;
|
|
|
|
- dates.sort(); // 确保日期按顺序排列
|
|
|
|
-
|
|
|
|
- let startDate = dayjs(dates[0]);
|
|
|
|
- let endDate = startDate;
|
|
|
|
-
|
|
|
|
- for (let i = 1; i < dates.length; i++) {
|
|
|
|
- const currentDate = dayjs(dates[i]);
|
|
|
|
-
|
|
|
|
- if (areDatesContinuous(endDate, currentDate)) {
|
|
|
|
- endDate = currentDate;
|
|
|
|
- } else {
|
|
|
|
- // 添加当前事件
|
|
|
|
- cal.createEvent({
|
|
|
|
- start: startDate.toDate(),
|
|
|
|
- end: endDate.add(1, "day").toDate(),
|
|
|
|
- description: `${chineseName}放假: 共${mark === '(休)' ? '休息' : '需补班'} ${dates.length} 天`,
|
|
|
|
- status: ICalEventStatus.CONFIRMED,
|
|
|
|
- summary: `${chineseName}${mark}`,
|
|
|
|
- transparency: ICalEventTransparency.TRANSPARENT,
|
|
|
|
- allDay: true,
|
|
|
|
- class: ICalEventClass.PUBLIC,
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // 重置开始和结束日期
|
|
|
|
- startDate = currentDate;
|
|
|
|
- endDate = currentDate;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ const calAddDays = (startDate: Dayjs, endDate: Dayjs, name: string, mark: DayType) => {
|
|
|
|
+ // 基于事件生成稳定的哈希值
|
|
|
|
+ const generateStableUUID = () => {
|
|
|
|
+ const hash = createHash('sha256');
|
|
|
|
+ hash.update(name + startDate.toDate().toISOString() + endDate.toDate().toISOString() + mark);
|
|
|
|
+ return hash.digest('hex');
|
|
|
|
+ };
|
|
|
|
|
|
- // 添加最后一个事件
|
|
|
|
cal.createEvent({
|
|
cal.createEvent({
|
|
start: startDate.toDate(),
|
|
start: startDate.toDate(),
|
|
end: endDate.add(1, "day").toDate(),
|
|
end: endDate.add(1, "day").toDate(),
|
|
- description: `${chineseName}放假: 共${mark === '(休)' ? '休息' : '需补班'} ${dates.length} 天`,
|
|
|
|
|
|
+ description: `${mark === DayType.Holiday ? info.holiday : info.workday}`,
|
|
status: ICalEventStatus.CONFIRMED,
|
|
status: ICalEventStatus.CONFIRMED,
|
|
- summary: `${chineseName}${mark}`,
|
|
|
|
|
|
+ summary: `${name}(${mark === DayType.Holiday ? info.holiday : info.workday})`,
|
|
|
|
+ location: info.location,
|
|
transparency: ICalEventTransparency.TRANSPARENT,
|
|
transparency: ICalEventTransparency.TRANSPARENT,
|
|
allDay: true,
|
|
allDay: true,
|
|
class: ICalEventClass.PUBLIC,
|
|
class: ICalEventClass.PUBLIC,
|
|
|
|
+ categories: [{ name: info.categories }],
|
|
|
|
+ x: [
|
|
|
|
+ { key: 'X-MICROSOFT-CDO-ALLDAYEVENT', value: 'TRUE' },
|
|
|
|
+ { key: 'X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT', value: 'TRUE' },
|
|
|
|
+ ...(
|
|
|
|
+ mark == DayType.Holiday
|
|
|
|
+ ? [{ key: 'X-APPLE-SPECIAL-DAY', value: 'WORK-HOLIDAY' }]
|
|
|
|
+ : mark == DayType.Workday
|
|
|
|
+ ? [{ key: 'X-APPLE-SPECIAL-DAY', value: 'ALTERNATE-WORKDAY' }]
|
|
|
|
+ : []
|
|
|
|
+ ),
|
|
|
|
+ { key: 'X-APPLE-UNIVERSAL-ID', value: generateStableUUID() }
|
|
|
|
+ ]
|
|
});
|
|
});
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-for (let i = endYear; i > endYear - 3; i--) {
|
|
|
|
- buildHolidays(i, holidays, "(休)");
|
|
|
|
- buildHolidays(i, workdays, "(班)");
|
|
|
|
|
|
+ const buildHolidays = (
|
|
|
|
+ years: number[],
|
|
|
|
+ days: Record<string, Holiday>,
|
|
|
|
+ mark: DayType
|
|
|
|
+ ) => {
|
|
|
|
+ // 合并相同节日的日期
|
|
|
|
+ const mergedHolidays: Record<string, { chineseName: string; dates: string[] }> = {};
|
|
|
|
+
|
|
|
|
+ for (const [date, info] of Object.entries(days)) {
|
|
|
|
+ if (years.includes(Number(date.slice(0, 4)))) {
|
|
|
|
+ const [name, chineseName] = info.split(",");
|
|
|
|
+ if (!mergedHolidays[name]) {
|
|
|
|
+ mergedHolidays[name] = {
|
|
|
|
+ chineseName,
|
|
|
|
+ dates: [],
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ mergedHolidays[name].dates.push(date);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 检查日期是否连续的函数
|
|
|
|
+ const areDatesContinuous = (date1: Dayjs, date2: Dayjs) => {
|
|
|
|
+ return dayjs(date2).diff(date1, "day") === 1;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ for (const [name, { chineseName, dates }] of Object.entries(mergedHolidays)) {
|
|
|
|
+ dates.sort(); // 确保日期按顺序排列
|
|
|
|
+
|
|
|
|
+ let startDate = dayjs(dates[0]);
|
|
|
|
+ let endDate = startDate;
|
|
|
|
+
|
|
|
|
+ for (let i = 1; i < dates.length; i++) {
|
|
|
|
+ const currentDate = dayjs(dates[i]);
|
|
|
|
+
|
|
|
|
+ if (areDatesContinuous(endDate, currentDate)) {
|
|
|
|
+ endDate = currentDate;
|
|
|
|
+ } else {
|
|
|
|
+ // 添加当前事件
|
|
|
|
+ calAddDays(startDate, endDate, language == 'CN' ? chineseName : name, mark);
|
|
|
|
+
|
|
|
|
+ // 重置开始和结束日期
|
|
|
|
+ startDate = currentDate;
|
|
|
|
+ endDate = currentDate;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加最后一个事件
|
|
|
|
+ calAddDays(startDate, endDate, language == 'CN' ? chineseName : name, mark);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ buildHolidays([endYear, endYear - 1, endYear - 2], holidays, DayType.Holiday);
|
|
|
|
+ buildHolidays([endYear, endYear - 1, endYear - 2], workdays, DayType.Workday);
|
|
|
|
+
|
|
|
|
+ // 将日历保存到 ./dist/holidays.ics 文件
|
|
|
|
+ fs.writeFile(`./dist/holidays${language == 'CN' ? '' : '.en'}.ics`, cal.toString(), "utf8", (err) => {
|
|
|
|
+ if (err) throw err;
|
|
|
|
+ console.log(`The ${language} ICS file has been saved!`);
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
-// 将日历保存到 ./dist/holidays.ics 文件
|
|
|
|
-fs.writeFile("./dist/holidays.ics", cal.toString(), "utf8", (err) => {
|
|
|
|
- if (err) throw err;
|
|
|
|
- console.log("The ICS file has been saved!");
|
|
|
|
-});
|
|
|
|
|
|
+buildIcal('CN')
|
|
|
|
+buildIcal('EN')
|