Browse Source

Merge pull request #16 from vsme/main

merge main
Yawei sun 8 months ago
parent
commit
8de1f6a031

+ 5 - 2
.github/workflows/send-email.yml

@@ -2,8 +2,11 @@ name: Send Email on Holidays Update
 
 on:
   schedule:
-    # 从 00:00 到 12:00 UTC,每 2 小时运行一次(对应北京时间 08:00 到 20:00)
-    - cron: '0 0-12/2 * * *'
+    # 从 00:00 到 10:00 UTC,每 2 小时运行一次(对应北京时间 08:00 到 06:00)
+    - cron: '0 0-10/2 * * *'
+
+  # Allows you to run this workflow manually from the Actions tab on GitHub.
+  workflow_dispatch:
 
 jobs:
   notify:

+ 24 - 0
.github/workflows/test.yml

@@ -0,0 +1,24 @@
+name: Run Tests
+
+on:
+  pull_request:  # 监听 Pull Request 事件
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: "20"
+          registry-url: "https://registry.npmjs.org"
+
+      - name: Install dependencies
+        run: npm install
+
+      - name: Run tests
+        run: npm test

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 node_modules/
 dist/
+coverage/
 
 .DS_Store

+ 16 - 0
CHANGELOG.md

@@ -1,5 +1,21 @@
 # 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.3.3](https://github.com/vsme/chinese-days) (2024-11-12)
+
+- 增加 2025 年节假日
+
+## [1.3.2](https://github.com/vsme/chinese-days) (2024-11-05)
+
+- 修复 `getLunarDate` 阴历闰月的第一天月份错误
+
 ## [1.3.1](https://github.com/vsme/chinese-days) (2024-06-15)
 
 - 增加 `iCal` 英文版本订阅

+ 5 - 5
README.en.md

