Browse Source

feat: update holiday days by year

Yaavi 8 months ago
parent
commit
c29e62a012
5 changed files with 97 additions and 32 deletions
  1. 4 0
      CHANGELOG.md
  2. 1 2
      scripts/build-ics.ts
  3. 76 25
      src/holidays/arrangement.ts
  4. 4 4
      test/holidays/arrangement.test.ts
  5. 12 1
      test/holidays/index.test.ts

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 # CHANGELOG
 
+## [1.4.0](https://github.com/vsme/chinese-days) (2024-11-21)
+
+- 完善法定节假日的放假天数,比如 `2007-05-01` 法定节假日为 `Labour Day,劳动节,3`,`2024-05-01` 法定节假日为 `Labour Day,劳动节,1`,而 `2025-05-01` 为 `Labour Day,劳动节,2`,最后一个数字 `3`、`1`、`2` 对应每年法定节假日的精准放假天数。
+
 ## [1.3.4](https://github.com/vsme/chinese-days) (2024-11-13)
 
 - 修正中秋节展示问题

+ 1 - 2
scripts/build-ics.ts

@@ -6,7 +6,6 @@ import ical, {
 } from "ical-generator";
 import generate from "../src/holidays/generate";
 import dayjs, { Dayjs } from "../src/utils/dayjs";
