@weiensong/chinese-calendar 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/README.zh-CN.md +138 -0
- package/data/2014.js +30 -0
- package/data/2015.js +42 -0
- package/data/2016.js +36 -0
- package/data/2017.js +33 -0
- package/data/2018.js +31 -0
- package/data/2019.js +48 -0
- package/data/2020.js +40 -0
- package/data/2021.js +32 -0
- package/data/2022.js +32 -0
- package/data/2023.js +35 -0
- package/data/2024.js +33 -0
- package/data/2025.js +33 -0
- package/data/2026.js +36 -0
- package/data/index.js +29 -0
- package/index.js +5 -0
- package/lib/date.js +86 -0
- package/lib/schedule.js +322 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 weiensong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# chinese-calendar
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@weiensong/chinese-calendar)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
A zero-dependency ESM package that determines whether a given date is a workday in mainland China. It respects the State Council's official holiday and adjusted-workday (调休) schedules, with optional support for Guangxi Sanyuesan (广西三月三).
|
|
7
|
+
|
|
8
|
+
[中文文档](./README.zh-CN.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @weiensong/chinese-calendar
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { isWorkday, getDayInfo } from "@weiensong/chinese-calendar";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `isWorkday(input, options?)`
|
|
25
|
+
|
|
26
|
+
Returns `true` if the date is a workday, `false` otherwise.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
isWorkday(new Date(2026, 0, 5)); // true — Monday
|
|
30
|
+
isWorkday("2026-01-01"); // false — New Year holiday
|
|
31
|
+
isWorkday("2026-01-04"); // true — adjusted workday for New Year
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `getDayInfo(input, options?)`
|
|
35
|
+
|
|
36
|
+
Returns a detailed result object explaining the classification.
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
getDayInfo("2026-01-04");
|
|
40
|
+
// {
|
|
41
|
+
// date: "2026-01-04",
|
|
42
|
+
// isWorkday: true,
|
|
43
|
+
// type: "adjusted-workday",
|
|
44
|
+
// holidayName: "元旦",
|
|
45
|
+
// scheduleYear: 2026,
|
|
46
|
+
// scheduleStatus: "official",
|
|
47
|
+
// sourceUrl: "https://www.gov.cn/..."
|
|
48
|
+
// }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| `type` | Meaning |
|
|
52
|
+
| ------------------ | ----------------------- |
|
|
53
|
+
| `regular-workday` | Regular weekday |
|
|
54
|
+
| `weekend` | Regular weekend |
|
|
55
|
+
| `holiday` | Official holiday |
|
|
56
|
+
| `adjusted-workday` | Adjusted workday (调休) |
|
|
57
|
+
|
|
58
|
+
### Options
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface Options {
|
|
62
|
+
requireOfficialSchedule?: boolean; // throw if no data for the year
|
|
63
|
+
enableGuangxiSanyuesan?: boolean; // enable Guangxi Sanyuesan holidays
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Strict mode
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { isWorkday, ScheduleNotFoundError } from "@weiensong/chinese-calendar";
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
isWorkday("2027-01-04", { requireOfficialSchedule: true });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.log(error instanceof ScheduleNotFoundError); // true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Guangxi Sanyuesan
|
|
80
|
+
|
|
81
|
+
Disabled by default. When enabled, any date falling within the Sanyuesan period is resolved solely by Guangxi rules — national adjusted-workday schedules do not apply during this window.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
isWorkday("2025-03-31"); // true — national workday
|
|
85
|
+
isWorkday("2025-03-31", { enableGuangxiSanyuesan: true }); // false — Guangxi Sanyuesan
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Date input
|
|
89
|
+
|
|
90
|
+
`isWorkday` and `getDayInfo` accept a `Date` object or a string. Strings in `YYYY-MM-DD` format are parsed as local calendar dates (no timezone offset). Other strings are parsed via `new Date()` and interpreted in the runtime's local timezone. Invalid input throws a `TypeError`.
|
|
91
|
+
|
|
92
|
+
## Coverage
|
|
93
|
+
|
|
94
|
+
Built-in data covers **2014–2026**, including subsequent adjustments such as the 2015 Victory Day holiday, 2019 Labor Day revision, and 2020 Spring Festival extension.
|
|
95
|
+
|
|
96
|
+
When no schedule data exists for a year, the package falls back to simple weekday/weekend rules. Set `requireOfficialSchedule: true` to throw a `ScheduleNotFoundError` instead.
|
|
97
|
+
|
|
98
|
+
## Adding a new year
|
|
99
|
+
|
|
100
|
+
1. Create `data/YYYY.js` following the schema below.
|
|
101
|
+
2. Register it in `data/index.js`.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
export default {
|
|
105
|
+
year: 2026,
|
|
106
|
+
source: {
|
|
107
|
+
title: "国务院办公厅关于2026年部分节假日安排的通知",
|
|
108
|
+
documentNumber: "国办发明电〔2025〕7号",
|
|
109
|
+
publishedAt: "2025-11-04",
|
|
110
|
+
url: "https://www.gov.cn/...",
|
|
111
|
+
},
|
|
112
|
+
holidays: [
|
|
113
|
+
{ name: "春节", start: "2026-02-15", end: "2026-02-23" },
|
|
114
|
+
],
|
|
115
|
+
adjustedWorkdays: [
|
|
116
|
+
{ date: "2026-02-14", relatedHoliday: "春节" },
|
|
117
|
+
],
|
|
118
|
+
guangxiSanyuesan: {
|
|
119
|
+
name: "广西三月三",
|
|
120
|
+
daysOff: [],
|
|
121
|
+
adjustedWorkdays: [],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The `guangxiSanyuesan` block is required; leave arrays empty if unused.
|
|
127
|
+
|
|
128
|
+
## Testing
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm test
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Uses Node.js built-in test runner. No additional dependencies required.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
[MIT](./LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# chinese-calendar
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@weiensong/chinese-calendar)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
零依赖的 ESM npm 包,判断中国大陆指定日期是否需要上班,遵循国务院官方节假日和调休安排,可选支持广西三月三。
|
|
7
|
+
|
|
8
|
+
[English Documentation](./README.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @weiensong/chinese-calendar
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { isWorkday, getDayInfo } from "@weiensong/chinese-calendar";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `isWorkday(input, options?)`
|
|
25
|
+
|
|
26
|
+
返回 `true` 表示上班日,`false` 表示休息日。
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
isWorkday(new Date(2026, 0, 5)); // true — 星期一
|
|
30
|
+
isWorkday("2026-01-01"); // false — 元旦假期
|
|
31
|
+
isWorkday("2026-01-04"); // true — 元旦调休上班
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `getDayInfo(input, options?)`
|
|
35
|
+
|
|
36
|
+
返回包含判断依据的详细对象。
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
getDayInfo("2026-01-04");
|
|
40
|
+
// {
|
|
41
|
+
// date: "2026-01-04",
|
|
42
|
+
// isWorkday: true,
|
|
43
|
+
// type: "adjusted-workday",
|
|
44
|
+
// holidayName: "元旦",
|
|
45
|
+
// scheduleYear: 2026,
|
|
46
|
+
// scheduleStatus: "official",
|
|
47
|
+
// sourceUrl: "https://www.gov.cn/..."
|
|
48
|
+
// }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| `type` | 含义 |
|
|
52
|
+
| ------------------ | ------------ |
|
|
53
|
+
| `regular-workday` | 普通工作日 |
|
|
54
|
+
| `weekend` | 普通周末 |
|
|
55
|
+
| `holiday` | 法定节假日 |
|
|
56
|
+
| `adjusted-workday` | 调休上班日 |
|
|
57
|
+
|
|
58
|
+
### 选项
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface Options {
|
|
62
|
+
requireOfficialSchedule?: boolean; // 无年度数据时抛出错误
|
|
63
|
+
enableGuangxiSanyuesan?: boolean; // 启用广西三月三
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 严格模式
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { isWorkday, ScheduleNotFoundError } from "@weiensong/chinese-calendar";
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
isWorkday("2027-01-04", { requireOfficialSchedule: true });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.log(error instanceof ScheduleNotFoundError); // true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### 广西三月三
|
|
80
|
+
|
|
81
|
+
默认不启用。启用后,三月三日期区间内的日期全部按广西规则判断,全国调休不覆盖该区间。
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
isWorkday("2025-03-31"); // true — 全国工作日
|
|
85
|
+
isWorkday("2025-03-31", { enableGuangxiSanyuesan: true }); // false — 广西三月三放假
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 日期输入
|
|
89
|
+
|
|
90
|
+
接受 `Date` 对象或字符串。`YYYY-MM-DD` 格式按本地日历日期解析(无时区偏移),其他字符串通过 `new Date()` 解析并按运行环境本地时区判断。无效输入抛出 `TypeError`。
|
|
91
|
+
|
|
92
|
+
## 覆盖范围
|
|
93
|
+
|
|
94
|
+
内置 **2014—2026** 年国务院节假日安排,包含历史上的后续调整(2015 年抗战胜利纪念日、2019 年劳动节调整、2020 年春节延长)。
|
|
95
|
+
|
|
96
|
+
缺少年度数据时默认退回普通星期规则,可启用严格模式抛出 `ScheduleNotFoundError`。
|
|
97
|
+
|
|
98
|
+
## 添加新年份
|
|
99
|
+
|
|
100
|
+
1. 按以下结构创建 `data/YYYY.js`。
|
|
101
|
+
2. 在 `data/index.js` 中注册。
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
export default {
|
|
105
|
+
year: 2026,
|
|
106
|
+
source: {
|
|
107
|
+
title: "国务院办公厅关于2026年部分节假日安排的通知",
|
|
108
|
+
documentNumber: "国办发明电〔2025〕7号",
|
|
109
|
+
publishedAt: "2025-11-04",
|
|
110
|
+
url: "https://www.gov.cn/...",
|
|
111
|
+
},
|
|
112
|
+
holidays: [
|
|
113
|
+
{ name: "春节", start: "2026-02-15", end: "2026-02-23" },
|
|
114
|
+
],
|
|
115
|
+
adjustedWorkdays: [
|
|
116
|
+
{ date: "2026-02-14", relatedHoliday: "春节" },
|
|
117
|
+
],
|
|
118
|
+
guangxiSanyuesan: {
|
|
119
|
+
name: "广西三月三",
|
|
120
|
+
daysOff: [],
|
|
121
|
+
adjustedWorkdays: [],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`guangxiSanyuesan` 字段为必填,没有假期时保留空数组。
|
|
127
|
+
|
|
128
|
+
## 测试
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm test
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
使用 Node.js 原生测试运行器,无需安装额外依赖。
|
|
135
|
+
|
|
136
|
+
## 许可证
|
|
137
|
+
|
|
138
|
+
[MIT](./LICENSE)
|
package/data/2014.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2014,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2014年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2013〕28号",
|
|
6
|
+
publishedAt: "2013-12-11",
|
|
7
|
+
url: "https://www.yueyang.gov.cn/gggs/szbm/content_230742.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2014-01-01", end: "2014-01-01" },
|
|
11
|
+
{ name: "春节", start: "2014-01-31", end: "2014-02-06" },
|
|
12
|
+
{ name: "清明节", start: "2014-04-05", end: "2014-04-07" },
|
|
13
|
+
{ name: "劳动节", start: "2014-05-01", end: "2014-05-03" },
|
|
14
|
+
{ name: "端午节", start: "2014-05-31", end: "2014-06-02" },
|
|
15
|
+
{ name: "中秋节", start: "2014-09-06", end: "2014-09-08" },
|
|
16
|
+
{ name: "国庆节", start: "2014-10-01", end: "2014-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2014-01-26", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2014-02-08", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2014-05-04", relatedHoliday: "劳动节" },
|
|
22
|
+
{ date: "2014-09-28", relatedHoliday: "国庆节" },
|
|
23
|
+
{ date: "2014-10-11", relatedHoliday: "国庆节" },
|
|
24
|
+
],
|
|
25
|
+
guangxiSanyuesan: {
|
|
26
|
+
name: "广西三月三",
|
|
27
|
+
daysOff: ["2014-04-02", "2014-04-03"],
|
|
28
|
+
adjustedWorkdays: [],
|
|
29
|
+
},
|
|
30
|
+
};
|
package/data/2015.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2015,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2015年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2014〕28号",
|
|
6
|
+
publishedAt: "2014-12-16",
|
|
7
|
+
url: "https://www.nea.gov.cn/2015-01/08/c_133904166.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2015-01-01", end: "2015-01-03" },
|
|
11
|
+
{ name: "春节", start: "2015-02-18", end: "2015-02-24" },
|
|
12
|
+
{ name: "清明节", start: "2015-04-04", end: "2015-04-06" },
|
|
13
|
+
{ name: "劳动节", start: "2015-05-01", end: "2015-05-03" },
|
|
14
|
+
{ name: "端午节", start: "2015-06-20", end: "2015-06-22" },
|
|
15
|
+
{
|
|
16
|
+
name: "抗战胜利纪念日",
|
|
17
|
+
start: "2015-09-03",
|
|
18
|
+
end: "2015-09-05",
|
|
19
|
+
sourceUrl:
|
|
20
|
+
"https://www.ndrc.gov.cn/xxgk/zcfb/qt/201505/t20150513_967883.html",
|
|
21
|
+
},
|
|
22
|
+
{ name: "中秋节", start: "2015-09-26", end: "2015-09-27" },
|
|
23
|
+
{ name: "国庆节", start: "2015-10-01", end: "2015-10-07" },
|
|
24
|
+
],
|
|
25
|
+
adjustedWorkdays: [
|
|
26
|
+
{ date: "2015-01-04", relatedHoliday: "元旦" },
|
|
27
|
+
{ date: "2015-02-15", relatedHoliday: "春节" },
|
|
28
|
+
{ date: "2015-02-28", relatedHoliday: "春节" },
|
|
29
|
+
{
|
|
30
|
+
date: "2015-09-06",
|
|
31
|
+
relatedHoliday: "抗战胜利纪念日",
|
|
32
|
+
sourceUrl:
|
|
33
|
+
"https://www.ndrc.gov.cn/xxgk/zcfb/qt/201505/t20150513_967883.html",
|
|
34
|
+
},
|
|
35
|
+
{ date: "2015-10-10", relatedHoliday: "国庆节" },
|
|
36
|
+
],
|
|
37
|
+
guangxiSanyuesan: {
|
|
38
|
+
name: "广西三月三",
|
|
39
|
+
daysOff: ["2015-04-20", "2015-04-21"],
|
|
40
|
+
adjustedWorkdays: [],
|
|
41
|
+
},
|
|
42
|
+
};
|
package/data/2016.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2016,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2016年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2015〕18号",
|
|
6
|
+
publishedAt: "2015-12-11",
|
|
7
|
+
url: "https://www.nea.gov.cn/2015-12/11/c_134905936.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2016-01-01", end: "2016-01-03" },
|
|
11
|
+
{ name: "春节", start: "2016-02-07", end: "2016-02-13" },
|
|
12
|
+
{ name: "清明节", start: "2016-04-02", end: "2016-04-04" },
|
|
13
|
+
{ name: "劳动节", start: "2016-04-30", end: "2016-05-02" },
|
|
14
|
+
{ name: "端午节", start: "2016-06-09", end: "2016-06-11" },
|
|
15
|
+
{ name: "中秋节", start: "2016-09-15", end: "2016-09-17" },
|
|
16
|
+
{ name: "国庆节", start: "2016-10-01", end: "2016-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2016-02-06", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2016-02-14", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2016-06-12", relatedHoliday: "端午节" },
|
|
22
|
+
{ date: "2016-09-18", relatedHoliday: "中秋节" },
|
|
23
|
+
{ date: "2016-10-08", relatedHoliday: "国庆节" },
|
|
24
|
+
{ date: "2016-10-09", relatedHoliday: "国庆节" },
|
|
25
|
+
],
|
|
26
|
+
guangxiSanyuesan: {
|
|
27
|
+
name: "广西三月三",
|
|
28
|
+
daysOff: [
|
|
29
|
+
"2016-04-08",
|
|
30
|
+
"2016-04-09",
|
|
31
|
+
"2016-04-10",
|
|
32
|
+
"2016-04-11",
|
|
33
|
+
],
|
|
34
|
+
adjustedWorkdays: [],
|
|
35
|
+
},
|
|
36
|
+
};
|
package/data/2017.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2017,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2017年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2016〕17号",
|
|
6
|
+
publishedAt: "2016-12-02",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/201612/01/394246/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2016-12-31", end: "2017-01-02" },
|
|
11
|
+
{ name: "春节", start: "2017-01-27", end: "2017-02-02" },
|
|
12
|
+
{ name: "清明节", start: "2017-04-02", end: "2017-04-04" },
|
|
13
|
+
{ name: "劳动节", start: "2017-04-29", end: "2017-05-01" },
|
|
14
|
+
{ name: "端午节", start: "2017-05-28", end: "2017-05-30" },
|
|
15
|
+
{
|
|
16
|
+
name: "国庆节、中秋节",
|
|
17
|
+
start: "2017-10-01",
|
|
18
|
+
end: "2017-10-08",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
adjustedWorkdays: [
|
|
22
|
+
{ date: "2017-01-22", relatedHoliday: "春节" },
|
|
23
|
+
{ date: "2017-02-04", relatedHoliday: "春节" },
|
|
24
|
+
{ date: "2017-04-01", relatedHoliday: "清明节" },
|
|
25
|
+
{ date: "2017-05-27", relatedHoliday: "端午节" },
|
|
26
|
+
{ date: "2017-09-30", relatedHoliday: "国庆节、中秋节" },
|
|
27
|
+
],
|
|
28
|
+
guangxiSanyuesan: {
|
|
29
|
+
name: "广西三月三",
|
|
30
|
+
daysOff: ["2017-03-30", "2017-03-31", "2017-04-01"],
|
|
31
|
+
adjustedWorkdays: ["2017-04-08"],
|
|
32
|
+
},
|
|
33
|
+
};
|
package/data/2018.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2018,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2018年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2017〕12号",
|
|
6
|
+
publishedAt: "2017-11-30",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/201711/30/415918/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2017-12-30", end: "2018-01-01" },
|
|
11
|
+
{ name: "春节", start: "2018-02-15", end: "2018-02-21" },
|
|
12
|
+
{ name: "清明节", start: "2018-04-05", end: "2018-04-07" },
|
|
13
|
+
{ name: "劳动节", start: "2018-04-29", end: "2018-05-01" },
|
|
14
|
+
{ name: "端午节", start: "2018-06-16", end: "2018-06-18" },
|
|
15
|
+
{ name: "中秋节", start: "2018-09-22", end: "2018-09-24" },
|
|
16
|
+
{ name: "国庆节", start: "2018-10-01", end: "2018-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2018-02-11", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2018-02-24", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2018-04-08", relatedHoliday: "清明节" },
|
|
22
|
+
{ date: "2018-04-28", relatedHoliday: "劳动节" },
|
|
23
|
+
{ date: "2018-09-29", relatedHoliday: "国庆节" },
|
|
24
|
+
{ date: "2018-09-30", relatedHoliday: "国庆节" },
|
|
25
|
+
],
|
|
26
|
+
guangxiSanyuesan: {
|
|
27
|
+
name: "广西三月三",
|
|
28
|
+
daysOff: ["2018-04-18", "2018-04-19", "2018-04-20"],
|
|
29
|
+
adjustedWorkdays: ["2018-04-15"],
|
|
30
|
+
},
|
|
31
|
+
};
|
package/data/2019.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2019,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2019年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2018〕15号",
|
|
6
|
+
publishedAt: "2018-12-06",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/201812/06/432792/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2018-12-30", end: "2019-01-01" },
|
|
11
|
+
{ name: "春节", start: "2019-02-04", end: "2019-02-10" },
|
|
12
|
+
{ name: "清明节", start: "2019-04-05", end: "2019-04-07" },
|
|
13
|
+
{
|
|
14
|
+
name: "劳动节",
|
|
15
|
+
start: "2019-05-01",
|
|
16
|
+
end: "2019-05-04",
|
|
17
|
+
sourceUrl:
|
|
18
|
+
"https://app.www.gov.cn/govdata/gov/201903/22/436883/article.html",
|
|
19
|
+
},
|
|
20
|
+
{ name: "端午节", start: "2019-06-07", end: "2019-06-09" },
|
|
21
|
+
{ name: "中秋节", start: "2019-09-13", end: "2019-09-15" },
|
|
22
|
+
{ name: "国庆节", start: "2019-10-01", end: "2019-10-07" },
|
|
23
|
+
],
|
|
24
|
+
adjustedWorkdays: [
|
|
25
|
+
{ date: "2018-12-29", relatedHoliday: "元旦" },
|
|
26
|
+
{ date: "2019-02-02", relatedHoliday: "春节" },
|
|
27
|
+
{ date: "2019-02-03", relatedHoliday: "春节" },
|
|
28
|
+
{
|
|
29
|
+
date: "2019-04-28",
|
|
30
|
+
relatedHoliday: "劳动节",
|
|
31
|
+
sourceUrl:
|
|
32
|
+
"https://app.www.gov.cn/govdata/gov/201903/22/436883/article.html",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
date: "2019-05-05",
|
|
36
|
+
relatedHoliday: "劳动节",
|
|
37
|
+
sourceUrl:
|
|
38
|
+
"https://app.www.gov.cn/govdata/gov/201903/22/436883/article.html",
|
|
39
|
+
},
|
|
40
|
+
{ date: "2019-09-29", relatedHoliday: "国庆节" },
|
|
41
|
+
{ date: "2019-10-12", relatedHoliday: "国庆节" },
|
|
42
|
+
],
|
|
43
|
+
guangxiSanyuesan: {
|
|
44
|
+
name: "广西三月三",
|
|
45
|
+
daysOff: ["2019-04-04", "2019-04-08"],
|
|
46
|
+
adjustedWorkdays: [],
|
|
47
|
+
},
|
|
48
|
+
};
|
package/data/2020.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2020,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2020年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2019〕16号",
|
|
6
|
+
publishedAt: "2019-11-21",
|
|
7
|
+
url: "https://big5.www.gov.cn/gate/big5/www.gov.cn/zhengce/content/2019-11/21/content_5454164.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2020-01-01", end: "2020-01-01" },
|
|
11
|
+
{
|
|
12
|
+
name: "春节",
|
|
13
|
+
start: "2020-01-24",
|
|
14
|
+
end: "2020-02-02",
|
|
15
|
+
sourceUrl:
|
|
16
|
+
"https://app.www.gov.cn/govdata/gov/202001/27/453442/article.html",
|
|
17
|
+
},
|
|
18
|
+
{ name: "清明节", start: "2020-04-04", end: "2020-04-06" },
|
|
19
|
+
{ name: "劳动节", start: "2020-05-01", end: "2020-05-05" },
|
|
20
|
+
{ name: "端午节", start: "2020-06-25", end: "2020-06-27" },
|
|
21
|
+
{
|
|
22
|
+
name: "国庆节、中秋节",
|
|
23
|
+
start: "2020-10-01",
|
|
24
|
+
end: "2020-10-08",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
adjustedWorkdays: [
|
|
28
|
+
{ date: "2020-01-19", relatedHoliday: "春节" },
|
|
29
|
+
{ date: "2020-04-26", relatedHoliday: "劳动节" },
|
|
30
|
+
{ date: "2020-05-09", relatedHoliday: "劳动节" },
|
|
31
|
+
{ date: "2020-06-28", relatedHoliday: "端午节" },
|
|
32
|
+
{ date: "2020-09-27", relatedHoliday: "国庆节、中秋节" },
|
|
33
|
+
{ date: "2020-10-10", relatedHoliday: "国庆节、中秋节" },
|
|
34
|
+
],
|
|
35
|
+
guangxiSanyuesan: {
|
|
36
|
+
name: "广西三月三",
|
|
37
|
+
daysOff: [],
|
|
38
|
+
adjustedWorkdays: [],
|
|
39
|
+
},
|
|
40
|
+
};
|
package/data/2021.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2021,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2021年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2020〕27号",
|
|
6
|
+
publishedAt: "2020-11-25",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/202011/26/465359/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2021-01-01", end: "2021-01-03" },
|
|
11
|
+
{ name: "春节", start: "2021-02-11", end: "2021-02-17" },
|
|
12
|
+
{ name: "清明节", start: "2021-04-03", end: "2021-04-05" },
|
|
13
|
+
{ name: "劳动节", start: "2021-05-01", end: "2021-05-05" },
|
|
14
|
+
{ name: "端午节", start: "2021-06-12", end: "2021-06-14" },
|
|
15
|
+
{ name: "中秋节", start: "2021-09-19", end: "2021-09-21" },
|
|
16
|
+
{ name: "国庆节", start: "2021-10-01", end: "2021-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2021-02-07", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2021-02-20", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2021-04-25", relatedHoliday: "劳动节" },
|
|
22
|
+
{ date: "2021-05-08", relatedHoliday: "劳动节" },
|
|
23
|
+
{ date: "2021-09-18", relatedHoliday: "中秋节" },
|
|
24
|
+
{ date: "2021-09-26", relatedHoliday: "国庆节" },
|
|
25
|
+
{ date: "2021-10-09", relatedHoliday: "国庆节" },
|
|
26
|
+
],
|
|
27
|
+
guangxiSanyuesan: {
|
|
28
|
+
name: "广西三月三",
|
|
29
|
+
daysOff: ["2021-04-14", "2021-04-15", "2021-04-16"],
|
|
30
|
+
adjustedWorkdays: ["2021-04-18"],
|
|
31
|
+
},
|
|
32
|
+
};
|
package/data/2022.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2022,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2022年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2021〕11号",
|
|
6
|
+
publishedAt: "2021-10-25",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/202110/25/477428/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2022-01-01", end: "2022-01-03" },
|
|
11
|
+
{ name: "春节", start: "2022-01-31", end: "2022-02-06" },
|
|
12
|
+
{ name: "清明节", start: "2022-04-03", end: "2022-04-05" },
|
|
13
|
+
{ name: "劳动节", start: "2022-04-30", end: "2022-05-04" },
|
|
14
|
+
{ name: "端午节", start: "2022-06-03", end: "2022-06-05" },
|
|
15
|
+
{ name: "中秋节", start: "2022-09-10", end: "2022-09-12" },
|
|
16
|
+
{ name: "国庆节", start: "2022-10-01", end: "2022-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2022-01-29", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2022-01-30", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2022-04-02", relatedHoliday: "清明节" },
|
|
22
|
+
{ date: "2022-04-24", relatedHoliday: "劳动节" },
|
|
23
|
+
{ date: "2022-05-07", relatedHoliday: "劳动节" },
|
|
24
|
+
{ date: "2022-10-08", relatedHoliday: "国庆节" },
|
|
25
|
+
{ date: "2022-10-09", relatedHoliday: "国庆节" },
|
|
26
|
+
],
|
|
27
|
+
guangxiSanyuesan: {
|
|
28
|
+
name: "广西三月三",
|
|
29
|
+
daysOff: ["2022-04-02", "2022-04-06"],
|
|
30
|
+
adjustedWorkdays: [],
|
|
31
|
+
},
|
|
32
|
+
};
|
package/data/2023.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2023,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2023年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2022〕16号",
|
|
6
|
+
publishedAt: "2022-12-08",
|
|
7
|
+
url: "https://app.www.gov.cn/govdata/gov/202212/08/495070/article.html",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2022-12-31", end: "2023-01-02" },
|
|
11
|
+
{ name: "春节", start: "2023-01-21", end: "2023-01-27" },
|
|
12
|
+
{ name: "清明节", start: "2023-04-05", end: "2023-04-05" },
|
|
13
|
+
{ name: "劳动节", start: "2023-04-29", end: "2023-05-03" },
|
|
14
|
+
{ name: "端午节", start: "2023-06-22", end: "2023-06-24" },
|
|
15
|
+
{
|
|
16
|
+
name: "中秋节、国庆节",
|
|
17
|
+
start: "2023-09-29",
|
|
18
|
+
end: "2023-10-06",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
adjustedWorkdays: [
|
|
22
|
+
{ date: "2023-01-28", relatedHoliday: "春节" },
|
|
23
|
+
{ date: "2023-01-29", relatedHoliday: "春节" },
|
|
24
|
+
{ date: "2023-04-23", relatedHoliday: "劳动节" },
|
|
25
|
+
{ date: "2023-05-06", relatedHoliday: "劳动节" },
|
|
26
|
+
{ date: "2023-06-25", relatedHoliday: "端午节" },
|
|
27
|
+
{ date: "2023-10-07", relatedHoliday: "中秋节、国庆节" },
|
|
28
|
+
{ date: "2023-10-08", relatedHoliday: "中秋节、国庆节" },
|
|
29
|
+
],
|
|
30
|
+
guangxiSanyuesan: {
|
|
31
|
+
name: "广西三月三",
|
|
32
|
+
daysOff: ["2023-04-21", "2023-04-22", "2023-04-23"],
|
|
33
|
+
adjustedWorkdays: [],
|
|
34
|
+
},
|
|
35
|
+
};
|
package/data/2024.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2024,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2024年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2023〕7号",
|
|
6
|
+
publishedAt: "2023-10-25",
|
|
7
|
+
url: "https://www.gov.cn/zhengce/content/202310/content_6911527.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2024-01-01", end: "2024-01-01" },
|
|
11
|
+
{ name: "春节", start: "2024-02-10", end: "2024-02-17" },
|
|
12
|
+
{ name: "清明节", start: "2024-04-04", end: "2024-04-06" },
|
|
13
|
+
{ name: "劳动节", start: "2024-05-01", end: "2024-05-05" },
|
|
14
|
+
{ name: "端午节", start: "2024-06-10", end: "2024-06-10" },
|
|
15
|
+
{ name: "中秋节", start: "2024-09-15", end: "2024-09-17" },
|
|
16
|
+
{ name: "国庆节", start: "2024-10-01", end: "2024-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2024-02-04", relatedHoliday: "春节" },
|
|
20
|
+
{ date: "2024-02-18", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2024-04-07", relatedHoliday: "清明节" },
|
|
22
|
+
{ date: "2024-04-28", relatedHoliday: "劳动节" },
|
|
23
|
+
{ date: "2024-05-11", relatedHoliday: "劳动节" },
|
|
24
|
+
{ date: "2024-09-14", relatedHoliday: "中秋节" },
|
|
25
|
+
{ date: "2024-09-29", relatedHoliday: "国庆节" },
|
|
26
|
+
{ date: "2024-10-12", relatedHoliday: "国庆节" },
|
|
27
|
+
],
|
|
28
|
+
guangxiSanyuesan: {
|
|
29
|
+
name: "广西三月三",
|
|
30
|
+
daysOff: ["2024-04-11", "2024-04-12"],
|
|
31
|
+
adjustedWorkdays: [],
|
|
32
|
+
},
|
|
33
|
+
};
|
package/data/2025.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2025,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2025年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2024〕12号",
|
|
6
|
+
publishedAt: "2024-11-12",
|
|
7
|
+
url: "https://www.gov.cn/zhengce/zhengceku/202411/content_6986383.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2025-01-01", end: "2025-01-01" },
|
|
11
|
+
{ name: "春节", start: "2025-01-28", end: "2025-02-04" },
|
|
12
|
+
{ name: "清明节", start: "2025-04-04", end: "2025-04-06" },
|
|
13
|
+
{ name: "劳动节", start: "2025-05-01", end: "2025-05-05" },
|
|
14
|
+
{ name: "端午节", start: "2025-05-31", end: "2025-06-02" },
|
|
15
|
+
{
|
|
16
|
+
name: "国庆节、中秋节",
|
|
17
|
+
start: "2025-10-01",
|
|
18
|
+
end: "2025-10-08",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
adjustedWorkdays: [
|
|
22
|
+
{ date: "2025-01-26", relatedHoliday: "春节" },
|
|
23
|
+
{ date: "2025-02-08", relatedHoliday: "春节" },
|
|
24
|
+
{ date: "2025-04-27", relatedHoliday: "劳动节" },
|
|
25
|
+
{ date: "2025-09-28", relatedHoliday: "国庆节、中秋节" },
|
|
26
|
+
{ date: "2025-10-11", relatedHoliday: "国庆节、中秋节" },
|
|
27
|
+
],
|
|
28
|
+
guangxiSanyuesan: {
|
|
29
|
+
name: "广西三月三",
|
|
30
|
+
daysOff: ["2025-03-31", "2025-04-01"],
|
|
31
|
+
adjustedWorkdays: [],
|
|
32
|
+
},
|
|
33
|
+
};
|
package/data/2026.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
year: 2026,
|
|
3
|
+
source: {
|
|
4
|
+
title: "国务院办公厅关于2026年部分节假日安排的通知",
|
|
5
|
+
documentNumber: "国办发明电〔2025〕7号",
|
|
6
|
+
publishedAt: "2025-11-04",
|
|
7
|
+
url: "https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm",
|
|
8
|
+
},
|
|
9
|
+
holidays: [
|
|
10
|
+
{ name: "元旦", start: "2026-01-01", end: "2026-01-03" },
|
|
11
|
+
{ name: "春节", start: "2026-02-15", end: "2026-02-23" },
|
|
12
|
+
{ name: "清明节", start: "2026-04-04", end: "2026-04-06" },
|
|
13
|
+
{ name: "劳动节", start: "2026-05-01", end: "2026-05-05" },
|
|
14
|
+
{ name: "端午节", start: "2026-06-19", end: "2026-06-21" },
|
|
15
|
+
{ name: "中秋节", start: "2026-09-25", end: "2026-09-27" },
|
|
16
|
+
{ name: "国庆节", start: "2026-10-01", end: "2026-10-07" },
|
|
17
|
+
],
|
|
18
|
+
adjustedWorkdays: [
|
|
19
|
+
{ date: "2026-01-04", relatedHoliday: "元旦" },
|
|
20
|
+
{ date: "2026-02-14", relatedHoliday: "春节" },
|
|
21
|
+
{ date: "2026-02-28", relatedHoliday: "春节" },
|
|
22
|
+
{ date: "2026-05-09", relatedHoliday: "劳动节" },
|
|
23
|
+
{ date: "2026-09-20", relatedHoliday: "国庆节" },
|
|
24
|
+
{ date: "2026-10-10", relatedHoliday: "国庆节" },
|
|
25
|
+
],
|
|
26
|
+
guangxiSanyuesan: {
|
|
27
|
+
name: "广西三月三",
|
|
28
|
+
daysOff: [
|
|
29
|
+
"2026-04-17",
|
|
30
|
+
"2026-04-18",
|
|
31
|
+
"2026-04-19",
|
|
32
|
+
"2026-04-20",
|
|
33
|
+
],
|
|
34
|
+
adjustedWorkdays: [],
|
|
35
|
+
},
|
|
36
|
+
};
|
package/data/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import schedule2014 from "./2014.js";
|
|
2
|
+
import schedule2015 from "./2015.js";
|
|
3
|
+
import schedule2016 from "./2016.js";
|
|
4
|
+
import schedule2017 from "./2017.js";
|
|
5
|
+
import schedule2018 from "./2018.js";
|
|
6
|
+
import schedule2019 from "./2019.js";
|
|
7
|
+
import schedule2020 from "./2020.js";
|
|
8
|
+
import schedule2021 from "./2021.js";
|
|
9
|
+
import schedule2022 from "./2022.js";
|
|
10
|
+
import schedule2023 from "./2023.js";
|
|
11
|
+
import schedule2024 from "./2024.js";
|
|
12
|
+
import schedule2025 from "./2025.js";
|
|
13
|
+
import schedule2026 from "./2026.js";
|
|
14
|
+
|
|
15
|
+
export const schedules = [
|
|
16
|
+
schedule2014,
|
|
17
|
+
schedule2015,
|
|
18
|
+
schedule2016,
|
|
19
|
+
schedule2017,
|
|
20
|
+
schedule2018,
|
|
21
|
+
schedule2019,
|
|
22
|
+
schedule2020,
|
|
23
|
+
schedule2021,
|
|
24
|
+
schedule2022,
|
|
25
|
+
schedule2023,
|
|
26
|
+
schedule2024,
|
|
27
|
+
schedule2025,
|
|
28
|
+
schedule2026,
|
|
29
|
+
];
|
package/index.js
ADDED
package/lib/date.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const INVALID_DATE_MESSAGE =
|
|
2
|
+
"date must be a valid Date object or date string";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse and validate a YYYY-MM-DD calendar date.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} value
|
|
8
|
+
* @returns {{ year: number, month: number, day: number, timestamp: number }}
|
|
9
|
+
*/
|
|
10
|
+
export function parseDateKey(value) {
|
|
11
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
12
|
+
|
|
13
|
+
if (!match) {
|
|
14
|
+
throw new TypeError(`invalid calendar date: ${value}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [, year, month, day] = match.map(Number);
|
|
18
|
+
const timestamp = Date.UTC(year, month - 1, day);
|
|
19
|
+
const parsed = new Date(timestamp);
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
parsed.getUTCFullYear() !== year ||
|
|
23
|
+
parsed.getUTCMonth() !== month - 1 ||
|
|
24
|
+
parsed.getUTCDate() !== day
|
|
25
|
+
) {
|
|
26
|
+
throw new TypeError(`invalid calendar date: ${value}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { year, month, day, timestamp };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a public Date or string input using local calendar semantics.
|
|
34
|
+
*
|
|
35
|
+
* @param {Date|string} input
|
|
36
|
+
* @returns {Date}
|
|
37
|
+
*/
|
|
38
|
+
export function parseDateInput(input) {
|
|
39
|
+
let date;
|
|
40
|
+
|
|
41
|
+
if (input instanceof Date) {
|
|
42
|
+
date = input;
|
|
43
|
+
} else if (typeof input === "string" && input.trim() !== "") {
|
|
44
|
+
const value = input.trim();
|
|
45
|
+
|
|
46
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
47
|
+
const { year, month, day } = parseDateKey(value);
|
|
48
|
+
date = new Date(0);
|
|
49
|
+
date.setHours(0, 0, 0, 0);
|
|
50
|
+
date.setFullYear(year, month - 1, day);
|
|
51
|
+
} else {
|
|
52
|
+
date = new Date(value);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
throw new TypeError(INVALID_DATE_MESSAGE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (Number.isNaN(date.getTime())) {
|
|
59
|
+
throw new TypeError(INVALID_DATE_MESSAGE);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return date;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a Date as a local YYYY-MM-DD calendar key.
|
|
67
|
+
*
|
|
68
|
+
* @param {Date} date
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
export function toDateKey(date) {
|
|
72
|
+
const year = String(date.getFullYear()).padStart(4, "0");
|
|
73
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
74
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
75
|
+
return `${year}-${month}-${day}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format a UTC timestamp as YYYY-MM-DD.
|
|
80
|
+
*
|
|
81
|
+
* @param {number} timestamp
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export function utcTimestampToDateKey(timestamp) {
|
|
85
|
+
return new Date(timestamp).toISOString().slice(0, 10);
|
|
86
|
+
}
|
package/lib/schedule.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { schedules } from "../data/index.js";
|
|
2
|
+
import {
|
|
3
|
+
parseDateInput,
|
|
4
|
+
parseDateKey,
|
|
5
|
+
toDateKey,
|
|
6
|
+
utcTimestampToDateKey,
|
|
7
|
+
} from "./date.js";
|
|
8
|
+
|
|
9
|
+
const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
|
|
10
|
+
const schedulesByYear = new Map();
|
|
11
|
+
const exceptionsByDate = new Map();
|
|
12
|
+
const guangxiSanyuesanByDate = new Map();
|
|
13
|
+
const guangxiSanyuesanWorkdaysByDate = new Map();
|
|
14
|
+
const guangxiSanyuesanPeriodDates = new Set();
|
|
15
|
+
|
|
16
|
+
export class ScheduleNotFoundError extends Error {
|
|
17
|
+
/** @param {number} year */
|
|
18
|
+
constructor(year) {
|
|
19
|
+
super(`official workday schedule is not available for ${year}`);
|
|
20
|
+
this.name = "ScheduleNotFoundError";
|
|
21
|
+
this.year = year;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertNonEmptyString(value, field) {
|
|
26
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
27
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function registerSchedule(schedule) {
|
|
32
|
+
if (!Number.isInteger(schedule.year)) {
|
|
33
|
+
throw new TypeError("schedule year must be an integer");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (schedulesByYear.has(schedule.year)) {
|
|
37
|
+
throw new TypeError(`duplicate schedule year: ${schedule.year}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
assertNonEmptyString(schedule.source?.title, "schedule source title");
|
|
41
|
+
assertNonEmptyString(schedule.source?.documentNumber, "schedule document number");
|
|
42
|
+
parseDateKey(schedule.source?.publishedAt);
|
|
43
|
+
assertNonEmptyString(schedule.source?.url, "schedule source URL");
|
|
44
|
+
|
|
45
|
+
schedulesByYear.set(schedule.year, schedule);
|
|
46
|
+
|
|
47
|
+
for (const holiday of schedule.holidays) {
|
|
48
|
+
assertNonEmptyString(holiday.name, "holiday name");
|
|
49
|
+
const start = parseDateKey(holiday.start);
|
|
50
|
+
const end = parseDateKey(holiday.end);
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
Math.abs(start.year - schedule.year) > 1 ||
|
|
54
|
+
Math.abs(end.year - schedule.year) > 1
|
|
55
|
+
) {
|
|
56
|
+
throw new TypeError(`holiday is too far from ${schedule.year}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (end.timestamp < start.timestamp) {
|
|
60
|
+
throw new TypeError(`holiday starts after it ends: ${holiday.name}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (
|
|
64
|
+
let timestamp = start.timestamp;
|
|
65
|
+
timestamp <= end.timestamp;
|
|
66
|
+
timestamp += ONE_DAY_IN_MILLISECONDS
|
|
67
|
+
) {
|
|
68
|
+
const date = utcTimestampToDateKey(timestamp);
|
|
69
|
+
|
|
70
|
+
if (exceptionsByDate.has(date)) {
|
|
71
|
+
throw new TypeError(`conflicting schedule entry: ${date}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exceptionsByDate.set(date, {
|
|
75
|
+
type: "holiday",
|
|
76
|
+
isWorkday: false,
|
|
77
|
+
holidayName: holiday.name,
|
|
78
|
+
sourceUrl: holiday.sourceUrl ?? schedule.source.url,
|
|
79
|
+
schedule,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const adjustedWorkday of schedule.adjustedWorkdays) {
|
|
85
|
+
assertNonEmptyString(
|
|
86
|
+
adjustedWorkday.relatedHoliday,
|
|
87
|
+
"adjusted workday related holiday",
|
|
88
|
+
);
|
|
89
|
+
const { year } = parseDateKey(adjustedWorkday.date);
|
|
90
|
+
|
|
91
|
+
if (Math.abs(year - schedule.year) > 1) {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
`adjusted workday is too far from ${schedule.year}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (exceptionsByDate.has(adjustedWorkday.date)) {
|
|
98
|
+
throw new TypeError(`conflicting schedule entry: ${adjustedWorkday.date}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
exceptionsByDate.set(adjustedWorkday.date, {
|
|
102
|
+
type: "adjusted-workday",
|
|
103
|
+
isWorkday: true,
|
|
104
|
+
holidayName: adjustedWorkday.relatedHoliday,
|
|
105
|
+
sourceUrl: adjustedWorkday.sourceUrl ?? schedule.source.url,
|
|
106
|
+
schedule,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
assertNonEmptyString(
|
|
111
|
+
schedule.guangxiSanyuesan?.name,
|
|
112
|
+
"Guangxi Sanyuesan name",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!Array.isArray(schedule.guangxiSanyuesan?.daysOff)) {
|
|
116
|
+
throw new TypeError("Guangxi Sanyuesan daysOff must be an array");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!Array.isArray(schedule.guangxiSanyuesan?.adjustedWorkdays)) {
|
|
120
|
+
throw new TypeError(
|
|
121
|
+
"Guangxi Sanyuesan adjustedWorkdays must be an array",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const date of schedule.guangxiSanyuesan.daysOff) {
|
|
126
|
+
const { year } = parseDateKey(date);
|
|
127
|
+
|
|
128
|
+
if (year !== schedule.year) {
|
|
129
|
+
throw new TypeError(
|
|
130
|
+
`Guangxi Sanyuesan day off must be within ${schedule.year}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (guangxiSanyuesanByDate.has(date)) {
|
|
135
|
+
throw new TypeError(`duplicate Guangxi Sanyuesan day off: ${date}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
guangxiSanyuesanByDate.set(date, {
|
|
139
|
+
holidayName: schedule.guangxiSanyuesan.name,
|
|
140
|
+
schedule,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const date of schedule.guangxiSanyuesan.adjustedWorkdays) {
|
|
145
|
+
const { year } = parseDateKey(date);
|
|
146
|
+
|
|
147
|
+
if (year !== schedule.year) {
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
`Guangxi Sanyuesan adjusted workday must be within ${schedule.year}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
guangxiSanyuesanByDate.has(date) ||
|
|
155
|
+
guangxiSanyuesanWorkdaysByDate.has(date)
|
|
156
|
+
) {
|
|
157
|
+
throw new TypeError(`conflicting Guangxi Sanyuesan entry: ${date}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
guangxiSanyuesanWorkdaysByDate.set(date, {
|
|
161
|
+
holidayName: schedule.guangxiSanyuesan.name,
|
|
162
|
+
schedule,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const allSanyuesanDates = [
|
|
167
|
+
...schedule.guangxiSanyuesan.daysOff,
|
|
168
|
+
...schedule.guangxiSanyuesan.adjustedWorkdays,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
if (allSanyuesanDates.length > 0) {
|
|
172
|
+
const timestamps = allSanyuesanDates.map((d) => parseDateKey(d).timestamp);
|
|
173
|
+
const minTs = Math.min(...timestamps);
|
|
174
|
+
const maxTs = Math.max(...timestamps);
|
|
175
|
+
|
|
176
|
+
for (
|
|
177
|
+
let ts = minTs;
|
|
178
|
+
ts <= maxTs;
|
|
179
|
+
ts += ONE_DAY_IN_MILLISECONDS
|
|
180
|
+
) {
|
|
181
|
+
guangxiSanyuesanPeriodDates.add(utcTimestampToDateKey(ts));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const schedule of schedules) {
|
|
187
|
+
registerSchedule(schedule);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function normalizeOptions(options) {
|
|
191
|
+
if (options === undefined) {
|
|
192
|
+
return {
|
|
193
|
+
requireOfficialSchedule: false,
|
|
194
|
+
enableGuangxiSanyuesan: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
199
|
+
throw new TypeError("options must be an object");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const requireOfficialSchedule = options.requireOfficialSchedule ?? false;
|
|
203
|
+
const enableGuangxiSanyuesan = options.enableGuangxiSanyuesan ?? false;
|
|
204
|
+
|
|
205
|
+
if (typeof requireOfficialSchedule !== "boolean") {
|
|
206
|
+
throw new TypeError("requireOfficialSchedule must be a boolean");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof enableGuangxiSanyuesan !== "boolean") {
|
|
210
|
+
throw new TypeError("enableGuangxiSanyuesan must be a boolean");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { requireOfficialSchedule, enableGuangxiSanyuesan };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Return an explainable workday classification for a date.
|
|
218
|
+
*
|
|
219
|
+
* @param {Date|string} input
|
|
220
|
+
* @param {{
|
|
221
|
+
* requireOfficialSchedule?: boolean,
|
|
222
|
+
* enableGuangxiSanyuesan?: boolean
|
|
223
|
+
* }} [options]
|
|
224
|
+
* @returns {{
|
|
225
|
+
* date: string,
|
|
226
|
+
* isWorkday: boolean,
|
|
227
|
+
* type: "regular-workday"|"weekend"|"holiday"|"adjusted-workday",
|
|
228
|
+
* holidayName: string|null,
|
|
229
|
+
* scheduleYear: number|null,
|
|
230
|
+
* scheduleStatus: "official"|"weekday-fallback",
|
|
231
|
+
* sourceUrl: string|null
|
|
232
|
+
* }}
|
|
233
|
+
*/
|
|
234
|
+
export function getDayInfo(input, options) {
|
|
235
|
+
const date = parseDateInput(input);
|
|
236
|
+
const normalizedOptions = normalizeOptions(options);
|
|
237
|
+
const dateKey = toDateKey(date);
|
|
238
|
+
const year = date.getFullYear();
|
|
239
|
+
const schedule = schedulesByYear.get(year);
|
|
240
|
+
|
|
241
|
+
if (!schedule && normalizedOptions.requireOfficialSchedule) {
|
|
242
|
+
throw new ScheduleNotFoundError(year);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const guangxiSanyuesanWorkday = normalizedOptions.enableGuangxiSanyuesan
|
|
246
|
+
? guangxiSanyuesanWorkdaysByDate.get(dateKey)
|
|
247
|
+
: undefined;
|
|
248
|
+
|
|
249
|
+
if (guangxiSanyuesanWorkday) {
|
|
250
|
+
return {
|
|
251
|
+
date: dateKey,
|
|
252
|
+
isWorkday: true,
|
|
253
|
+
type: "adjusted-workday",
|
|
254
|
+
holidayName: guangxiSanyuesanWorkday.holidayName,
|
|
255
|
+
scheduleYear: guangxiSanyuesanWorkday.schedule.year,
|
|
256
|
+
scheduleStatus: "official",
|
|
257
|
+
sourceUrl: null,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const guangxiSanyuesan = normalizedOptions.enableGuangxiSanyuesan
|
|
262
|
+
? guangxiSanyuesanByDate.get(dateKey)
|
|
263
|
+
: undefined;
|
|
264
|
+
|
|
265
|
+
if (guangxiSanyuesan) {
|
|
266
|
+
return {
|
|
267
|
+
date: dateKey,
|
|
268
|
+
isWorkday: false,
|
|
269
|
+
type: "holiday",
|
|
270
|
+
holidayName: guangxiSanyuesan.holidayName,
|
|
271
|
+
scheduleYear: guangxiSanyuesan.schedule.year,
|
|
272
|
+
scheduleStatus: "official",
|
|
273
|
+
sourceUrl: null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const inSanyuesanPeriod =
|
|
278
|
+
normalizedOptions.enableGuangxiSanyuesan &&
|
|
279
|
+
guangxiSanyuesanPeriodDates.has(dateKey);
|
|
280
|
+
|
|
281
|
+
if (!inSanyuesanPeriod) {
|
|
282
|
+
const exception = exceptionsByDate.get(dateKey);
|
|
283
|
+
|
|
284
|
+
if (exception) {
|
|
285
|
+
return {
|
|
286
|
+
date: dateKey,
|
|
287
|
+
isWorkday: exception.isWorkday,
|
|
288
|
+
type: exception.type,
|
|
289
|
+
holidayName: exception.holidayName,
|
|
290
|
+
scheduleYear: exception.schedule.year,
|
|
291
|
+
scheduleStatus: "official",
|
|
292
|
+
sourceUrl: exception.sourceUrl,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const isWorkday = date.getDay() >= 1 && date.getDay() <= 5;
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
date: dateKey,
|
|
301
|
+
isWorkday,
|
|
302
|
+
type: isWorkday ? "regular-workday" : "weekend",
|
|
303
|
+
holidayName: null,
|
|
304
|
+
scheduleYear: schedule?.year ?? null,
|
|
305
|
+
scheduleStatus: schedule ? "official" : "weekday-fallback",
|
|
306
|
+
sourceUrl: schedule?.source.url ?? null,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Determine whether a date is a workday in mainland China's official schedule.
|
|
312
|
+
*
|
|
313
|
+
* @param {Date|string} input
|
|
314
|
+
* @param {{
|
|
315
|
+
* requireOfficialSchedule?: boolean,
|
|
316
|
+
* enableGuangxiSanyuesan?: boolean
|
|
317
|
+
* }} [options]
|
|
318
|
+
* @returns {boolean}
|
|
319
|
+
*/
|
|
320
|
+
export function isWorkday(input, options) {
|
|
321
|
+
return getDayInfo(input, options).isWorkday;
|
|
322
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@weiensong/chinese-calendar",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Determine whether a date is a workday in mainland China, respecting State Council holiday and adjusted-workday schedules",
|
|
5
|
+
"author": "weiensong",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": "./index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"lib",
|
|
12
|
+
"data"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/touero/chinese-calendar.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/touero/chinese-calendar#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/touero/chinese-calendar/issues"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"registry": "https://registry.npmjs.org"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"chinese-calendar",
|
|
30
|
+
"workday",
|
|
31
|
+
"holiday",
|
|
32
|
+
"china",
|
|
33
|
+
"中国节假日",
|
|
34
|
+
"工作日",
|
|
35
|
+
"调休"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|