@@ -4,17 +4,15 @@
 [![GitHub License](https://img.shields.io/github/license/vsme/chinese-days)](https://github.com/vsme/chinese-days/blob/main/LICENSE)
 [![README](https://img.shields.io/badge/README-中文-brightgreen.svg)](https://github.com/vsme/chinese-days/blob/main/README.md)
 
-> Translated by ChatGPT-4, PRs are welcome.
-
 This project provides a series of functions for querying Chinese holidays, adjusted working days, working days, 24 solar terms, and converting between lunar and solar calendars. Additionally, it supports ics file subscription for holidays, which can be subscribed to by Google Calendar, Apple Calendar, Microsoft Outlook, and other clients. The holiday information will be updated according to the announcements from the State Council.
 
-+ **Holidays**: Supports the years 2004 to 2024, including the extended Spring Festival of 2020.
++ **Holidays**: Supports the years 2004 to 2025, including the extended Spring Festival of 2020.
 + **24 Solar Terms**: Supports the years 1900 to 2100.
 + **Lunar Days**: Supports the years 1900 to 2100.
 
 ## Subscribe to Calendar
 
-The subscribed calendar includes holidays and adjusted working days for the past three years (2022-2024).
+The subscribed calendar includes holidays and adjusted working days for the past three years (2023-2025).
 
 Subscription URL: [https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.ics](https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.ics) (default language is Chinese)
 
@@ -24,6 +22,8 @@ For English: [https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.en.ics](ht
 
 A `JSON` file of Chinese holidays is provided and can be directly referenced through this link: [chinese-days.json](https://cdn.jsdelivr.net/npm/chinese-days/dist/chinese-days.json).
 
+For example, in `Java`, you can refer to [Warnier-zhang/java-chinese-days](https://github.com/Warnier-zhang/java-chinese-days), which is only for querying Chinese holidays, in-lieu days, and regular workdays.
+
 ## Quick Start
 
 ### Recommended approach
@@ -301,4 +301,4 @@ console.log(getSolarDateFromLunar('2001-04-05'));
 ## Acknowledgements
 
 1. Lunar calendar data is sourced from the [Bigkoo/Android-PickerView](https://github.com/Bigkoo/Android-PickerView) project.
-2. Chinese holiday data generation references the `Python` version of the [LKI/chinese-calendar](https://github.com/LKI/chinese-calendar) project.
+2. Chinese holiday data generation references the `Python` version of the [LKI/chinese-calendar](https://github.com/LKI/chinese-calendar) project.

+ 4 - 2
README.md

@@ -8,7 +8,7 @@
 
 每日会执行 `Action` 自动抓取数据,节假日变化时发送邮件提醒,信息会跟随国务院发布进行更新。
 
-+ **节假日**:支持 2004年 至 2024年,包括 2020年 的春节延长
++ **节假日**:支持 2004年 至 2025年,包括 2020年 的春节延长
 + **24节气**:支持 1900年 至 2100年。
 + **农历日**:支持 1900年 至 2100年。
 
@@ -16,13 +16,15 @@
 
 如果你不使用 `JS` 或 `TS` 开发项目,本项目提供了中国节假日的 `JSON` 文件,通过链接 [chinese-days.json](https://cdn.jsdelivr.net/npm/chinese-days/dist/chinese-days.json) 可以直接引用。
 
+比如在 `Java` 中使用,可以参考 [Warnier-zhang/java-chinese-days](https://github.com/Warnier-zhang/java-chinese-days),仅用于查询中国节假日、调休日、工作日;
+
 ## 日历订阅
 
 在 Google Calendar、Apple Calendar、Microsoft Outlook 等客户端中,可以设置订阅地址:[https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.ics](https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.ics) 来获取日历订阅。
 
 For English: [https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.en.ics](https://cdn.jsdelivr.net/npm/chinese-days/dist/holidays.en.ics)
 
-订阅的日历包含近三年(2022-2024年)的节假日和调休日。
+订阅的日历包含近三年(2023-2025年)的节假日和调休日。
 
 ## 快速开始
 

+ 3 - 0
docs/env.d.ts

@@ -1 +1,4 @@
 /// <reference types="vite/client" />
+
+// chineseDays
+declare var chineseDays: any;

+ 1 - 0
docs/index.html

@@ -5,6 +5,7 @@
     <link rel="icon" href="/favicon.ico">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Chinese Days</title>
+    <script src="//cdn.jsdelivr.net/npm/chinese-days/dist/index.min.js"></script>
   </head>
   <body>
     <div id="app"></div>

+ 1 - 8
docs/package-lock.json

@@ -8,7 +8,6 @@
       "name": "demo",
       "version": "0.0.0",
       "dependencies": {
-        "chinese-days": "^1.3.1",
         "highlight.js": "^11.10.0",
         "markdown-it": "^14.1.0",
         "vue": "^3.4.38"
@@ -795,7 +794,7 @@
     },
     "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
       "version": "1.3.0",
-      "dev": true,
+      "extraneous": true,
       "inBundle": true,
       "license": "MIT",
       "engines": {
@@ -2812,12 +2811,6 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
-    "node_modules/chinese-days": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/chinese-days/-/chinese-days-1.3.1.tgz",
-      "integrity": "sha512-ImoMcnHKUQQjIF0RMsoANgdvLDNoJYEoFrJ5pxz3XDXbmwV1AKQw/ArYxPuNc6Fv/cCHl97mRg0EAlXw+qEcZQ==",
-      "license": "MIT"
-    },
     "node_modules/ci-info": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",

+ 0 - 1
docs/package.json

@@ -13,7 +13,6 @@
     "lint:fix": "eslint . --fix"
   },
   "dependencies": {
-    "chinese-days": "^1.3.1",
     "highlight.js": "^11.10.0",
     "markdown-it": "^14.1.0",
     "vue": "^3.4.38"

+ 2 - 1
docs/src/components/CalendarComp.vue

@@ -1,6 +1,7 @@
 <script lang="ts" setup>
 import { computed, ref } from 'vue'
-import { getDayDetail, getLunarDate, getSolarTermsInRange, isInLieu } from 'chinese-days'
+
+const { getDayDetail, getLunarDate, getSolarTermsInRange, isInLieu } = chineseDays
 
 const props = withDefaults(
   defineProps<{

+ 3 - 0
docs/vite.config.ts

@@ -15,4 +15,7 @@ export default defineConfig({
       '@': fileURLToPath(new URL('./src', import.meta.url)),
     },
   },
+  define: {
+    chineseDays: 'window.chineseDays'
+  }
 })

+ 4 - 0
jest.config.ts

@@ -1,4 +1,8 @@
 module.exports = {
   preset: "ts-jest",
   testEnvironment: "node",
+  collectCoverage: true,
+  collectCoverageFrom: [
+    "src/**/*.{ts,tsx}",
+  ]
 };

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "chinese-days",
-  "version": "1.3.1",
+  "version": "1.4.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "chinese-days",
-      "version": "1.3.1",
+      "version": "1.4.0",
       "license": "MIT",
       "devDependencies": {
         "@types/jest": "^29.5.12",

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "chinese-days",
-  "version": "1.3.1",
+  "version": "1.4.0",
   "description": "中国节假日、调休日、工作日、24节气查询,农历阳历互转,支持 TS、CommonJS、UMD 模块化使用,提供 ics 日历格式,可供 Google Calendar、Apple Calendar、Microsoft Outlook 等客户端订阅。",
   "main": "dist/index.min.js",
   "module": "dist/index.es.js",
@@ -8,7 +8,7 @@
   "type": "commonjs",
   "repository": {
     "type": "git",
-    "url": "https://github.com/vsme/chinese-days"
+    "url": "git+https://github.com/vsme/chinese-days.git"
   },
   "files": [
     "dist/*",

+ 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
   ) => {
     // 合并相同节日的日期

+ 1 - 1
scripts/fetch.ts

@@ -1,7 +1,7 @@
 import fs from "fs";
 import path from 'path';
 import axios from "axios";
-import cheerio from "cheerio";
+import * as cheerio from "cheerio";
 import { ArgumentParser } from "argparse";
 
 const getPaperUrls = async (year: number): Promise<string[]> => {

+ 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,春节,3",
-  T = "Tomb-sweeping Day,清明,1",
-  L = "Labour Day,劳动节,1",
-  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);

+ 19 - 0
src/holidays/generate.ts

@@ -2,6 +2,25 @@ import Arrangement from "./arrangement.ts"
 
 export default () => {
   const arrangement = new Arrangement()
+  /**
+   * 2025
+   * https://www.gov.cn/zhengce/zhengceku/202411/content_6986383.htm
+   * 一、元旦:1月1日(周三)放假1天,不调休。
+   * 二、春节:1月28日(农历除夕、周二)至2月4日(农历正月初七、周二)放假调休,共8天。1月26日(周日)、2月8日(周六)上班。
+   * 三、清明节:4月4日(周五)至6日(周日)放假,共3天。
+   * 四、劳动节:5月1日(周四)至5日(周一)放假调休,共5天。4月27日(周日)上班。
+   * 五、端午节:5月31日(周六)至6月2日(周一)放假,共3天。
+   * 六、国庆节、中秋节:10月1日(周三)至8日(周三)放假调休,共8天。9月28日(周日)、10月11日(周六)上班。
+   */
+  arrangement.y(2025)
+    .ny().r(1, 1)
+    .s().r(1, 28).to(2, 4).w(1, 26).w(2, 8).i(2, 3).i(2, 4)
+    .t().r(4, 4).to(4, 6)
+    .l().r(5, 1).to(5, 5).w(4, 27).i(5, 5)
+    .d().r(5, 31).to(6, 2)
+    .n().r(10, 1).to(10, 8).w(9, 28).w(10, 11).i(10, 7).i(10, 8)
+    .m().r(10, 6)
+
   /**
    * 2024
    * https://www.gov.cn/zhengce/content/202310/content_6911527.htm

+ 5 - 3
src/solar_lunar/index.ts

@@ -84,7 +84,7 @@ const getLunarYearText = (lunarYear: number): string => {
  * @param endYear 结束农历年份
  * @returns 农历年份列表
  */
-const getLunarYears = (startYear: number, endYear: number) => {
+export const getLunarYears = (startYear: number, endYear: number) => {
   const years = [];
   for (let i = startYear; i <= endYear; i++) {
     years.push({
@@ -101,7 +101,7 @@ const getLunarYears = (startYear: number, endYear: number) => {
  * @param year 年份
  * @returns 农历闰月月份
  */
-const getYearLeapMonth = (year: number) => {
+export const getYearLeapMonth = (year: number) => {
   const leap = yearLeapMonth(year)
   return {
     year,
@@ -146,7 +146,7 @@ export const getLunarDate = (date: ConfigType): LunarDateDetail => {
   leap = yearLeapMonth(i); // 闰哪个月
   lunarDate[6] = 0; // 闰月标记,初始为0
 
-  for (let j = 1; j < 13 && offset > 0; j++) {
+  for (let j = 1; j < 13 && offset >= 0; j++) {
     if (leap > 0 && j === (leap + 1) && lunarDate[6] === 0) {
       --j;
       lunarDate[6] = 1;
@@ -262,6 +262,8 @@ export const getSolarDateFromLunar = (lunarDate: ConfigType): {
 }
 
 export default {
+  getLunarYears,
+  getYearLeapMonth,
   getLunarDate,
   getLunarDatesInRange,
   getSolarDateFromLunar,

+ 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");
     });
   });
 

+ 72 - 3
test/holidays/index.test.ts

@@ -10,6 +10,12 @@ import {
 } from '../../src';
 
 describe('Holiday Functions', () => {
+  test('should throw an error for invalid date', () => {
+    expect(() => isHoliday('invalid-date')).toThrow(
+      'unsupported type object, expected type is Date or Dayjs'
+    );
+  });
+
   test('isHoliday should return correct boolean values', () => {
     const date1 = '2024-05-01';
     const date2 = '2024-05-06';
@@ -34,6 +40,28 @@ describe('Holiday Functions', () => {
     expect(isInLieu(date2)).toBe(true);
   });
 
+  test('getDayDetail should return correct details', () => {
+    const date = '2024-04-29';
+    const detail = getDayDetail(date);
+
+    expect(detail).toEqual({
+      date: '2024-04-29',
+      work: true,
+      name: "Monday",
+    });
+  });
+
+  test('getDayDetail should return correct details', () => {
+    const date = '2025-01-26';
+    const detail = getDayDetail(date);
+
+    expect(detail).toEqual({
+      date: '2025-01-26',
+      work: true,
+      name: "Spring Festival,春节,4",
+    });
+  });
+
   test('getDayDetail should return correct details', () => {
     const date = '2024-05-01';
     const detail = getDayDetail(date);
@@ -41,32 +69,73 @@ 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",
     });
   });
 
   test('getHolidaysInRange should return correct holidays within a range', () => {
     const start = '2024-05-01';
     const end = '2024-05-31';
-    const holidaysInRange = getHolidaysInRange(start, end, true);
+    const holidaysInRange = getHolidaysInRange(start, end, false);
 
     expect(holidaysInRange).toContain('2024-05-01');
   });
 
+  test('getHolidaysInRange should return correct holidays within a range', () => {
+    const start = '2024-05-01';
+    const end = '2024-05-31';
+    const holidaysInRange = getHolidaysInRange(start, end, true);
+
+    expect(holidaysInRange).toContain('2024-05-12');
+  });
+
   test('getWorkdaysInRange should return correct workdays within a range', () => {
     const start = '2024-05-01';
     const end = '2024-05-31';
-    const workdaysInRange = getWorkdaysInRange(start, end, true);
+    const workdaysInRange = getWorkdaysInRange(start, end, false);
 
     expect(workdaysInRange).toContain('2024-05-06');
   });
 
+  test('getWorkdaysInRange should return correct workdays within a range', () => {
+    const start = '2024-05-01';
+    const end = '2024-05-31';
+    const workdaysInRange = getWorkdaysInRange(start, end, true);
+
+    expect(workdaysInRange).toContain('2024-05-11');
+  });
+
   test('findWorkday should return correct workday', () => {
     const date = '2024-05-01';
     const nextWorkday = findWorkday(1, date);
 
     expect(nextWorkday).toBe('2024-05-06');
   });
+
+  test('findWorkday should return correct workday', () => {
+    const date = '2024-05-11';
+    const nextWorkday = findWorkday(0, date);
+
+    expect(nextWorkday).toBe('2024-05-11');
+  });
+
+  test('findWorkday should return correct workday', () => {
+    const date = '2024-05-12';
+    const nextWorkday = findWorkday(0, date);
+
+    expect(nextWorkday).toBe('2024-05-13');
+  });
 });
 
 describe('Arrangement Class', () => {

+ 41 - 5
test/solar_lunar/index.test.ts

@@ -1,4 +1,6 @@
 import {
+  getLunarYears,
+  getYearLeapMonth,
   getLunarDate,
   getLunarDatesInRange,
   getSolarDateFromLunar,
@@ -6,7 +8,24 @@ import {
 
 describe("solar_lunar", () => {
   test("getLunarDate should return correct lunar date for a given solar date", () => {
-    let result = getLunarDate("2057-09-28");
+    // 闰月第一天
+    let result = getLunarDate("2014-10-24");
+    expect(result).toEqual({
+      date: '2014-10-24',
+      lunarYear: 2014,
+      lunarMon: 9,
+      lunarDay: 1,
+      isLeap: true,
+      zodiac: '马',
+      yearCyl: '甲午',
+      monCyl: '甲戌',
+      dayCyl: '戊辰',
+      lunarYearCN: '二零一四',
+      lunarMonCN: '九月',
+      lunarDayCN: '初一'
+    })
+
+    result = getLunarDate("2057-09-28");
     expect(result).toEqual({
       date: "2057-09-28",
       lunarYear: 2057,
@@ -53,6 +72,23 @@ describe("solar_lunar", () => {
     expect(result).toEqual({ date: "2001-04-27", leapMonthDate: "2001-05-27" });
   });
 
+  test("getLunarYears should return correct", () => {
+    let result = getLunarYears(2001, 2003);
+    expect(result).toEqual([
+      {"lunarYear": "辛巳年", "lunarYearCN": "二零零一", "year": 2001},
+      {"lunarYear": "壬午年", "lunarYearCN": "二零零二", "year": 2002},
+      {"lunarYear": "癸未年", "lunarYearCN": "二零零三", "year": 2003}
+    ]);
+  });
+
+  test("getYearLeapMonth should return correct", () => {
+    let result = getYearLeapMonth(2022);
+    expect(result).toEqual({"days": 0, "leapMonth": undefined, "leapMonthCN": undefined, "year": 2022});
+
+    result = getYearLeapMonth(2023);
+    expect(result).toEqual({"days": 29, "leapMonth": 2, "leapMonthCN": "闰二月", "year": 2023});
+  });
+
   test("getLunarDatesInRange should return correct lunar dates for a given solar date range", () => {
     let result = getLunarDatesInRange("2001-05-21", "2001-05-26");
     expect(result).toEqual([
@@ -87,15 +123,15 @@ describe("solar_lunar", () => {
       {
         date: "2001-05-23",
         lunarYear: 2001,
-        lunarMon: 5,
+        lunarMon: 4,
         lunarDay: 1,
-        isLeap: false,
+        isLeap: true,
         zodiac: "蛇",
         yearCyl: "辛巳",
-        monCyl: "甲午",
+        monCyl: "癸巳",
         dayCyl: "丙戌",
         lunarYearCN: "二零零一",
-        lunarMonCN: "月",
+        lunarMonCN: "月",
         lunarDayCN: "初一",
       },
       {

+ 83 - 1
test/solar_terms/index.test.ts

@@ -1,10 +1,16 @@
 import dayjs from "../../src/utils/dayjs";
-import { getSolarTermDate, getSolarTerms, type SolarTerm } from "../../src";
+import { getSolarTermDate, getSolarTerms, getSolarTermsInRange, type SolarTerm } from "../../src";
 
 import type { SolarTermKey } from "../../src/solar_terms/constants";
 
 describe("Solar Terms", () => {
   describe("getSolarTermDate", () => {
+    it("should correctly calculate the solar term date for 'lesser_cold' in 1998", () => {
+      const term: SolarTermKey = "lesser_cold";
+      const date = getSolarTermDate(1998, 1, term);
+      expect(date).toBe("1998-01-05");
+    });
+
     it("should correctly calculate the solar term date for 'lesser_cold' in 2024", () => {
       const term: SolarTermKey = "lesser_cold";
       const date = getSolarTermDate(2024, 1, term);
@@ -71,5 +77,81 @@ describe("Solar Terms", () => {
       ];
       expect(terms).toEqual(expected);
     });
+
+    it("should handle a single day range", () => {
+      const date = dayjs("2024-01-06");
+      const terms = getSolarTerms(date);
+      const expected: SolarTerm[] = [
+        { date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 },
+      ];
+      expect(terms).toEqual(expected);
+    });
+  });
+
+  describe('getSolarTermsInRange', () => {
+    test('should get solar terms within the specified date range', () => {
+      const start = dayjs('2024-01-04');
+      const end = dayjs('2024-01-07');
+  
+      const result = getSolarTermsInRange(start, end);
+      expect(result).toEqual([
+        {
+          date: '2024-01-04',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 14
+        },
+        {
+          date: '2024-01-05',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 15
+        },
+        { date: '2024-01-06', term: 'lesser_cold', name: '小寒', index: 1 },
+        { date: '2024-01-07', term: 'lesser_cold', name: '小寒', index: 2 }
+      ]);
+    });
+  
+    test('should get solar terms for the current day when end date is not provided', () => {
+      const start = dayjs('2024-01-20');
+  
+      const result = getSolarTermsInRange(start);
+  
+      expect(result).toEqual([{ date: '2024-01-20', term: 'greater_cold', name: '大寒', index: 1 }]);
+    });
+  
+    test('should handle date range spanning across the year boundary', () => {
+      const start = dayjs('2024-12-30');
+      const end = dayjs('2025-01-02');
+  
+      const result = getSolarTermsInRange(start, end);
+  
+      expect(result).toEqual([
+        {
+          date: '2024-12-30',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 10
+        },
+        {
+          date: '2024-12-31',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 11
+        },
+        {
+          date: '2025-01-01',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 12
+        },
+        {
+          date: '2025-01-02',
+          term: 'the_winter_solstice',
+          name: '冬至',
+          index: 13
+        }
+      ]);
+    });
   });
 });

+ 14 - 2
test/utils/dayjs.test.ts

@@ -3,7 +3,10 @@ import simpleDayjs from "../../src/utils/dayjs";
 
 describe("SimpleDayjs", () => {
   it("should return true for a valid date", () => {
-    const date = simpleDayjs("2024-02-10");
+    let date = simpleDayjs();
+    expect(date.isValid()).toBe(true);
+
+    date = simpleDayjs("2024-02-10");
     expect(date.isValid()).toBe(true);
   });
 
@@ -12,9 +15,11 @@ describe("SimpleDayjs", () => {
     expect(date.isValid()).toBe(false);
   });
 
-  it("should calculate the difference in days", () => {
+  it("should calculate the difference", () => {
     const date1 = simpleDayjs("2024-02-10");
     const date2 = simpleDayjs("2000-01-01");
+    expect(date1.diff(date2, "year")).toBe(24);
+    expect(date1.diff(date2, "month")).toBe(289);
     expect(date1.diff(date2, "day")).toBe(8806);
   });
 
@@ -23,6 +28,13 @@ describe("SimpleDayjs", () => {
     expect(date.startOf("year").format("YYYY-MM-DD")).toBe("2024-01-01");
   });
 
+  it("should format the end of the ...", () => {
+    const date = simpleDayjs("2024-02-10");
+    expect(date.endOf("year").format("YYYY-MM-DD HH:mm:ss")).toBe("2024-12-31 23:59:59");
+    expect(date.endOf("month").format("YYYY-MM-DD HH:mm:ss")).toBe("2024-02-29 23:59:59");
+    expect(date.endOf("day").format("YYYY-MM-DD HH:mm:ss")).toBe("2024-02-10 23:59:59");
+  });
+
   it("should add one month to the date", () => {
     const date = simpleDayjs("2024-02-10");
     expect(date.add(1, "month").format("YYYY-MM-DD")).toBe("2024-03-10");