-import { Holiday } from "../src/holidays/arrangement";
 import { createHash } from 'crypto';
 
 enum DayType {
@@ -95,7 +94,7 @@ END:VTIMEZONE`,
 
   const buildHolidays = (
     years: number[],
-    days: Record<string, Holiday>,
+    days: Record<string, string>,
     mark: DayType
   ) => {
     // 合并相同节日的日期

+ 76 - 25
src/holidays/arrangement.ts

@@ -1,16 +1,16 @@
 import dayjs from "../utils/dayjs";
 
 export enum Holiday {
-  NY = "New Year's Day,元旦,1",
-  S = "Spring Festival,春节,4",
-  T = "Tomb-sweeping Day,清明,1",
-  L = "Labour Day,劳动节,2",
-  D = "Dragon Boat Festival,端午,1",
-  N = "National Day,国庆节,3",
-  M = "Mid-autumn Festival,中秋,1",
+  NY = "New Year's Day,元旦",
+  S = "Spring Festival,春节",
+  T = "Tomb-sweeping Day,清明",
+  L = "Labour Day,劳动节",
+  D = "Dragon Boat Festival,端午",
+  N = "National Day,国庆节",
+  M = "Mid-autumn Festival,中秋",
 
   /** special holidays */
-  A = "Anti-Fascist 70th Day,中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日,1",
+  A = "Anti-Fascist 70th Day,中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日",
 }
 
 interface DayDetails {
@@ -27,11 +27,39 @@ enum DayType {
   InLieu = 3,
 }
 
+/** 国务院规定的天数,1999-2025的变化 */
+const holidayDays: Record<number, Partial<Record<Holiday, number>>> = {
+  // 1999 元旦 1 天、春节、劳动节、国庆节放假 3天
+  1999: {
+    [Holiday.NY]: 1,
+    [Holiday.S]: 3,
+    [Holiday.L]: 3,
+    [Holiday.N]: 3,
+  },
+  // 2008 劳动节改为 1 天,增加清明、端午、中秋各 1 天
+  2008: {
+    [Holiday.T]: 1,
+    [Holiday.L]: 1,
+    [Holiday.D]: 1,
+    [Holiday.M]: 1,
+  },
+  // 2014 春节剔除除夕,改为初一、二、三,依旧 3 天
+  // 2015 增加 中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日 1 天
+  2015: {
+    [Holiday.A]: 1,
+  },
+  // 2025 春节和劳动节 各增加 1 天
+  2025: {
+    [Holiday.S]: 4,
+    [Holiday.L]: 2,
+  },
+};
+
 class Arrangement {
   private dayDetails: DayDetails = {};
-  public holidays: Record<string, Holiday> = {};
-  public workdays: Record<string, Holiday> = {};
-  public inLieuDays: Record<string, Holiday> = {};
+  public holidays: Record<string, string> = {};
+  public workdays: Record<string, string> = {};
+  public inLieuDays: Record<string, string> = {};
 
   /** year at */
   y(year: number) {
@@ -39,6 +67,22 @@ class Arrangement {
     return this;
   }
 
+  /** 查询某年 节假日天数 */
+  getHolidayDays(year: number, holiday: Holiday): number {
+    let lastDefinedDays = 0;
+
+    // 遍历规则,查找适用于该年份的节日天数
+    for (const [ruleYear, holidays] of Object.entries(holidayDays)) {
+      const ruleYearNum = parseInt(ruleYear);
+      if (ruleYearNum > year) break;
+      if (holidays[holiday] !== undefined) {
+        lastDefinedDays = holidays[holiday];
+      }
+    }
+
+    return lastDefinedDays;
+  }
+
   mark(holiday: Holiday) {
     this.dayDetails.holiday = holiday;
     return this;
@@ -51,22 +95,28 @@ class Arrangement {
     if (!this.dayDetails.holiday) {
       throw new Error("should set holiday before saving holiday");
     }
+
+    this.dayDetails.month = month;
+    this.dayDetails.day = day;
     this.dayDetails.dayType = dayType;
-    const date = dayjs(`${this.dayDetails.year}-${month}-${day}`);
+
+    const date = dayjs(`${this.dayDetails.year}-${month}-${day}`).format("YYYY-MM-DD");
+    const holidayDays = this.getHolidayDays(this.dayDetails.year, this.dayDetails.holiday);
+    const holidayDescription = `${this.dayDetails.holiday},${holidayDays}`
+
     if (dayType === DayType.Holiday) {
-      this.holidays[date.format("YYYY-MM-DD")] = this.dayDetails.holiday;
+      this.holidays[date] = holidayDescription;
     } else if (dayType === DayType.Workday) {
-      this.workdays[date.format("YYYY-MM-DD")] = this.dayDetails.holiday;
+      this.workdays[date] = holidayDescription;
     } else if (dayType === DayType.InLieu) {
-      this.inLieuDays[date.format("YYYY-MM-DD")] = this.dayDetails.holiday;
+      this.inLieuDays[date] = holidayDescription;
     }
-    this.dayDetails.month = month;
-    this.dayDetails.day = day;
     return this;
   }
 
   to(month: number, day: number) {
     if (
+      !this.dayDetails.holiday ||
       !this.dayDetails.year ||
       !this.dayDetails.month ||
       !this.dayDetails.day
@@ -80,18 +130,19 @@ class Arrangement {
     if (endDate.isBefore(startDate) || endDate.isSame(startDate)) {
       throw new Error("end date should be after start date");
     }
+
+    const holidayDays = this.getHolidayDays(this.dayDetails.year, this.dayDetails.holiday);
+    const holidayDescription = `${this.dayDetails.holiday},${holidayDays}`
+
     const diffDays = endDate.diff(startDate, "day");
     for (let i = 1; i <= diffDays; i++) {
-      const theDate = startDate.add(i, "day");
+      const theDate = startDate.add(i, "day").format("YYYY-MM-DD");
       if (this.dayDetails.dayType === DayType.Holiday) {
-        this.holidays[theDate.format("YYYY-MM-DD")] = this.dayDetails
-          .holiday as Holiday;
+        this.holidays[theDate] = holidayDescription;
       } else if (this.dayDetails.dayType === DayType.Workday) {
-        this.workdays[theDate.format("YYYY-MM-DD")] = this.dayDetails
-          .holiday as Holiday;
+        this.workdays[theDate] = holidayDescription;
       } else if (this.dayDetails.dayType === DayType.InLieu) {
-        this.inLieuDays[theDate.format("YYYY-MM-DD")] = this.dayDetails
-          .holiday as Holiday;
+        this.inLieuDays[theDate] = holidayDescription;
       }
     }
     return this;
@@ -122,7 +173,7 @@ class Arrangement {
   t() {
     return this.mark(Holiday.T);
   }
-  
+
   /** Labour Day 五一 */
   l() {
     return this.mark(Holiday.L);

+ 4 - 4
test/holidays/arrangement.test.ts

@@ -26,19 +26,19 @@ describe('Arrangement class', () => {
   it('should save holiday correctly', () => {
     arrangement.y(2024).ny().r(1, 1);
     const date = dayjs('2024-01-01').format('YYYY-MM-DD');
-    expect(arrangement.holidays[date]).toBe(Holiday.NY);
+    expect(arrangement.holidays[date]).toBe("New Year's Day,元旦,1");
   });
 
   it('should save workday correctly', () => {
     arrangement.y(2024).s().w(2, 4);
     const date = dayjs('2024-02-04').format('YYYY-MM-DD');
-    expect(arrangement.workdays[date]).toBe(Holiday.S);
+    expect(arrangement.workdays[date]).toBe("Spring Festival,春节,3");
   });
 
   it('should save in-lieu day correctly', () => {
     arrangement.y(2024).m().i(9, 16);
     const date = dayjs('2024-09-16').format('YYYY-MM-DD');
-    expect(arrangement.inLieuDays[date]).toBe(Holiday.M);
+    expect(arrangement.inLieuDays[date]).toBe("Mid-autumn Festival,中秋,1");
   });
 
   it('should save holiday range correctly', () => {
@@ -47,7 +47,7 @@ describe('Arrangement class', () => {
       dayjs(date).format('YYYY-MM-DD')
     );
     dates.forEach(date => {
-      expect(arrangement.holidays[date]).toBe(Holiday.S);
+      expect(arrangement.holidays[date]).toBe("Spring Festival,春节,3");
     });
   });
 

+ 12 - 1
test/holidays/index.test.ts

@@ -41,7 +41,18 @@ describe('Holiday Functions', () => {
     expect(detail).toEqual({
       date: '2024-05-01',
       work: false,
-      name: Holiday.L,
+      name: "Labour Day,劳动节,1",
+    });
+  });
+
+  test('getDayDetail should return correct details', () => {
+    const date = '2025-05-01';
+    const detail = getDayDetail(date);
+
+    expect(detail).toEqual({
+      date: '2025-05-01',
+      work: false,
+      name: "Labour Day,劳动节,2",
     });
   });