Browse Source

feat: 增加ics日历文件

yaavi 1 year ago
parent
commit
7a6fe13ea5
4 changed files with 188 additions and 2 deletions
  1. 58 0
      package-lock.json
  2. 2 1
      package.json
  3. 127 0
      scripts/build-ics.ts
  4. 1 1
      scripts/build-json.ts

+ 58 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "@types/jest": "^29.5.12",
         "@types/jest": "^29.5.12",
         "axios": "^1.7.2",
         "axios": "^1.7.2",
         "cheerio": "^1.0.0-rc.12",
         "cheerio": "^1.0.0-rc.12",
+        "ical-generator": "^7.1.0",
         "jest": "^29.7.0",
         "jest": "^29.7.0",
         "ts-jest": "^29.1.2",
         "ts-jest": "^29.1.2",
         "ts-node": "^10.9.2",
         "ts-node": "^10.9.2",
@@ -3602,6 +3603,57 @@
         "node": ">=10.17.0"
         "node": ">=10.17.0"
       }
       }
     },
     },
+    "node_modules/ical-generator": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-7.1.0.tgz",
+      "integrity": "sha512-mv7wW35+YHbaFDQGgFPSnn+SOiN805UvQL8VaMye6F6AgTQCDlT8dc0XIndCHgp22KvOdJ0po795QHoPIAq+kA==",
+      "dev": true,
+      "dependencies": {
+        "uuid-random": "^1.3.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@touch4it/ical-timezones": ">=1.6.0",
+        "@types/luxon": ">= 1.26.0",
+        "@types/mocha": ">= 8.2.1",
+        "dayjs": ">= 1.10.0",
+        "luxon": ">= 1.26.0",
+        "moment": ">= 2.29.0",
+        "moment-timezone": ">= 0.5.33",
+        "rrule": ">= 2.6.8"
+      },
+      "peerDependenciesMeta": {
+        "@touch4it/ical-timezones": {
+          "optional": true
+        },
+        "@types/luxon": {
+          "optional": true
+        },
+        "@types/mocha": {
+          "optional": true
+        },
+        "@types/node": {
+          "optional": true
+        },
+        "dayjs": {
+          "optional": true
+        },
+        "luxon": {
+          "optional": true
+        },
+        "moment": {
+          "optional": true
+        },
+        "moment-timezone": {
+          "optional": true
+        },
+        "rrule": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/import-lazy": {
     "node_modules/import-lazy": {
       "version": "4.0.0",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
@@ -5981,6 +6033,12 @@
         "punycode": "^2.1.0"
         "punycode": "^2.1.0"
       }
       }
     },
     },
+    "node_modules/uuid-random": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz",
+      "integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ==",
+      "dev": true
+    },
     "node_modules/v8-compile-cache-lib": {
     "node_modules/v8-compile-cache-lib": {
       "version": "3.0.1",
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

+ 2 - 1
package.json

@@ -20,7 +20,7 @@
   ],
   ],
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
-    "build": "tsc && vite build && ts-node scripts/build-json.ts",
+    "build": "tsc && vite build && ts-node scripts/build-json.ts && ts-node scripts/build-ics.ts",
     "fetch": "ts-node scripts/fetch.ts",
     "fetch": "ts-node scripts/fetch.ts",
     "test": "jest"
     "test": "jest"
   },
   },
@@ -43,6 +43,7 @@
     "@types/jest": "^29.5.12",
     "@types/jest": "^29.5.12",
     "axios": "^1.7.2",
     "axios": "^1.7.2",
     "cheerio": "^1.0.0-rc.12",
     "cheerio": "^1.0.0-rc.12",
+    "ical-generator": "^7.1.0",
     "jest": "^29.7.0",
     "jest": "^29.7.0",
     "ts-jest": "^29.1.2",
     "ts-jest": "^29.1.2",
     "ts-node": "^10.9.2",
     "ts-node": "^10.9.2",

+ 127 - 0
scripts/build-ics.ts

@@ -0,0 +1,127 @@
+import fs from "fs";
+import ical, {
+  ICalEventClass,
+  ICalEventStatus,
+  ICalEventTransparency,
+} from "ical-generator";
+import generate from "../src/holidays/generate";
+import dayjs, { Dayjs } from "../src/utils/dayjs";
+import { Holiday } from "../src/holidays/arrangement";
+
+const { holidays, workdays, inLieuDays } = generate();
+
+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) => `
+BEGIN:VTIMEZONE
+TZID:${tzid}
+X-LIC-LOCATION:${tzid}
+BEGIN:STANDARD
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0800
+TZNAME:CST
+DTSTART:19700101T000000
+END:STANDARD
+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,
+          status: ICalEventStatus.CONFIRMED,
+          summary: `${chineseName}${mark}`,
+          transparency: ICalEventTransparency.TRANSPARENT,
+          allDay: true,
+          class: ICalEventClass.PUBLIC,
+        });
+
+        // 重置开始和结束日期
+        startDate = currentDate;
+        endDate = currentDate;
+      }
+    }
+
+    // 添加最后一个事件
+    cal.createEvent({
+      start: startDate.toDate(),
+      end: endDate.add(1, "day").toDate(),
+      description: chineseName,
+      status: ICalEventStatus.CONFIRMED,
+      summary: `${chineseName}${mark}`,
+      transparency: ICalEventTransparency.TRANSPARENT,
+      allDay: true,
+      class: ICalEventClass.PUBLIC,
+    });
+  }
+};
+
+for (let i = endYear; i > endYear - 3; i--) {
+  buildHolidays(i, holidays, "(休)");
+  buildHolidays(i, workdays, "(班)");
+}
+
+// 将日历保存到 ./dist/holidays.ics 文件
+fs.writeFile("./dist/holidays.ics", cal.toString(), "utf8", (err) => {
+  if (err) throw err;
+  console.log("The ICS file has been saved!");
+});

+ 1 - 1
scripts/build-json.ts

@@ -4,5 +4,5 @@ import generate from '../src/holidays/generate';
 // 保存到 ./dist/chinese-days.json 文件
 // 保存到 ./dist/chinese-days.json 文件
 fs.writeFile("./dist/chinese-days.json", JSON.stringify(generate()), (err) => {
 fs.writeFile("./dist/chinese-days.json", JSON.stringify(generate()), (err) => {
   if (err) throw err;
   if (err) throw err;
-  console.log("The json file has been saved!");
+  console.log("The JSON file has been saved!");
 });
 });