@sanguo-dev/calendar 0.0.0-stage → 1.0.1
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 +228 -2
- package/dist/index.cjs +1766 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.mts +269 -0
- package/dist/index.iife.js +1769 -0
- package/dist/index.mjs +1760 -0
- package/dist/index.umd.js +1770 -0
- package/package.json +66 -4
- package/src/config.ts +198 -0
- package/src/data/Calendar.ts +599 -0
- package/src/data/festival.json +195 -0
- package/src/data/rest.json +74 -0
- package/src/index.ts +317 -0
- package/src/types.ts +146 -0
- package/src/utils/Assert.ts +230 -0
- package/src/utils/LunarFunction.ts +215 -0
- package/src/utils/SetFestival.ts +40 -0
- package/src/utils/SetRest.ts +84 -0
- package/src/utils/SolarFunction.ts +162 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1766 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
// src/data/Calendar.ts
|
|
6
|
+
/**
|
|
7
|
+
* 农历1900-2100的润大小信息表
|
|
8
|
+
* @Array Of Property
|
|
9
|
+
* @return Hex
|
|
10
|
+
*/
|
|
11
|
+
const LunarInfo = [
|
|
12
|
+
19416,
|
|
13
|
+
19168,
|
|
14
|
+
42352,
|
|
15
|
+
21717,
|
|
16
|
+
53856,
|
|
17
|
+
55632,
|
|
18
|
+
91476,
|
|
19
|
+
22176,
|
|
20
|
+
39632,
|
|
21
|
+
21970,
|
|
22
|
+
19168,
|
|
23
|
+
42422,
|
|
24
|
+
42192,
|
|
25
|
+
53840,
|
|
26
|
+
119381,
|
|
27
|
+
46400,
|
|
28
|
+
54944,
|
|
29
|
+
44450,
|
|
30
|
+
38320,
|
|
31
|
+
84343,
|
|
32
|
+
18800,
|
|
33
|
+
42160,
|
|
34
|
+
46261,
|
|
35
|
+
27216,
|
|
36
|
+
27968,
|
|
37
|
+
109396,
|
|
38
|
+
11104,
|
|
39
|
+
38256,
|
|
40
|
+
21234,
|
|
41
|
+
18800,
|
|
42
|
+
25958,
|
|
43
|
+
54432,
|
|
44
|
+
59984,
|
|
45
|
+
28309,
|
|
46
|
+
23248,
|
|
47
|
+
11104,
|
|
48
|
+
100067,
|
|
49
|
+
37600,
|
|
50
|
+
116951,
|
|
51
|
+
51536,
|
|
52
|
+
54432,
|
|
53
|
+
120998,
|
|
54
|
+
46416,
|
|
55
|
+
22176,
|
|
56
|
+
107956,
|
|
57
|
+
9680,
|
|
58
|
+
37584,
|
|
59
|
+
53938,
|
|
60
|
+
43344,
|
|
61
|
+
46423,
|
|
62
|
+
27808,
|
|
63
|
+
46416,
|
|
64
|
+
86869,
|
|
65
|
+
19872,
|
|
66
|
+
42416,
|
|
67
|
+
83315,
|
|
68
|
+
21168,
|
|
69
|
+
43432,
|
|
70
|
+
59728,
|
|
71
|
+
27296,
|
|
72
|
+
44710,
|
|
73
|
+
43856,
|
|
74
|
+
19296,
|
|
75
|
+
43748,
|
|
76
|
+
42352,
|
|
77
|
+
21088,
|
|
78
|
+
62051,
|
|
79
|
+
55632,
|
|
80
|
+
23383,
|
|
81
|
+
22176,
|
|
82
|
+
38608,
|
|
83
|
+
19925,
|
|
84
|
+
19152,
|
|
85
|
+
42192,
|
|
86
|
+
54484,
|
|
87
|
+
53840,
|
|
88
|
+
54616,
|
|
89
|
+
46400,
|
|
90
|
+
46752,
|
|
91
|
+
103846,
|
|
92
|
+
38320,
|
|
93
|
+
18864,
|
|
94
|
+
43380,
|
|
95
|
+
42160,
|
|
96
|
+
45690,
|
|
97
|
+
27216,
|
|
98
|
+
27968,
|
|
99
|
+
44870,
|
|
100
|
+
43872,
|
|
101
|
+
38256,
|
|
102
|
+
19189,
|
|
103
|
+
18800,
|
|
104
|
+
25776,
|
|
105
|
+
29859,
|
|
106
|
+
59984,
|
|
107
|
+
27480,
|
|
108
|
+
21952,
|
|
109
|
+
43872,
|
|
110
|
+
38613,
|
|
111
|
+
37600,
|
|
112
|
+
51552,
|
|
113
|
+
55636,
|
|
114
|
+
54432,
|
|
115
|
+
55888,
|
|
116
|
+
30034,
|
|
117
|
+
22176,
|
|
118
|
+
43959,
|
|
119
|
+
9680,
|
|
120
|
+
37584,
|
|
121
|
+
51893,
|
|
122
|
+
43344,
|
|
123
|
+
46240,
|
|
124
|
+
47780,
|
|
125
|
+
44368,
|
|
126
|
+
21977,
|
|
127
|
+
19360,
|
|
128
|
+
42416,
|
|
129
|
+
86390,
|
|
130
|
+
21168,
|
|
131
|
+
43312,
|
|
132
|
+
31060,
|
|
133
|
+
27296,
|
|
134
|
+
44368,
|
|
135
|
+
23378,
|
|
136
|
+
19296,
|
|
137
|
+
42726,
|
|
138
|
+
42208,
|
|
139
|
+
53856,
|
|
140
|
+
60005,
|
|
141
|
+
54576,
|
|
142
|
+
23200,
|
|
143
|
+
30371,
|
|
144
|
+
38608,
|
|
145
|
+
19195,
|
|
146
|
+
19152,
|
|
147
|
+
42192,
|
|
148
|
+
118966,
|
|
149
|
+
53840,
|
|
150
|
+
54560,
|
|
151
|
+
56645,
|
|
152
|
+
46496,
|
|
153
|
+
22224,
|
|
154
|
+
21938,
|
|
155
|
+
18864,
|
|
156
|
+
42359,
|
|
157
|
+
42160,
|
|
158
|
+
43600,
|
|
159
|
+
111189,
|
|
160
|
+
27936,
|
|
161
|
+
44448,
|
|
162
|
+
84835,
|
|
163
|
+
37744,
|
|
164
|
+
18936,
|
|
165
|
+
18800,
|
|
166
|
+
25776,
|
|
167
|
+
92326,
|
|
168
|
+
59984,
|
|
169
|
+
27424,
|
|
170
|
+
108228,
|
|
171
|
+
43744,
|
|
172
|
+
41696,
|
|
173
|
+
53987,
|
|
174
|
+
51552,
|
|
175
|
+
54615,
|
|
176
|
+
54432,
|
|
177
|
+
55888,
|
|
178
|
+
23893,
|
|
179
|
+
22176,
|
|
180
|
+
42704,
|
|
181
|
+
21972,
|
|
182
|
+
21200,
|
|
183
|
+
43448,
|
|
184
|
+
43344,
|
|
185
|
+
46240,
|
|
186
|
+
46758,
|
|
187
|
+
44368,
|
|
188
|
+
21920,
|
|
189
|
+
43940,
|
|
190
|
+
42416,
|
|
191
|
+
21168,
|
|
192
|
+
45683,
|
|
193
|
+
26928,
|
|
194
|
+
29495,
|
|
195
|
+
27296,
|
|
196
|
+
44368,
|
|
197
|
+
84821,
|
|
198
|
+
19296,
|
|
199
|
+
42352,
|
|
200
|
+
21732,
|
|
201
|
+
53600,
|
|
202
|
+
59752,
|
|
203
|
+
54560,
|
|
204
|
+
55968,
|
|
205
|
+
92838,
|
|
206
|
+
22224,
|
|
207
|
+
19168,
|
|
208
|
+
43476,
|
|
209
|
+
41680,
|
|
210
|
+
53584,
|
|
211
|
+
62034,
|
|
212
|
+
54560
|
|
213
|
+
];
|
|
214
|
+
/**
|
|
215
|
+
* 公历每个月份的天数普通表
|
|
216
|
+
* @Array Of Property
|
|
217
|
+
* @return Number
|
|
218
|
+
*/
|
|
219
|
+
const SolarMonth = [
|
|
220
|
+
31,
|
|
221
|
+
28,
|
|
222
|
+
31,
|
|
223
|
+
30,
|
|
224
|
+
31,
|
|
225
|
+
30,
|
|
226
|
+
31,
|
|
227
|
+
31,
|
|
228
|
+
30,
|
|
229
|
+
31,
|
|
230
|
+
30,
|
|
231
|
+
31
|
|
232
|
+
];
|
|
233
|
+
/**
|
|
234
|
+
* 天干地支之天干速查表
|
|
235
|
+
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
|
236
|
+
* @return Cn string
|
|
237
|
+
*/
|
|
238
|
+
const Gan = [
|
|
239
|
+
"甲",
|
|
240
|
+
"乙",
|
|
241
|
+
"丙",
|
|
242
|
+
"丁",
|
|
243
|
+
"戊",
|
|
244
|
+
"己",
|
|
245
|
+
"庚",
|
|
246
|
+
"辛",
|
|
247
|
+
"壬",
|
|
248
|
+
"癸"
|
|
249
|
+
];
|
|
250
|
+
/**
|
|
251
|
+
* 天干地支之地支速查表
|
|
252
|
+
* @Array Of Property
|
|
253
|
+
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
|
254
|
+
* @return Cn string
|
|
255
|
+
*/
|
|
256
|
+
const Zhi = [
|
|
257
|
+
"子",
|
|
258
|
+
"丑",
|
|
259
|
+
"寅",
|
|
260
|
+
"卯",
|
|
261
|
+
"辰",
|
|
262
|
+
"巳",
|
|
263
|
+
"午",
|
|
264
|
+
"未",
|
|
265
|
+
"申",
|
|
266
|
+
"酉",
|
|
267
|
+
"戌",
|
|
268
|
+
"亥"
|
|
269
|
+
];
|
|
270
|
+
/**
|
|
271
|
+
* 每日的时辰对照表
|
|
272
|
+
* @Array Of Property
|
|
273
|
+
* @return Cn string
|
|
274
|
+
*/
|
|
275
|
+
const GanZhiHour = [
|
|
276
|
+
"甲子-乙丑-丙寅-丁卯-戊辰-己巳-庚午-辛未-壬申-癸酉-甲戌-乙亥",
|
|
277
|
+
"丙子-丁丑-戊寅-己卯-庚辰-辛巳-壬午-癸未-甲申-乙酉-丙戌-丁亥",
|
|
278
|
+
"戊子-己丑-庚寅-辛卯-壬辰-癸巳-甲午-乙未-丙申-丁酉-戊戌-己亥",
|
|
279
|
+
"庚子-辛丑-壬寅-癸卯-甲辰-乙巳-丙午-丁未-戊申-己酉-庚戌-辛亥",
|
|
280
|
+
"壬子-癸丑-甲寅-乙卯-丙辰-丁巳-戊午-己未-庚申-辛酉-壬戌-癸亥",
|
|
281
|
+
"甲子-乙丑-丙寅-丁卯-戊辰-己巳-庚午-辛未-壬申-癸酉-甲戌-乙亥",
|
|
282
|
+
"丙子-丁丑-戊寅-己卯-庚辰-辛巳-壬午-癸未-甲申-乙酉-丙戌-丁亥",
|
|
283
|
+
"戊子-己丑-庚寅-辛卯-壬辰-癸巳-甲午-乙未-丙申-丁酉-戊戌-己亥",
|
|
284
|
+
"庚子-辛丑-壬寅-癸卯-甲辰-乙巳-丙午-丁未-戊申-己酉-庚戌-辛亥",
|
|
285
|
+
"壬子-癸丑-甲寅-乙卯-丙辰-丁巳-戊午-己未-庚申-辛酉-壬戌-癸亥"
|
|
286
|
+
];
|
|
287
|
+
/**
|
|
288
|
+
* 天干地支之地支速查表<=>生肖
|
|
289
|
+
* @Array Of Property
|
|
290
|
+
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
|
291
|
+
* @return Cn string
|
|
292
|
+
*/
|
|
293
|
+
const Animals = [
|
|
294
|
+
"鼠",
|
|
295
|
+
"牛",
|
|
296
|
+
"虎",
|
|
297
|
+
"兔",
|
|
298
|
+
"龙",
|
|
299
|
+
"蛇",
|
|
300
|
+
"马",
|
|
301
|
+
"羊",
|
|
302
|
+
"猴",
|
|
303
|
+
"鸡",
|
|
304
|
+
"狗",
|
|
305
|
+
"猪"
|
|
306
|
+
];
|
|
307
|
+
/**
|
|
308
|
+
* 24节气速查表
|
|
309
|
+
* @Array Of Property
|
|
310
|
+
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
|
311
|
+
* @return Cn string
|
|
312
|
+
*/
|
|
313
|
+
const SolarTerm = [
|
|
314
|
+
"小寒",
|
|
315
|
+
"大寒",
|
|
316
|
+
"立春",
|
|
317
|
+
"雨水",
|
|
318
|
+
"惊蛰",
|
|
319
|
+
"春分",
|
|
320
|
+
"清明",
|
|
321
|
+
"谷雨",
|
|
322
|
+
"立夏",
|
|
323
|
+
"小满",
|
|
324
|
+
"芒种",
|
|
325
|
+
"夏至",
|
|
326
|
+
"小暑",
|
|
327
|
+
"大暑",
|
|
328
|
+
"立秋",
|
|
329
|
+
"处暑",
|
|
330
|
+
"白露",
|
|
331
|
+
"秋分",
|
|
332
|
+
"寒露",
|
|
333
|
+
"霜降",
|
|
334
|
+
"立冬",
|
|
335
|
+
"小雪",
|
|
336
|
+
"大雪",
|
|
337
|
+
"冬至"
|
|
338
|
+
];
|
|
339
|
+
/**
|
|
340
|
+
* 1900-2100各年的24节气日期速查表
|
|
341
|
+
* @Array Of Property
|
|
342
|
+
* @return 0x string For splice
|
|
343
|
+
*/
|
|
344
|
+
const TermInfo = [
|
|
345
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
346
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
347
|
+
"97bcf97c3598082c95f8c965cc920f",
|
|
348
|
+
"97bd0b06bdb0722c965ce1cfcc920f",
|
|
349
|
+
"b027097bd097c36b0b6fc9274c91aa",
|
|
350
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
351
|
+
"97bcf97c359801ec95f8c965cc920f",
|
|
352
|
+
"97bd0b06bdb0722c965ce1cfcc920f",
|
|
353
|
+
"b027097bd097c36b0b6fc9274c91aa",
|
|
354
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
355
|
+
"97bcf97c359801ec95f8c965cc920f",
|
|
356
|
+
"97bd0b06bdb0722c965ce1cfcc920f",
|
|
357
|
+
"b027097bd097c36b0b6fc9274c91aa",
|
|
358
|
+
"9778397bd19801ec9210c965cc920e",
|
|
359
|
+
"97b6b97bd19801ec95f8c965cc920f",
|
|
360
|
+
"97bd09801d98082c95f8e1cfcc920f",
|
|
361
|
+
"97bd097bd097c36b0b6fc9210c8dc2",
|
|
362
|
+
"9778397bd197c36c9210c9274c91aa",
|
|
363
|
+
"97b6b97bd19801ec95f8c965cc920e",
|
|
364
|
+
"97bd09801d98082c95f8e1cfcc920f",
|
|
365
|
+
"97bd097bd097c36b0b6fc9210c8dc2",
|
|
366
|
+
"9778397bd097c36c9210c9274c91aa",
|
|
367
|
+
"97b6b97bd19801ec95f8c965cc920e",
|
|
368
|
+
"97bcf97c3598082c95f8e1cfcc920f",
|
|
369
|
+
"97bd097bd097c36b0b6fc9210c8dc2",
|
|
370
|
+
"9778397bd097c36c9210c9274c91aa",
|
|
371
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
372
|
+
"97bcf97c3598082c95f8c965cc920f",
|
|
373
|
+
"97bd097bd097c35b0b6fc920fb0722",
|
|
374
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
375
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
376
|
+
"97bcf97c3598082c95f8c965cc920f",
|
|
377
|
+
"97bd097bd097c35b0b6fc920fb0722",
|
|
378
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
379
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
380
|
+
"97bcf97c359801ec95f8c965cc920f",
|
|
381
|
+
"97bd097bd097c35b0b6fc920fb0722",
|
|
382
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
383
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
384
|
+
"97bcf97c359801ec95f8c965cc920f",
|
|
385
|
+
"97bd097bd097c35b0b6fc920fb0722",
|
|
386
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
387
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
388
|
+
"97bcf97c359801ec95f8c965cc920f",
|
|
389
|
+
"97bd097bd07f595b0b6fc920fb0722",
|
|
390
|
+
"9778397bd097c36b0b6fc9210c8dc2",
|
|
391
|
+
"9778397bd19801ec9210c9274c920e",
|
|
392
|
+
"97b6b97bd19801ec95f8c965cc920f",
|
|
393
|
+
"97bd07f5307f595b0b0bc920fb0722",
|
|
394
|
+
"7f0e397bd097c36b0b6fc9210c8dc2",
|
|
395
|
+
"9778397bd097c36c9210c9274c920e",
|
|
396
|
+
"97b6b97bd19801ec95f8c965cc920f",
|
|
397
|
+
"97bd07f5307f595b0b0bc920fb0722",
|
|
398
|
+
"7f0e397bd097c36b0b6fc9210c8dc2",
|
|
399
|
+
"9778397bd097c36c9210c9274c91aa",
|
|
400
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
401
|
+
"97bd07f1487f595b0b0bc920fb0722",
|
|
402
|
+
"7f0e397bd097c36b0b6fc9210c8dc2",
|
|
403
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
404
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
405
|
+
"97bcf7f1487f595b0b0bb0b6fb0722",
|
|
406
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
407
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
408
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
409
|
+
"97bcf7f1487f595b0b0bb0b6fb0722",
|
|
410
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
411
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
412
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
413
|
+
"97bcf7f1487f531b0b0bb0b6fb0722",
|
|
414
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
415
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
416
|
+
"97b6b97bd19801ec9210c965cc920e",
|
|
417
|
+
"97bcf7f1487f531b0b0bb0b6fb0722",
|
|
418
|
+
"7f0e397bd07f595b0b6fc920fb0722",
|
|
419
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
420
|
+
"97b6b97bd19801ec9210c9274c920e",
|
|
421
|
+
"97bcf7f0e47f531b0b0bb0b6fb0722",
|
|
422
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
423
|
+
"9778397bd097c36b0b6fc9210c91aa",
|
|
424
|
+
"97b6b97bd197c36c9210c9274c920e",
|
|
425
|
+
"97bcf7f0e47f531b0b0bb0b6fb0722",
|
|
426
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
427
|
+
"9778397bd097c36b0b6fc9210c8dc2",
|
|
428
|
+
"9778397bd097c36c9210c9274c920e",
|
|
429
|
+
"97b6b7f0e47f531b0723b0b6fb0722",
|
|
430
|
+
"7f0e37f5307f595b0b0bc920fb0722",
|
|
431
|
+
"7f0e397bd097c36b0b6fc9210c8dc2",
|
|
432
|
+
"9778397bd097c36b0b70c9274c91aa",
|
|
433
|
+
"97b6b7f0e47f531b0723b0b6fb0721",
|
|
434
|
+
"7f0e37f1487f595b0b0bb0b6fb0722",
|
|
435
|
+
"7f0e397bd097c35b0b6fc9210c8dc2",
|
|
436
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
437
|
+
"97b6b7f0e47f531b0723b0b6fb0721",
|
|
438
|
+
"7f0e27f1487f595b0b0bb0b6fb0722",
|
|
439
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
440
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
441
|
+
"97b6b7f0e47f531b0723b0b6fb0721",
|
|
442
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
443
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
444
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
445
|
+
"97b6b7f0e47f531b0723b0b6fb0721",
|
|
446
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
447
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
448
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
449
|
+
"97b6b7f0e47f531b0723b0b6fb0721",
|
|
450
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
451
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
452
|
+
"9778397bd097c36b0b6fc9274c91aa",
|
|
453
|
+
"97b6b7f0e47f531b0723b0787b0721",
|
|
454
|
+
"7f0e27f0e47f531b0b0bb0b6fb0722",
|
|
455
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
456
|
+
"9778397bd097c36b0b6fc9210c91aa",
|
|
457
|
+
"97b6b7f0e47f149b0723b0787b0721",
|
|
458
|
+
"7f0e27f0e47f531b0723b0b6fb0722",
|
|
459
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
460
|
+
"9778397bd097c36b0b6fc9210c8dc2",
|
|
461
|
+
"977837f0e37f149b0723b0787b0721",
|
|
462
|
+
"7f07e7f0e47f531b0723b0b6fb0722",
|
|
463
|
+
"7f0e37f5307f595b0b0bc920fb0722",
|
|
464
|
+
"7f0e397bd097c35b0b6fc9210c8dc2",
|
|
465
|
+
"977837f0e37f14998082b0787b0721",
|
|
466
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
467
|
+
"7f0e37f1487f595b0b0bb0b6fb0722",
|
|
468
|
+
"7f0e397bd097c35b0b6fc9210c8dc2",
|
|
469
|
+
"977837f0e37f14998082b0787b06bd",
|
|
470
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
471
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
472
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
473
|
+
"977837f0e37f14998082b0787b06bd",
|
|
474
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
475
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
476
|
+
"7f0e397bd097c35b0b6fc920fb0722",
|
|
477
|
+
"977837f0e37f14998082b0787b06bd",
|
|
478
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
479
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
480
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
481
|
+
"977837f0e37f14998082b0787b06bd",
|
|
482
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
483
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
484
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
485
|
+
"977837f0e37f14998082b0787b06bd",
|
|
486
|
+
"7f07e7f0e47f149b0723b0787b0721",
|
|
487
|
+
"7f0e27f0e47f531b0b0bb0b6fb0722",
|
|
488
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
489
|
+
"977837f0e37f14998082b0723b06bd",
|
|
490
|
+
"7f07e7f0e37f149b0723b0787b0721",
|
|
491
|
+
"7f0e27f0e47f531b0723b0b6fb0722",
|
|
492
|
+
"7f0e397bd07f595b0b0bc920fb0722",
|
|
493
|
+
"977837f0e37f14898082b0723b02d5",
|
|
494
|
+
"7ec967f0e37f14998082b0787b0721",
|
|
495
|
+
"7f07e7f0e47f531b0723b0b6fb0722",
|
|
496
|
+
"7f0e37f1487f595b0b0bb0b6fb0722",
|
|
497
|
+
"7f0e37f0e37f14898082b0723b02d5",
|
|
498
|
+
"7ec967f0e37f14998082b0787b0721",
|
|
499
|
+
"7f07e7f0e47f531b0723b0b6fb0722",
|
|
500
|
+
"7f0e37f1487f531b0b0bb0b6fb0722",
|
|
501
|
+
"7f0e37f0e37f14898082b0723b02d5",
|
|
502
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
503
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
504
|
+
"7f0e37f1487f531b0b0bb0b6fb0722",
|
|
505
|
+
"7f0e37f0e37f14898082b072297c35",
|
|
506
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
507
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
508
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
509
|
+
"7f0e37f0e37f14898082b072297c35",
|
|
510
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
511
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
512
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
513
|
+
"7f0e37f0e366aa89801eb072297c35",
|
|
514
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
515
|
+
"7f07e7f0e47f149b0723b0787b0721",
|
|
516
|
+
"7f0e27f1487f531b0b0bb0b6fb0722",
|
|
517
|
+
"7f0e37f0e366aa89801eb072297c35",
|
|
518
|
+
"7ec967f0e37f14998082b0723b06bd",
|
|
519
|
+
"7f07e7f0e47f149b0723b0787b0721",
|
|
520
|
+
"7f0e27f0e47f531b0723b0b6fb0722",
|
|
521
|
+
"7f0e37f0e366aa89801eb072297c35",
|
|
522
|
+
"7ec967f0e37f14998082b0723b06bd",
|
|
523
|
+
"7f07e7f0e37f14998083b0787b0721",
|
|
524
|
+
"7f0e27f0e47f531b0723b0b6fb0722",
|
|
525
|
+
"7f0e37f0e366aa89801eb072297c35",
|
|
526
|
+
"7ec967f0e37f14898082b0723b02d5",
|
|
527
|
+
"7f07e7f0e37f14998082b0787b0721",
|
|
528
|
+
"7f07e7f0e47f531b0723b0b6fb0722",
|
|
529
|
+
"7f0e36665b66aa89801e9808297c35",
|
|
530
|
+
"665f67f0e37f14898082b0723b02d5",
|
|
531
|
+
"7ec967f0e37f14998082b0787b0721",
|
|
532
|
+
"7f07e7f0e47f531b0723b0b6fb0722",
|
|
533
|
+
"7f0e36665b66a449801e9808297c35",
|
|
534
|
+
"665f67f0e37f14898082b0723b02d5",
|
|
535
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
536
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
537
|
+
"7f0e36665b66a449801e9808297c35",
|
|
538
|
+
"665f67f0e37f14898082b072297c35",
|
|
539
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
540
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
541
|
+
"7f0e26665b66a449801e9808297c35",
|
|
542
|
+
"665f67f0e37f1489801eb072297c35",
|
|
543
|
+
"7ec967f0e37f14998082b0787b06bd",
|
|
544
|
+
"7f07e7f0e47f531b0723b0b6fb0721",
|
|
545
|
+
"7f0e27f1487f531b0b0bb0b6fb0722"
|
|
546
|
+
];
|
|
547
|
+
/**
|
|
548
|
+
* 数字转中文速查表
|
|
549
|
+
* @Array Of Property
|
|
550
|
+
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
|
551
|
+
* @return Cn string
|
|
552
|
+
*/
|
|
553
|
+
const nStr1 = [
|
|
554
|
+
"日",
|
|
555
|
+
"一",
|
|
556
|
+
"二",
|
|
557
|
+
"三",
|
|
558
|
+
"四",
|
|
559
|
+
"五",
|
|
560
|
+
"六",
|
|
561
|
+
"七",
|
|
562
|
+
"八",
|
|
563
|
+
"九",
|
|
564
|
+
"十"
|
|
565
|
+
];
|
|
566
|
+
/**
|
|
567
|
+
* 日期转农历称呼速查表
|
|
568
|
+
* @Array Of Property
|
|
569
|
+
* @trans ['初','十','廿','卅']
|
|
570
|
+
* @return Cn string
|
|
571
|
+
*/
|
|
572
|
+
const nStr2 = [
|
|
573
|
+
"初",
|
|
574
|
+
"十",
|
|
575
|
+
"廿",
|
|
576
|
+
"卅"
|
|
577
|
+
];
|
|
578
|
+
/**
|
|
579
|
+
* 月份转农历称呼速查表
|
|
580
|
+
* @Array Of Property
|
|
581
|
+
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
|
582
|
+
* @return Cn string
|
|
583
|
+
*/
|
|
584
|
+
const nStr3 = [
|
|
585
|
+
"正",
|
|
586
|
+
"二",
|
|
587
|
+
"三",
|
|
588
|
+
"四",
|
|
589
|
+
"五",
|
|
590
|
+
"六",
|
|
591
|
+
"七",
|
|
592
|
+
"八",
|
|
593
|
+
"九",
|
|
594
|
+
"十",
|
|
595
|
+
"冬",
|
|
596
|
+
"腊"
|
|
597
|
+
];
|
|
598
|
+
// src/utils/Assert.ts
|
|
599
|
+
/** 四位 "MMDD",月份 01-12、日期 01-31 */
|
|
600
|
+
const MMDD_RE = /^(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])$/;
|
|
601
|
+
/** 预生成的 MMDD 查表键(12 × 31),避免每次查节日 / 调休都拼字符串 */
|
|
602
|
+
const MMDD = Array.from({ length: 13 }, () => Array.from({ length: 32 }, () => ""));
|
|
603
|
+
for (let m = 1; m <= 12; m++) for (let d = 1; d <= 31; d++) MMDD[m][d] = (m < 10 ? "0" + m : String(m)) + (d < 10 ? "0" + d : String(d));
|
|
604
|
+
/** 取四位 MMDD 字符串,例如 mmddOf(1, 1) → "0101" */
|
|
605
|
+
const mmddOf = function(m, d) {
|
|
606
|
+
return MMDD[m][d];
|
|
607
|
+
};
|
|
608
|
+
/** 调休年份:1900-2100 */
|
|
609
|
+
const YEAR_RE = /^(19\d{2}|20\d{2}|2100)$/;
|
|
610
|
+
/** 公历每个月的最大天数,2 月按闰年放宽到 29 */
|
|
611
|
+
const SOLAR_MAX_DAY = [
|
|
612
|
+
31,
|
|
613
|
+
29,
|
|
614
|
+
31,
|
|
615
|
+
30,
|
|
616
|
+
31,
|
|
617
|
+
30,
|
|
618
|
+
31,
|
|
619
|
+
31,
|
|
620
|
+
30,
|
|
621
|
+
31,
|
|
622
|
+
30,
|
|
623
|
+
31
|
|
624
|
+
];
|
|
625
|
+
/** 调休表每年的字段 */
|
|
626
|
+
const REST_FIELDS = ["xiu", "ban"];
|
|
627
|
+
/** 抛出带位置信息的 TypeError,用于配置格式错误 */
|
|
628
|
+
function failType(path, message) {
|
|
629
|
+
throw new TypeError(`[calendar] ${path} ${message}`);
|
|
630
|
+
}
|
|
631
|
+
/** 抛出带位置信息的 RangeError,用于数值越界 */
|
|
632
|
+
function failRange(path, message) {
|
|
633
|
+
throw new RangeError(`[calendar] ${path} ${message}`);
|
|
634
|
+
}
|
|
635
|
+
/** 是否为普通对象(非 null、非数组) */
|
|
636
|
+
const isPlainObject = function(value) {
|
|
637
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
638
|
+
};
|
|
639
|
+
/** 校验是普通对象,否则按 path 抛 TypeError;断言之后可以直接当字典用 */
|
|
640
|
+
function assertPlainObject(value, path, message = "必须是对象") {
|
|
641
|
+
if (!isPlainObject(value)) failType(path, `${message},收到 ${describe(value)}`);
|
|
642
|
+
}
|
|
643
|
+
/** 校验对象里没有白名单之外的字段 */
|
|
644
|
+
const assertKnownKeys = function(value, allowed, path) {
|
|
645
|
+
Object.keys(value).forEach(function(key) {
|
|
646
|
+
if (allowed.indexOf(key) === -1) failType(`${path}.${key}`, `不认识的字段,只支持 ${allowed.join(" 与 ")}`);
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
/** 把任意值格式化成便于排查的短字符串 */
|
|
650
|
+
const describe = function(value) {
|
|
651
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
652
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return String(value);
|
|
653
|
+
if (Array.isArray(value)) return `Array(${value.length})`;
|
|
654
|
+
if (typeof value === "object") {
|
|
655
|
+
const keys = Object.keys(value);
|
|
656
|
+
return `Object(keys: ${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", …" : ""})`;
|
|
657
|
+
}
|
|
658
|
+
return typeof value;
|
|
659
|
+
};
|
|
660
|
+
/** 校验一个整数并返回它 */
|
|
661
|
+
const assertInteger = function(path, value, min, max) {
|
|
662
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return failRange(path, `必须是整数,收到 ${describe(value)}`);
|
|
663
|
+
if (value < min || value > max) return failRange(path, `必须在 ${min}-${max} 之间,收到 ${value}`);
|
|
664
|
+
return value;
|
|
665
|
+
};
|
|
666
|
+
/** 公历 MMDD 是否对应真实存在的日期(例如 0231、0431 为 false) */
|
|
667
|
+
const isSolarMonthDay = function(m, d) {
|
|
668
|
+
return d >= 1 && d <= (SOLAR_MAX_DAY[m - 1] ?? 0);
|
|
669
|
+
};
|
|
670
|
+
/** 农历 MMDD:每个月最多 30 天 */
|
|
671
|
+
const isLunarMonthDay = function(m, d) {
|
|
672
|
+
return d >= 1 && d <= 30;
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* 校验并解析四位 MMDD:节日 key 与调休日期共用同一套格式和日期检查
|
|
676
|
+
*
|
|
677
|
+
* @param value 待校验值,必须是 "MMDD" 字符串
|
|
678
|
+
* @param path 出错时用于定位的路径
|
|
679
|
+
* @param options.lunar 按农历校验(每月最多 30 天),默认公历
|
|
680
|
+
* @param options.label 报错信息里的主语,例如 "节日 key"
|
|
681
|
+
*/
|
|
682
|
+
function assertMMDD(value, path, options = {}) {
|
|
683
|
+
const { lunar = false, label } = options;
|
|
684
|
+
const prefix = label === void 0 ? "" : `${label} `;
|
|
685
|
+
const parsed = typeof value === "string" && MMDD_RE.test(value) ? {
|
|
686
|
+
month: Number(value.slice(0, 2)),
|
|
687
|
+
day: Number(value.slice(2)),
|
|
688
|
+
text: value
|
|
689
|
+
} : null;
|
|
690
|
+
if (parsed === null) failType(path, `${prefix}必须是四位 "MMDD"(如 "0101"),收到 ${describe(value)}`);
|
|
691
|
+
if (!(lunar ? isLunarMonthDay(parsed.month, parsed.day) : isSolarMonthDay(parsed.month, parsed.day))) failType(path, `${lunar ? "农历" : "公历"}不存在 ${parsed.month} 月 ${parsed.day} 日`);
|
|
692
|
+
return parsed;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* 校验并规范化节日表
|
|
696
|
+
*
|
|
697
|
+
* @param input 待校验的节日表
|
|
698
|
+
* @param path 出错时用于定位的路径,例如 "createCalendar.setFestival.sFtv"
|
|
699
|
+
* @param lunar 农历节日(true)还是公历节日(false)
|
|
700
|
+
*/
|
|
701
|
+
const normalizeFestivalMap = function(input, path, lunar = false) {
|
|
702
|
+
assertPlainObject(input, path);
|
|
703
|
+
const result = {};
|
|
704
|
+
Object.keys(input).forEach(function(key) {
|
|
705
|
+
assertMMDD(key, `${path}.${key}`, {
|
|
706
|
+
lunar,
|
|
707
|
+
label: "节日 key"
|
|
708
|
+
});
|
|
709
|
+
const value = input[key];
|
|
710
|
+
if (!Array.isArray(value)) throw failType(`${path}.${key}`, `节日名称必须是字符串数组,收到 ${describe(value)}`);
|
|
711
|
+
const names = [];
|
|
712
|
+
value.forEach(function(name, index) {
|
|
713
|
+
if (typeof name !== "string" || name.trim() === "") throw failType(`${path}.${key}[${index}]`, `节日名称必须是非空字符串,收到 ${describe(name)}`);
|
|
714
|
+
names.push(name);
|
|
715
|
+
});
|
|
716
|
+
result[key] = names;
|
|
717
|
+
});
|
|
718
|
+
return result;
|
|
719
|
+
};
|
|
720
|
+
/** 校验并去重一组调休日期(MMDD 字符串) */
|
|
721
|
+
const normalizeDayList = function(input, path) {
|
|
722
|
+
if (!Array.isArray(input)) throw failType(path, `必须是 "MMDD" 字符串数组,收到 ${describe(input)}`);
|
|
723
|
+
const days = [];
|
|
724
|
+
input.forEach(function(day, index) {
|
|
725
|
+
days.push(assertMMDD(day, `${path}[${index}]`).text);
|
|
726
|
+
});
|
|
727
|
+
return Array.from(new Set(days));
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
* 校验并规范化调休表,key 为年份
|
|
731
|
+
*
|
|
732
|
+
* @param input 待校验的调休表
|
|
733
|
+
* @param path 出错时用于定位的路径
|
|
734
|
+
*/
|
|
735
|
+
const normalizeRestMap = function(input, path) {
|
|
736
|
+
assertPlainObject(input, path);
|
|
737
|
+
const result = {};
|
|
738
|
+
Object.keys(input).forEach(function(yearKey) {
|
|
739
|
+
if (!YEAR_RE.test(yearKey)) throw failType(`${path}.${yearKey}`, `年份必须是 1900-2100 的四位数字,收到 ${describe(yearKey)}`);
|
|
740
|
+
const entry = input[yearKey];
|
|
741
|
+
assertPlainObject(entry, `${path}.${yearKey}`, "必须是 { xiu: [], ban: [] }");
|
|
742
|
+
assertKnownKeys(entry, REST_FIELDS, `${path}.${yearKey}`);
|
|
743
|
+
const xiu = entry.xiu === void 0 ? [] : normalizeDayList(entry.xiu, `${path}.${yearKey}.xiu`);
|
|
744
|
+
const ban = entry.ban === void 0 ? [] : normalizeDayList(entry.ban, `${path}.${yearKey}.ban`);
|
|
745
|
+
const conflict = xiu.filter(function(day) {
|
|
746
|
+
return ban.indexOf(day) !== -1;
|
|
747
|
+
});
|
|
748
|
+
if (conflict.length > 0) throw failType(`${path}.${yearKey}`, `同一天不能既是放假日又是补班日:${conflict.join("、")}`);
|
|
749
|
+
result[yearKey] = {
|
|
750
|
+
xiu,
|
|
751
|
+
ban
|
|
752
|
+
};
|
|
753
|
+
});
|
|
754
|
+
return result;
|
|
755
|
+
};
|
|
756
|
+
// src/utils/LunarFunction.ts
|
|
757
|
+
/** 农历数据表覆盖的年份 */
|
|
758
|
+
const LUNAR_YEAR_MIN = 1900;
|
|
759
|
+
const LUNAR_YEAR_MAX = 2100;
|
|
760
|
+
/**
|
|
761
|
+
* 校验农历年份是否在数据表范围内,越界直接抛 RangeError
|
|
762
|
+
*/
|
|
763
|
+
const assertLunarYear = function(y, path) {
|
|
764
|
+
return assertInteger(path, y, LUNAR_YEAR_MIN, LUNAR_YEAR_MAX);
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* 返回农历 y 年一整年的总天数
|
|
768
|
+
* @param y 农历年(1900-2100)
|
|
769
|
+
* @return 348-385
|
|
770
|
+
*/
|
|
771
|
+
const lYearDays = function(y) {
|
|
772
|
+
assertLunarYear(y, "lYearDays(year)");
|
|
773
|
+
let sum = 348;
|
|
774
|
+
for (let i = 32768; i > 8; i >>= 1) sum += LunarInfo[y - 1900] & i ? 1 : 0;
|
|
775
|
+
return sum + leapDays(y);
|
|
776
|
+
};
|
|
777
|
+
/**
|
|
778
|
+
* 返回农历 y 年闰月是哪个月;没有闰月返回 0
|
|
779
|
+
* @param y 农历年(1900-2100)
|
|
780
|
+
*/
|
|
781
|
+
const leapMonth = function(y) {
|
|
782
|
+
assertLunarYear(y, "leapMonth(year)");
|
|
783
|
+
return LunarInfo[y - 1900] & 15;
|
|
784
|
+
};
|
|
785
|
+
/**
|
|
786
|
+
* 返回农历 y 年闰月的天数;没有闰月返回 0
|
|
787
|
+
* @param y 农历年(1900-2100)
|
|
788
|
+
*/
|
|
789
|
+
const leapDays = function(y) {
|
|
790
|
+
if (leapMonth(y)) return LunarInfo[y - 1900] & 65536 ? 30 : 29;
|
|
791
|
+
return 0;
|
|
792
|
+
};
|
|
793
|
+
/**
|
|
794
|
+
* 返回农历 y 年 m 月(非闰月)的天数,闰月天数请用 leapDays
|
|
795
|
+
* @param y 农历年(1900-2100)
|
|
796
|
+
* @param m 农历月(1-12)
|
|
797
|
+
*/
|
|
798
|
+
const lMonthDays = function(y, m) {
|
|
799
|
+
assertLunarYear(y, "lMonthDays(year)");
|
|
800
|
+
assertInteger("lMonthDays(month)", m, 1, 12);
|
|
801
|
+
return LunarInfo[y - 1900] & 65536 >> m ? 30 : 29;
|
|
802
|
+
};
|
|
803
|
+
/** 农历每年正月初一距 1900-01-31 的天数;长度 202,最后一项是 2101 正月初一 */
|
|
804
|
+
const YEAR_START = (function() {
|
|
805
|
+
const starts = [0];
|
|
806
|
+
for (let y = LUNAR_YEAR_MIN; y <= LUNAR_YEAR_MAX; y++) starts.push(starts[starts.length - 1] + lYearDays(y));
|
|
807
|
+
return starts;
|
|
808
|
+
})();
|
|
809
|
+
/** 某年正月初一距 1900-01-31 的天数 */
|
|
810
|
+
const yearStartOf = function(year) {
|
|
811
|
+
assertLunarYear(year, "yearStartOf(year)");
|
|
812
|
+
return YEAR_START[year - LUNAR_YEAR_MIN];
|
|
813
|
+
};
|
|
814
|
+
const segmentCache = /* @__PURE__ */ new Map();
|
|
815
|
+
/** 某年的月份段表(按时间顺序),第一次构建后缓存 */
|
|
816
|
+
const monthSegmentsOf = function(year) {
|
|
817
|
+
const cached = segmentCache.get(year);
|
|
818
|
+
if (cached) return cached;
|
|
819
|
+
const leap = leapMonth(year);
|
|
820
|
+
const segments = [];
|
|
821
|
+
let start = 0;
|
|
822
|
+
for (let m = 1; m <= 12; m++) {
|
|
823
|
+
segments.push({
|
|
824
|
+
month: m,
|
|
825
|
+
isLeap: false,
|
|
826
|
+
start
|
|
827
|
+
});
|
|
828
|
+
start += lMonthDays(year, m);
|
|
829
|
+
if (leap === m) {
|
|
830
|
+
segments.push({
|
|
831
|
+
month: m,
|
|
832
|
+
isLeap: true,
|
|
833
|
+
start
|
|
834
|
+
});
|
|
835
|
+
start += leapDays(year);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
segmentCache.set(year, segments);
|
|
839
|
+
return segments;
|
|
840
|
+
};
|
|
841
|
+
/** 把“距 1900-01-31 的天数”换算成农历年与当年第几天(二分查找,替代逐年累减) */
|
|
842
|
+
const resolveLunarYear = function(offset) {
|
|
843
|
+
let low = 0;
|
|
844
|
+
let high = 200;
|
|
845
|
+
while (low < high) {
|
|
846
|
+
const mid = low + high + 1 >>> 1;
|
|
847
|
+
if (YEAR_START[mid] <= offset) low = mid;
|
|
848
|
+
else high = mid - 1;
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
year: LUNAR_YEAR_MIN + low,
|
|
852
|
+
offsetInYear: offset - YEAR_START[low]
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
/**
|
|
856
|
+
* 农历年转换为干支纪年
|
|
857
|
+
* @param lYear 农历年,允许 1899 这类边界年(只做算术,不查表)
|
|
858
|
+
*/
|
|
859
|
+
const toGanZhiYear = function(lYear) {
|
|
860
|
+
if (typeof lYear !== "number" || !Number.isInteger(lYear)) failRange("toGanZhiYear(year)", `必须是整数,收到 ${lYear}`);
|
|
861
|
+
let ganKey = (lYear - 3) % 10;
|
|
862
|
+
let zhiKey = (lYear - 3) % 12;
|
|
863
|
+
if (ganKey <= 0) ganKey += 10;
|
|
864
|
+
if (zhiKey <= 0) zhiKey += 12;
|
|
865
|
+
return Gan[ganKey - 1] + Zhi[zhiKey - 1];
|
|
866
|
+
};
|
|
867
|
+
/**
|
|
868
|
+
* 传入相对甲子的偏移量返回干支
|
|
869
|
+
* @param offset 可以为负数
|
|
870
|
+
*/
|
|
871
|
+
const toGanZhi = function(offset) {
|
|
872
|
+
if (typeof offset !== "number" || !Number.isInteger(offset)) failRange("toGanZhi(offset)", `必须是整数,收到 ${offset}`);
|
|
873
|
+
const gan = (offset % 10 + 10) % 10;
|
|
874
|
+
const zhi = (offset % 12 + 12) % 12;
|
|
875
|
+
return Gan[gan] + Zhi[zhi];
|
|
876
|
+
};
|
|
877
|
+
/**
|
|
878
|
+
* 传入当天的干支偏移量,返回当天十二个时辰的干支
|
|
879
|
+
* @param offset 相对甲子的偏移量(与 gzDay 用同一个偏移量)
|
|
880
|
+
*/
|
|
881
|
+
const toHourGanZhi = function(offset) {
|
|
882
|
+
if (typeof offset !== "number" || !Number.isInteger(offset)) failRange("toHourGanZhi(offset)", `必须是整数,收到 ${offset}`);
|
|
883
|
+
const index = (offset % 10 + 10) % 10;
|
|
884
|
+
return GanZhiHour[index].split("-");
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* 农历数字月份转中文,例如 1 -> "正月"、12 -> "腊月"
|
|
888
|
+
* @param m 农历月(1-12)
|
|
889
|
+
*/
|
|
890
|
+
const toChinaMonth = function(m) {
|
|
891
|
+
assertInteger("toChinaMonth(month)", m, 1, 12);
|
|
892
|
+
return nStr3[m - 1] + "月";
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* 农历日期数字转中文,例如 1 -> "初一"、21 -> "廿一"
|
|
896
|
+
* @param d 农历日(1-30)
|
|
897
|
+
*/
|
|
898
|
+
const toChinaDay = function(d) {
|
|
899
|
+
assertInteger("toChinaDay(day)", d, 1, 30);
|
|
900
|
+
switch (d) {
|
|
901
|
+
case 10: return "初十";
|
|
902
|
+
case 20: return "二十";
|
|
903
|
+
case 30: return "三十";
|
|
904
|
+
default: return nStr2[Math.floor(d / 10)] + nStr1[d % 10];
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
/**
|
|
908
|
+
* 取第 n 个节气名(n 从 0 开始,0 为小寒)
|
|
909
|
+
* @param n 0-23
|
|
910
|
+
*/
|
|
911
|
+
const getSolarTerm = function(n) {
|
|
912
|
+
assertInteger("getSolarTerm(n)", n, 0, 23);
|
|
913
|
+
return SolarTerm[n];
|
|
914
|
+
};
|
|
915
|
+
// src/utils/SolarFunction.ts
|
|
916
|
+
/** 公历年份下限,只为挡住明显错误的入参 */
|
|
917
|
+
const SOLAR_YEAR_MIN = 1;
|
|
918
|
+
const SOLAR_YEAR_MAX = 9999;
|
|
919
|
+
const MS_PER_DAY = 864e5;
|
|
920
|
+
/** 1970-01-01 是星期四,用作星期推算的基准 */
|
|
921
|
+
const EPOCH_WEEKDAY = 4;
|
|
922
|
+
/** 用毫秒数直接算星期,避免每次都 new Date */
|
|
923
|
+
const weekdayOf = function(y, m, d) {
|
|
924
|
+
return ((Math.round(Date.UTC(y, m - 1, d) / MS_PER_DAY) + EPOCH_WEEKDAY) % 7 + 7) % 7;
|
|
925
|
+
};
|
|
926
|
+
/** 按年缓存的 24 个节气日期,同一年只解析一次 */
|
|
927
|
+
const termCache = /* @__PURE__ */ new Map();
|
|
928
|
+
/** 解析某一年的节气表,返回 24 个日期数字 */
|
|
929
|
+
const parseTerms = function(y) {
|
|
930
|
+
const table = TermInfo[y - 1900];
|
|
931
|
+
if (typeof table !== "string" || table.length < 30) failRange("getTerm(year)", `缺少 ${y} 年的节气数据`);
|
|
932
|
+
const days = [];
|
|
933
|
+
for (let i = 0; i < 6; i++) {
|
|
934
|
+
const chunk = parseInt(table.slice(i * 5, i * 5 + 5), 16).toString();
|
|
935
|
+
days.push(Number(chunk.slice(0, 1)), Number(chunk.slice(1, 3)), Number(chunk.slice(3, 4)), Number(chunk.slice(4, 6)));
|
|
936
|
+
}
|
|
937
|
+
return days;
|
|
938
|
+
};
|
|
939
|
+
/**
|
|
940
|
+
* 返回公历 y 年 m 月的天数(会自动处理闰年)
|
|
941
|
+
* @param y 公历年
|
|
942
|
+
* @param m 公历月(1-12)
|
|
943
|
+
* @return 28-31
|
|
944
|
+
*/
|
|
945
|
+
const solarDays = function(y, m) {
|
|
946
|
+
assertInteger("solarDays(year)", y, SOLAR_YEAR_MIN, SOLAR_YEAR_MAX);
|
|
947
|
+
assertInteger("solarDays(month)", m, 1, 12);
|
|
948
|
+
if (m === 2) return y % 4 === 0 && y % 100 !== 0 || y % 400 === 0 ? 29 : 28;
|
|
949
|
+
return SolarMonth[m - 1];
|
|
950
|
+
};
|
|
951
|
+
/**
|
|
952
|
+
* 返回公历 y 年 m 月 1 日是星期几
|
|
953
|
+
* @param y 公历年
|
|
954
|
+
* @param m 公历月(1-12)
|
|
955
|
+
* @return 0-6,0 为周日
|
|
956
|
+
*/
|
|
957
|
+
const solarFirstWeek = function(y, m) {
|
|
958
|
+
assertInteger("solarFirstWeek(year)", y, SOLAR_YEAR_MIN, SOLAR_YEAR_MAX);
|
|
959
|
+
assertInteger("solarFirstWeek(month)", m, 1, 12);
|
|
960
|
+
return weekdayOf(y, m, 1);
|
|
961
|
+
};
|
|
962
|
+
/**
|
|
963
|
+
* 返回公历 y 年 m 月第 nth 个星期 weekday 是几号
|
|
964
|
+
* @param y 公历年
|
|
965
|
+
* @param m 公历月(1-12)
|
|
966
|
+
* @param weekday 0-6,0 为周日
|
|
967
|
+
* @param nth 第几个,从 1 开始
|
|
968
|
+
* @example nthWeekdayOfMonth(2026, 5, 0, 2) // 10,2026 年母亲节是 5 月 10 日
|
|
969
|
+
*/
|
|
970
|
+
const nthWeekdayOfMonth = function(y, m, weekday, nth) {
|
|
971
|
+
assertInteger("nthWeekdayOfMonth(weekday)", weekday, 0, 6);
|
|
972
|
+
assertInteger("nthWeekdayOfMonth(nth)", nth, 1, 5);
|
|
973
|
+
const day = 1 + (weekday - solarFirstWeek(y, m) + 7) % 7 + (nth - 1) * 7;
|
|
974
|
+
if (day > solarDays(y, m)) failRange("nthWeekdayOfMonth", `${y} 年 ${m} 月没有第 ${nth} 个星期${weekday},超出当月天数`);
|
|
975
|
+
return day;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* 星座名称,每个星座占两个字符,末尾补一个摩羯座用于 12 月下旬切片
|
|
979
|
+
*/
|
|
980
|
+
const ASTRO_NAMES = "摩羯水瓶双鱼白羊金牛双子巨蟹狮子处女天秤天蝎射手摩羯";
|
|
981
|
+
/** 每个月切换星座的日期,当天大于等于该值即属于后一个星座 */
|
|
982
|
+
const ASTRO_CUSP = [
|
|
983
|
+
20,
|
|
984
|
+
19,
|
|
985
|
+
21,
|
|
986
|
+
20,
|
|
987
|
+
21,
|
|
988
|
+
22,
|
|
989
|
+
23,
|
|
990
|
+
23,
|
|
991
|
+
23,
|
|
992
|
+
24,
|
|
993
|
+
23,
|
|
994
|
+
22
|
|
995
|
+
];
|
|
996
|
+
/**
|
|
997
|
+
* 公历月、日判断所属星座
|
|
998
|
+
* @param m 公历月(1-12)
|
|
999
|
+
* @param d 公历日
|
|
1000
|
+
* @example toAstro(4, 19) // "白羊座"
|
|
1001
|
+
*/
|
|
1002
|
+
const toAstro = function(m, d) {
|
|
1003
|
+
assertInteger("toAstro(month)", m, 1, 12);
|
|
1004
|
+
assertInteger("toAstro(day)", d, 1, 31);
|
|
1005
|
+
const start = m * 2 - (d < ASTRO_CUSP[m - 1] ? 2 : 0);
|
|
1006
|
+
return ASTRO_NAMES.slice(start, start + 2) + "座";
|
|
1007
|
+
};
|
|
1008
|
+
/**
|
|
1009
|
+
* 传入公历年获得该年第 n 个节气所在公历月的日期
|
|
1010
|
+
* @param y 公历年(1900-2100)
|
|
1011
|
+
* @param n 第几个节气(1-24),1 为小寒
|
|
1012
|
+
* @return 日期数字
|
|
1013
|
+
* @example getTerm(1987, 3) // 4,1987 年 2 月 4 日立春
|
|
1014
|
+
*/
|
|
1015
|
+
const getTerm = function(y, n) {
|
|
1016
|
+
assertInteger("getTerm(year)", y, 1900, 2100);
|
|
1017
|
+
assertInteger("getTerm(n)", n, 1, 24);
|
|
1018
|
+
let days = termCache.get(y);
|
|
1019
|
+
if (days === void 0) {
|
|
1020
|
+
days = parseTerms(y);
|
|
1021
|
+
termCache.set(y, days);
|
|
1022
|
+
}
|
|
1023
|
+
const day = days[n - 1];
|
|
1024
|
+
if (!Number.isInteger(day)) failRange("getTerm", `${y} 年第 ${n} 个节气解析失败`);
|
|
1025
|
+
return day;
|
|
1026
|
+
};
|
|
1027
|
+
/**
|
|
1028
|
+
* 年份转生肖,以农历年为准
|
|
1029
|
+
* @param y 年份
|
|
1030
|
+
* @example getAnimal(1987) // "兔"
|
|
1031
|
+
*/
|
|
1032
|
+
const getAnimal = function(y) {
|
|
1033
|
+
assertInteger("getAnimal(year)", y, SOLAR_YEAR_MIN, SOLAR_YEAR_MAX);
|
|
1034
|
+
return Animals[((y - 4) % 12 + 12) % 12];
|
|
1035
|
+
};
|
|
1036
|
+
/**
|
|
1037
|
+
* 数字转中文,0-10
|
|
1038
|
+
* @param num 0-10
|
|
1039
|
+
* @example toChinaNum(3) // "三"
|
|
1040
|
+
*/
|
|
1041
|
+
const toChinaNum = function(num) {
|
|
1042
|
+
assertInteger("toChinaNum(num)", num, 0, 10);
|
|
1043
|
+
return nStr1[num];
|
|
1044
|
+
};
|
|
1045
|
+
// src/utils/SetFestival.ts
|
|
1046
|
+
/** 没有节日时共用的空数组,冻结后不怕调用方误改 */
|
|
1047
|
+
const NO_FESTIVAL = Object.freeze([]);
|
|
1048
|
+
/**
|
|
1049
|
+
* 把两份节日表按 key 合并并去重,不修改入参
|
|
1050
|
+
*/
|
|
1051
|
+
const mergeFestivalMap = function(base, extra) {
|
|
1052
|
+
if (!extra) return base;
|
|
1053
|
+
const result = { ...base };
|
|
1054
|
+
Object.keys(extra).forEach(function(key) {
|
|
1055
|
+
const merged = result[key] ? result[key].concat(extra[key]) : extra[key];
|
|
1056
|
+
result[key] = Array.from(new Set(merged));
|
|
1057
|
+
});
|
|
1058
|
+
return result;
|
|
1059
|
+
};
|
|
1060
|
+
/**
|
|
1061
|
+
* 冻结节日表,防止调用方后续误改
|
|
1062
|
+
*/
|
|
1063
|
+
const freezeFestivalMap = function(map) {
|
|
1064
|
+
Object.keys(map).forEach(function(key) {
|
|
1065
|
+
Object.freeze(map[key]);
|
|
1066
|
+
});
|
|
1067
|
+
return Object.freeze(map);
|
|
1068
|
+
};
|
|
1069
|
+
/**
|
|
1070
|
+
* 取某一天(MMDD)的节日名称,没有则返回共享的空数组。
|
|
1071
|
+
*
|
|
1072
|
+
* 返回的是只读数组:配置里的数组已被冻结,调用方要追加节日必须自己 copy。
|
|
1073
|
+
*/
|
|
1074
|
+
const festivalList = function(map, m, d) {
|
|
1075
|
+
return map[mmddOf(m, d)] ?? NO_FESTIVAL;
|
|
1076
|
+
};
|
|
1077
|
+
// src/utils/SetRest.ts
|
|
1078
|
+
/**
|
|
1079
|
+
* 按年、按列表合并调休数据,不修改入参
|
|
1080
|
+
*/
|
|
1081
|
+
const mergeRestMap = function(base, extra) {
|
|
1082
|
+
if (!extra) return base;
|
|
1083
|
+
const result = {};
|
|
1084
|
+
new Set(Object.keys(base).concat(Object.keys(extra))).forEach(function(year) {
|
|
1085
|
+
const left = base[year];
|
|
1086
|
+
const right = extra[year];
|
|
1087
|
+
if (left && right) result[year] = {
|
|
1088
|
+
xiu: Array.from(new Set(left.xiu.concat(right.xiu))),
|
|
1089
|
+
ban: Array.from(new Set(left.ban.concat(right.ban)))
|
|
1090
|
+
};
|
|
1091
|
+
else if (left) result[year] = left;
|
|
1092
|
+
else if (right) result[year] = right;
|
|
1093
|
+
});
|
|
1094
|
+
return result;
|
|
1095
|
+
};
|
|
1096
|
+
/**
|
|
1097
|
+
* 冻结调休表
|
|
1098
|
+
*/
|
|
1099
|
+
const freezeRestMap = function(map) {
|
|
1100
|
+
Object.keys(map).forEach(function(year) {
|
|
1101
|
+
Object.freeze(map[year].xiu);
|
|
1102
|
+
Object.freeze(map[year].ban);
|
|
1103
|
+
Object.freeze(map[year]);
|
|
1104
|
+
});
|
|
1105
|
+
return Object.freeze(map);
|
|
1106
|
+
};
|
|
1107
|
+
/**
|
|
1108
|
+
* 建立年份 -> Set 的索引
|
|
1109
|
+
*/
|
|
1110
|
+
const buildRestIndex = function(map) {
|
|
1111
|
+
const index = {};
|
|
1112
|
+
Object.keys(map).forEach(function(year) {
|
|
1113
|
+
index[year] = {
|
|
1114
|
+
xiu: new Set(map[year].xiu),
|
|
1115
|
+
ban: new Set(map[year].ban)
|
|
1116
|
+
};
|
|
1117
|
+
});
|
|
1118
|
+
return index;
|
|
1119
|
+
};
|
|
1120
|
+
/**
|
|
1121
|
+
* 查询某天的调休状态
|
|
1122
|
+
*
|
|
1123
|
+
* 只返回一个字符串,不为每天分配 { isHoliday, isBan } 对象。
|
|
1124
|
+
*/
|
|
1125
|
+
const restOf = function(index, y, m, d) {
|
|
1126
|
+
const entry = index[y];
|
|
1127
|
+
if (!entry) return void 0;
|
|
1128
|
+
const key = mmddOf(m, d);
|
|
1129
|
+
if (entry.ban.has(key)) return "ban";
|
|
1130
|
+
if (entry.xiu.has(key)) return "xiu";
|
|
1131
|
+
};
|
|
1132
|
+
// src/data/festival.json
|
|
1133
|
+
var festival_default = {
|
|
1134
|
+
sFtv: {
|
|
1135
|
+
"1001": [
|
|
1136
|
+
"国庆节",
|
|
1137
|
+
"国际音乐日",
|
|
1138
|
+
"国际老人节"
|
|
1139
|
+
],
|
|
1140
|
+
"1002": ["国际和平与民主自由斗争日"],
|
|
1141
|
+
"1004": [
|
|
1142
|
+
"世界动物日",
|
|
1143
|
+
"世界住房日",
|
|
1144
|
+
"国际建筑日"
|
|
1145
|
+
],
|
|
1146
|
+
"1005": ["世界教师日"],
|
|
1147
|
+
"1008": [
|
|
1148
|
+
"世界视觉日",
|
|
1149
|
+
"全国高血压日",
|
|
1150
|
+
"世界视力日"
|
|
1151
|
+
],
|
|
1152
|
+
"1009": ["世界邮政日"],
|
|
1153
|
+
"1010": ["世界精神卫生日", "辛亥革命纪念日"],
|
|
1154
|
+
"1011": ["萝莉节"],
|
|
1155
|
+
"1012": ["世界关节炎日"],
|
|
1156
|
+
"1013": [
|
|
1157
|
+
"国际减轻自然灾害日",
|
|
1158
|
+
"世界保健日",
|
|
1159
|
+
"国际标准时间日",
|
|
1160
|
+
"中国少年先锋队诞辰日"
|
|
1161
|
+
],
|
|
1162
|
+
"1014": ["世界标准日"],
|
|
1163
|
+
"1015": [
|
|
1164
|
+
"国际盲人节",
|
|
1165
|
+
"全球洗手日",
|
|
1166
|
+
"世界农村妇女日"
|
|
1167
|
+
],
|
|
1168
|
+
"1016": ["世界粮食节"],
|
|
1169
|
+
"1017": ["国际消除贫困日", "国家扶贫日"],
|
|
1170
|
+
"1019": ["世界镇痛日"],
|
|
1171
|
+
"1020": ["世界骨质疏松日", "世界厨师日"],
|
|
1172
|
+
"1022": ["世界传统医药日"],
|
|
1173
|
+
"1024": ["联合国日", "世界发展信息日"],
|
|
1174
|
+
"1025": [
|
|
1175
|
+
"人类天花绝迹日",
|
|
1176
|
+
"抗美援朝纪念日",
|
|
1177
|
+
"台湾光复纪念日"
|
|
1178
|
+
],
|
|
1179
|
+
"1028": ["世界男性健康日"],
|
|
1180
|
+
"1029": ["圣纪节"],
|
|
1181
|
+
"1031": ["世界勤俭日"],
|
|
1182
|
+
"1101": ["万圣节"],
|
|
1183
|
+
"1108": ["中国记者日", "记者节"],
|
|
1184
|
+
"1109": ["消防宣传日"],
|
|
1185
|
+
"1111": ["光棍节", "双十一购物狂欢节"],
|
|
1186
|
+
"1112": ["吉尼斯世界纪录日"],
|
|
1187
|
+
"1114": ["联合国糖尿病日", "屌丝节"],
|
|
1188
|
+
"1116": ["国际宽容日"],
|
|
1189
|
+
"1117": ["国际大学生节"],
|
|
1190
|
+
"1119": ["世界厕所日"],
|
|
1191
|
+
"1120": ["世界儿童日"],
|
|
1192
|
+
"1121": ["世界电视日", "世界问候日"],
|
|
1193
|
+
"1125": ["国际消除对妇女的暴力日"],
|
|
1194
|
+
"1129": ["国际声援巴勒斯坦人民日"],
|
|
1195
|
+
"1201": ["世界艾滋病日"],
|
|
1196
|
+
"1202": ["全国交通安全日"],
|
|
1197
|
+
"1203": ["国际残疾人日"],
|
|
1198
|
+
"1204": ["全国法制宣传日", "国家宪法日"],
|
|
1199
|
+
"1205": ["国际志愿人员日"],
|
|
1200
|
+
"1207": ["国际民航日"],
|
|
1201
|
+
"1209": ["世界足球日", "国际反腐败日"],
|
|
1202
|
+
"1210": ["世界人权日"],
|
|
1203
|
+
"1212": ["双十二购物狂欢节", "国际儿童电视广播日"],
|
|
1204
|
+
"1213": ["国家公祭日"],
|
|
1205
|
+
"1215": ["世界强化免疫日"],
|
|
1206
|
+
"1218": ["国际移徙者日"],
|
|
1207
|
+
"1219": ["联合国南南合作日"],
|
|
1208
|
+
"1220": ["澳门回归纪念"],
|
|
1209
|
+
"1221": ["国际篮球日"],
|
|
1210
|
+
"1224": ["平安夜", "长津湖战役胜利纪念日"],
|
|
1211
|
+
"1225": ["圣诞节"],
|
|
1212
|
+
"1226": ["毛泽东诞辰日"],
|
|
1213
|
+
"0101": ["元旦"],
|
|
1214
|
+
"0110": ["中国人民警察节"],
|
|
1215
|
+
"0131": ["国际麻风节"],
|
|
1216
|
+
"0126": ["国际海关日"],
|
|
1217
|
+
"0127": ["国际大屠杀纪念日"],
|
|
1218
|
+
"0202": ["世界湿地日"],
|
|
1219
|
+
"0207": ["国际声援南非日"],
|
|
1220
|
+
"0210": ["国际气象节"],
|
|
1221
|
+
"0214": ["情人节"],
|
|
1222
|
+
"0224": ["第三世界青年日"],
|
|
1223
|
+
"0204": ["世界抗癌症日", "世界癌症日"],
|
|
1224
|
+
"0221": ["国际母语日"],
|
|
1225
|
+
"0228": ["国际罕见病日", "世界居住条件调查日"],
|
|
1226
|
+
"0301": ["国际海豹日", "国际民防日"],
|
|
1227
|
+
"0303": ["全国爱耳日"],
|
|
1228
|
+
"0305": ["学雷锋纪念日", "中国青年志愿者服务日"],
|
|
1229
|
+
"0308": ["妇女节"],
|
|
1230
|
+
"0312": ["植树节", "世界肾脏日"],
|
|
1231
|
+
"0314": ["白色情人节", "国际警察日"],
|
|
1232
|
+
"0315": ["消费者权益日"],
|
|
1233
|
+
"0317": ["国际航海日", "中国国医节"],
|
|
1234
|
+
"0320": ["世界无肉日"],
|
|
1235
|
+
"0321": [
|
|
1236
|
+
"世界睡眠日",
|
|
1237
|
+
"世界森林日",
|
|
1238
|
+
"世界儿歌日",
|
|
1239
|
+
"国际消除种族歧视日",
|
|
1240
|
+
"世界诗歌日"
|
|
1241
|
+
],
|
|
1242
|
+
"0322": ["世界水日"],
|
|
1243
|
+
"0323": ["世界气象日"],
|
|
1244
|
+
"0311": ["国际尊严尊敬日"],
|
|
1245
|
+
"0316": ["手拉手情系贫困小伙伴全国统一行动日", "世界社会工作日"],
|
|
1246
|
+
"0324": ["世界防治结核病日"],
|
|
1247
|
+
"0307": ["女生节"],
|
|
1248
|
+
"0306": ["世界青光眼日"],
|
|
1249
|
+
"0327": ["世界戏剧日"],
|
|
1250
|
+
"0309": ["保护母亲河日"],
|
|
1251
|
+
"0318": ["全国科技人才活动日"],
|
|
1252
|
+
"0329": ["全国中小学生安全教育日"],
|
|
1253
|
+
"0410": ["耶稣受难日", "非洲环境保护日"],
|
|
1254
|
+
"0401": ["愚人节"],
|
|
1255
|
+
"0402": ["国际儿童图书日", "世界自闭症日"],
|
|
1256
|
+
"0407": ["世界卫生日"],
|
|
1257
|
+
"0411": ["世界帕金森病日"],
|
|
1258
|
+
"0422": ["世界地球日"],
|
|
1259
|
+
"0423": ["世界读书日", "中国海军建军节"],
|
|
1260
|
+
"0426": ["世界知识产权日", "世界防治疟疾日"],
|
|
1261
|
+
"0404": ["复活节", "清明节"],
|
|
1262
|
+
"0417": ["世界血友病日"],
|
|
1263
|
+
"0418": ["国际古迹遗址日"],
|
|
1264
|
+
"0421": ["秘书节"],
|
|
1265
|
+
"0425": ["全国儿童预防接种宣传日"],
|
|
1266
|
+
"0428": ["世界安全生产与健康日"],
|
|
1267
|
+
"0429": ["化学战受害者纪念日"],
|
|
1268
|
+
"0415": ["全民国家安全教育日"],
|
|
1269
|
+
"0424": ["中国航天日"],
|
|
1270
|
+
"0430": ["全国交通安全反思日"],
|
|
1271
|
+
"0501": ["劳动节"],
|
|
1272
|
+
"0504": ["青年节", "五四运动纪念日"],
|
|
1273
|
+
"0505": ["世界哮喘日", "碘缺乏病防治日"],
|
|
1274
|
+
"0508": ["世界红十字日", "世界微笑日"],
|
|
1275
|
+
"0512": ["国际护士节", "全国防灾减灾日"],
|
|
1276
|
+
"0510": ["中国品牌日"],
|
|
1277
|
+
"0515": ["国际家庭日", "防治碘缺乏病日"],
|
|
1278
|
+
"0517": [
|
|
1279
|
+
"世界电信日",
|
|
1280
|
+
"全国助残日",
|
|
1281
|
+
"世界高血压日"
|
|
1282
|
+
],
|
|
1283
|
+
"0518": ["国际博物馆日"],
|
|
1284
|
+
"0520": [
|
|
1285
|
+
"全国学生营养日",
|
|
1286
|
+
"全国母乳喂养宣传日",
|
|
1287
|
+
"世界计量日"
|
|
1288
|
+
],
|
|
1289
|
+
"0522": ["国际生物多样性日"],
|
|
1290
|
+
"0531": ["世界无烟日"],
|
|
1291
|
+
"0513": ["开斋节"],
|
|
1292
|
+
"0503": ["世界新闻自由日"],
|
|
1293
|
+
"0521": ["世界文化发展日"],
|
|
1294
|
+
"0526": ["世界向人体条件挑战日"],
|
|
1295
|
+
"0529": ["国际维和人员日"],
|
|
1296
|
+
"0509": ["救助贫困母亲日"],
|
|
1297
|
+
"0519": ["中国旅游日"],
|
|
1298
|
+
"0606": ["全国爱眼日"],
|
|
1299
|
+
"0601": ["国际儿童节", "世界牛奶日"],
|
|
1300
|
+
"0605": ["世界环境日"],
|
|
1301
|
+
"0612": ["中国文化遗产日", "世界无童工日"],
|
|
1302
|
+
"0620": ["世界难民日"],
|
|
1303
|
+
"0617": ["世界防治荒漠化和干旱日"],
|
|
1304
|
+
"0623": ["国际奥林匹克日"],
|
|
1305
|
+
"0625": ["全国土地日"],
|
|
1306
|
+
"0626": ["国际禁毒日", "联合国宪章日"],
|
|
1307
|
+
"0604": ["受侵略戕害的无辜儿童国际日"],
|
|
1308
|
+
"0608": ["世界海洋日"],
|
|
1309
|
+
"0614": ["世界献血日"],
|
|
1310
|
+
"0630": ["世界青年联欢节"],
|
|
1311
|
+
"0622": ["中国儿童慈善活动日"],
|
|
1312
|
+
"0701": [
|
|
1313
|
+
"香港回归纪念日",
|
|
1314
|
+
"建党节",
|
|
1315
|
+
"世界建筑日"
|
|
1316
|
+
],
|
|
1317
|
+
"0703": ["国际合作节"],
|
|
1318
|
+
"0711": ["世界人口日", "中国航海日"],
|
|
1319
|
+
"0720": ["古尔邦节", "人类月球日"],
|
|
1320
|
+
"0702": ["国际体育记者日"],
|
|
1321
|
+
"0706": ["国际接吻日"],
|
|
1322
|
+
"0707": ["七七事变纪念日"],
|
|
1323
|
+
"0708": ["世界过敏性疾病日"],
|
|
1324
|
+
"0718": ["纳尔逊·曼德拉国际日"],
|
|
1325
|
+
"0728": ["世界肝炎日"],
|
|
1326
|
+
"0731": ["非洲妇女节"],
|
|
1327
|
+
"0801": ["建军节"],
|
|
1328
|
+
"0803": ["男人节"],
|
|
1329
|
+
"0808": ["八卦节", "全民健身日"],
|
|
1330
|
+
"0809": ["国际土著人日"],
|
|
1331
|
+
"0826": ["纳米比亚日", "全国律师咨询日"],
|
|
1332
|
+
"0815": ["日本无条件投降日"],
|
|
1333
|
+
"0819": ["中国医师节"],
|
|
1334
|
+
"0903": ["中国人民抗日战争纪念日", "中国人民抗日战争胜利纪念日"],
|
|
1335
|
+
"0908": ["国际扫盲日"],
|
|
1336
|
+
"0910": ["教师节", "世界预防自杀日"],
|
|
1337
|
+
"0916": ["国际臭氧层保护日"],
|
|
1338
|
+
"0918": [
|
|
1339
|
+
"中国国耻日",
|
|
1340
|
+
"世界清洁地球日",
|
|
1341
|
+
"九一八事变",
|
|
1342
|
+
"全民国防教育日"
|
|
1343
|
+
],
|
|
1344
|
+
"0920": ["国际爱牙日", "公民道德宣传日"],
|
|
1345
|
+
"0921": [
|
|
1346
|
+
"国际和平日",
|
|
1347
|
+
"世界停火日",
|
|
1348
|
+
"世界阿尔茨海默病日"
|
|
1349
|
+
],
|
|
1350
|
+
"0927": [
|
|
1351
|
+
"国际聋人节",
|
|
1352
|
+
"世界旅游日",
|
|
1353
|
+
"国际聋人日"
|
|
1354
|
+
],
|
|
1355
|
+
"0928": ["孔子诞辰"],
|
|
1356
|
+
"0911": ["世界急救日"],
|
|
1357
|
+
"0915": ["国际民主日"],
|
|
1358
|
+
"0922": ["世界无车日"],
|
|
1359
|
+
"0926": ["世界避孕日"],
|
|
1360
|
+
"0930": ["国际翻译日", "烈士纪念日"],
|
|
1361
|
+
"0905": ["中华慈善日"]
|
|
1362
|
+
},
|
|
1363
|
+
lFtv: {
|
|
1364
|
+
"0101": ["春节"],
|
|
1365
|
+
"0107": ["人日"],
|
|
1366
|
+
"0115": ["元宵节"],
|
|
1367
|
+
"0202": ["龙抬头", "中和节"],
|
|
1368
|
+
"0303": ["上巳节"],
|
|
1369
|
+
"0505": ["端午节"],
|
|
1370
|
+
"0520": ["分龙节"],
|
|
1371
|
+
"0616": ["鲁班节"],
|
|
1372
|
+
"0707": ["七夕情人节"],
|
|
1373
|
+
"0714": ["鬼节(南方)"],
|
|
1374
|
+
"0715": ["中元节", "盂兰节"],
|
|
1375
|
+
"0815": ["中秋节"],
|
|
1376
|
+
"0909": ["重阳节"],
|
|
1377
|
+
"1001": ["寒衣节"],
|
|
1378
|
+
"1015": ["下元节"],
|
|
1379
|
+
"1208": ["腊八节"],
|
|
1380
|
+
"1223": ["小年", "祭灶节"],
|
|
1381
|
+
"1230": ["除夕"]
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
// src/data/rest.json
|
|
1385
|
+
var rest_default = {
|
|
1386
|
+
"2026": {
|
|
1387
|
+
"xiu": [
|
|
1388
|
+
"0101",
|
|
1389
|
+
"0102",
|
|
1390
|
+
"0103",
|
|
1391
|
+
"0215",
|
|
1392
|
+
"0216",
|
|
1393
|
+
"0217",
|
|
1394
|
+
"0218",
|
|
1395
|
+
"0219",
|
|
1396
|
+
"0220",
|
|
1397
|
+
"0221",
|
|
1398
|
+
"0222",
|
|
1399
|
+
"0223",
|
|
1400
|
+
"0404",
|
|
1401
|
+
"0405",
|
|
1402
|
+
"0406",
|
|
1403
|
+
"0501",
|
|
1404
|
+
"0502",
|
|
1405
|
+
"0503",
|
|
1406
|
+
"0504",
|
|
1407
|
+
"0505",
|
|
1408
|
+
"0619",
|
|
1409
|
+
"0620",
|
|
1410
|
+
"0621",
|
|
1411
|
+
"0925",
|
|
1412
|
+
"0926",
|
|
1413
|
+
"0927",
|
|
1414
|
+
"1001",
|
|
1415
|
+
"1002",
|
|
1416
|
+
"1003",
|
|
1417
|
+
"1004",
|
|
1418
|
+
"1005",
|
|
1419
|
+
"1006",
|
|
1420
|
+
"1007"
|
|
1421
|
+
],
|
|
1422
|
+
"ban": [
|
|
1423
|
+
"0104",
|
|
1424
|
+
"0214",
|
|
1425
|
+
"0228",
|
|
1426
|
+
"0509",
|
|
1427
|
+
"0920",
|
|
1428
|
+
"1010"
|
|
1429
|
+
]
|
|
1430
|
+
},
|
|
1431
|
+
"2027": {
|
|
1432
|
+
"xiu": [
|
|
1433
|
+
"0101",
|
|
1434
|
+
"0102",
|
|
1435
|
+
"0103",
|
|
1436
|
+
"0205",
|
|
1437
|
+
"0206",
|
|
1438
|
+
"0207",
|
|
1439
|
+
"0208",
|
|
1440
|
+
"0209",
|
|
1441
|
+
"0210",
|
|
1442
|
+
"0211",
|
|
1443
|
+
"0212",
|
|
1444
|
+
"0213",
|
|
1445
|
+
"0403",
|
|
1446
|
+
"0404",
|
|
1447
|
+
"0405",
|
|
1448
|
+
"0501",
|
|
1449
|
+
"0502",
|
|
1450
|
+
"0503",
|
|
1451
|
+
"0504",
|
|
1452
|
+
"0505",
|
|
1453
|
+
"0609",
|
|
1454
|
+
"0915",
|
|
1455
|
+
"1001",
|
|
1456
|
+
"1002",
|
|
1457
|
+
"1003",
|
|
1458
|
+
"1004",
|
|
1459
|
+
"1005",
|
|
1460
|
+
"1006",
|
|
1461
|
+
"1007"
|
|
1462
|
+
],
|
|
1463
|
+
"ban": [
|
|
1464
|
+
"0131",
|
|
1465
|
+
"0214",
|
|
1466
|
+
"0508",
|
|
1467
|
+
"0926",
|
|
1468
|
+
"1009"
|
|
1469
|
+
]
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
// src/config.ts
|
|
1473
|
+
/**
|
|
1474
|
+
* 冻结并预建索引;rest 没变时复用调用方给的 restIndex,避免重复建 Set
|
|
1475
|
+
*/
|
|
1476
|
+
const createResolved = function(sFtv, lFtv, rest, restIndex) {
|
|
1477
|
+
const frozenRest = freezeRestMap(rest);
|
|
1478
|
+
return {
|
|
1479
|
+
sFtv: freezeFestivalMap(sFtv),
|
|
1480
|
+
lFtv: freezeFestivalMap(lFtv),
|
|
1481
|
+
rest: frozenRest,
|
|
1482
|
+
restIndex: restIndex ?? buildRestIndex(frozenRest)
|
|
1483
|
+
};
|
|
1484
|
+
};
|
|
1485
|
+
/**
|
|
1486
|
+
* 内置数据在模块加载时就校验一遍,数据写错会立刻抛错而不是在运行时返回脏值
|
|
1487
|
+
*/
|
|
1488
|
+
const FESTIVAL_DATA = festival_default;
|
|
1489
|
+
const BASE = createResolved(normalizeFestivalMap(FESTIVAL_DATA.sFtv, "data/festival.json.sFtv"), normalizeFestivalMap(FESTIVAL_DATA.lFtv, "data/festival.json.lFtv", true), normalizeRestMap(rest_default, "data/rest.json"));
|
|
1490
|
+
const EMPTY = createResolved({}, {}, {});
|
|
1491
|
+
const FESTIVAL_FIELDS = ["sFtv", "lFtv"];
|
|
1492
|
+
const HOLIDAY_FIELDS = ["setFestival", "setRest"];
|
|
1493
|
+
/**
|
|
1494
|
+
* 校验并规范用户传入的配置,整份配置只在这里检查一次。
|
|
1495
|
+
*
|
|
1496
|
+
* 返回的片段是全新的数组 / 对象,不和入参共享引用;
|
|
1497
|
+
* 合并、冻结、建索引这些后续步骤都直接信任这里的结果,不再重复校验。
|
|
1498
|
+
*/
|
|
1499
|
+
const normalizeHoliday = function(config = {}, options = {}, path = "createCalendar") {
|
|
1500
|
+
assertPlainObject(config, path);
|
|
1501
|
+
assertKnownKeys(config, HOLIDAY_FIELDS, path);
|
|
1502
|
+
assertPlainObject(options, `${path}(options)`, "第二个参数必须是对象");
|
|
1503
|
+
if (options.replace !== void 0 && typeof options.replace !== "boolean") failType(`${path}(options).replace`, `必须是布尔值,收到 ${describe(options.replace)}`);
|
|
1504
|
+
let sFtv;
|
|
1505
|
+
let lFtv;
|
|
1506
|
+
if (config.setFestival !== void 0) {
|
|
1507
|
+
const festivalConfig = config.setFestival;
|
|
1508
|
+
assertPlainObject(festivalConfig, `${path}.setFestival`);
|
|
1509
|
+
assertKnownKeys(festivalConfig, FESTIVAL_FIELDS, `${path}.setFestival`);
|
|
1510
|
+
if (festivalConfig.sFtv !== void 0) sFtv = normalizeFestivalMap(festivalConfig.sFtv, `${path}.setFestival.sFtv`);
|
|
1511
|
+
if (festivalConfig.lFtv !== void 0) lFtv = normalizeFestivalMap(festivalConfig.lFtv, `${path}.setFestival.lFtv`, true);
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
replace: options.replace === true,
|
|
1515
|
+
sFtv,
|
|
1516
|
+
lFtv,
|
|
1517
|
+
rest: config.setRest === void 0 ? void 0 : normalizeRestMap(config.setRest, `${path}.setRest`)
|
|
1518
|
+
};
|
|
1519
|
+
};
|
|
1520
|
+
/**
|
|
1521
|
+
* 把补丁合并到 base 上;replace 为 true 时从空配置开始。
|
|
1522
|
+
*
|
|
1523
|
+
* 补丁来自 normalizeHoliday,已经是干净数据,这里只做合并、冻结和建索引。
|
|
1524
|
+
*/
|
|
1525
|
+
const applyHoliday = function(base, patch) {
|
|
1526
|
+
const from = patch.replace ? EMPTY : base;
|
|
1527
|
+
if (patch.sFtv === void 0 && patch.lFtv === void 0 && patch.rest === void 0) return from;
|
|
1528
|
+
return createResolved(patch.sFtv === void 0 ? from.sFtv : mergeFestivalMap(from.sFtv, patch.sFtv), patch.lFtv === void 0 ? from.lFtv : mergeFestivalMap(from.lFtv, patch.lFtv), patch.rest === void 0 ? from.rest : mergeRestMap(from.rest, patch.rest), patch.rest === void 0 ? from.restIndex : void 0);
|
|
1529
|
+
};
|
|
1530
|
+
/** 复制一份节日表,返回值可以安全修改 */
|
|
1531
|
+
const cloneFestivalMap = function(map) {
|
|
1532
|
+
const result = {};
|
|
1533
|
+
Object.keys(map).forEach(function(key) {
|
|
1534
|
+
result[key] = map[key].slice();
|
|
1535
|
+
});
|
|
1536
|
+
return result;
|
|
1537
|
+
};
|
|
1538
|
+
/** 复制一份调休表,返回值可以安全修改 */
|
|
1539
|
+
const cloneRestMap = function(rest) {
|
|
1540
|
+
const result = {};
|
|
1541
|
+
Object.keys(rest).forEach(function(year) {
|
|
1542
|
+
result[year] = {
|
|
1543
|
+
xiu: rest[year].xiu.slice(),
|
|
1544
|
+
ban: rest[year].ban.slice()
|
|
1545
|
+
};
|
|
1546
|
+
});
|
|
1547
|
+
return result;
|
|
1548
|
+
};
|
|
1549
|
+
/** 把内部配置转成可再次使用的快照 */
|
|
1550
|
+
const snapshotOf = function(resolved) {
|
|
1551
|
+
return {
|
|
1552
|
+
setFestival: {
|
|
1553
|
+
sFtv: cloneFestivalMap(resolved.sFtv),
|
|
1554
|
+
lFtv: cloneFestivalMap(resolved.lFtv)
|
|
1555
|
+
},
|
|
1556
|
+
setRest: cloneRestMap(resolved.rest)
|
|
1557
|
+
};
|
|
1558
|
+
};
|
|
1559
|
+
/** 只含用户传入部分的快照:own 不参与查询,不需要冻结 / 建索引 */
|
|
1560
|
+
const snapshotOfPatch = function(patch) {
|
|
1561
|
+
return {
|
|
1562
|
+
setFestival: {
|
|
1563
|
+
sFtv: cloneFestivalMap(patch.sFtv ?? {}),
|
|
1564
|
+
lFtv: cloneFestivalMap(patch.lFtv ?? {})
|
|
1565
|
+
},
|
|
1566
|
+
setRest: cloneRestMap(patch.rest ?? {})
|
|
1567
|
+
};
|
|
1568
|
+
};
|
|
1569
|
+
/**
|
|
1570
|
+
* createCalendar 用的配置。
|
|
1571
|
+
*
|
|
1572
|
+
* 只有一份可查询的 merged;getHolidays({ merged: false }) 要的"用户传入的那部分"
|
|
1573
|
+
* 不参与查询,所以不另外建 ResolvedConfig,需要时直接从 patch 生成快照。
|
|
1574
|
+
* replace 为 true 时 merged 本身就是用户那份,两条路径共用同一份配置。
|
|
1575
|
+
*/
|
|
1576
|
+
const createCalendarConfig = function(config = {}, options = {}) {
|
|
1577
|
+
const patch = normalizeHoliday(config, options);
|
|
1578
|
+
const merged = applyHoliday(BASE, patch);
|
|
1579
|
+
return {
|
|
1580
|
+
merged,
|
|
1581
|
+
snapshotOwn: patch.replace ? () => snapshotOf(merged) : () => snapshotOfPatch(patch)
|
|
1582
|
+
};
|
|
1583
|
+
};
|
|
1584
|
+
// src/index.ts
|
|
1585
|
+
/** 支持的公历 / 农历年份范围 */
|
|
1586
|
+
const MIN_YEAR = 1900;
|
|
1587
|
+
const MAX_YEAR = 2100;
|
|
1588
|
+
const DAY_MS = 864e5;
|
|
1589
|
+
/** 农历数据起点:1900-01-31,即农历 1900 年正月初一 */
|
|
1590
|
+
const MIN_SOLAR_UTC = Date.UTC(MIN_YEAR, 0, 31);
|
|
1591
|
+
/** 支持的最后一个公历日 */
|
|
1592
|
+
const MAX_SOLAR_UTC = Date.UTC(MAX_YEAR, 11, 31);
|
|
1593
|
+
/** 把公历年月日校验成合法的、在支持范围内的日期 */
|
|
1594
|
+
const validateSolar = function(y, m, d) {
|
|
1595
|
+
const year = assertInteger("solarToLunar(year)", y, MIN_YEAR, MAX_YEAR);
|
|
1596
|
+
const month = assertInteger("solarToLunar(month)", m, 1, 12);
|
|
1597
|
+
const day = assertInteger("solarToLunar(day)", d, 1, solarDays(year, month));
|
|
1598
|
+
const utc = Date.UTC(year, month - 1, day);
|
|
1599
|
+
if (utc < MIN_SOLAR_UTC) failRange("solarToLunar", `${year}-${month}-${day} 早于 1900-01-31,超出农历数据范围`);
|
|
1600
|
+
if (utc > MAX_SOLAR_UTC) failRange("solarToLunar", `${year}-${month}-${day} 晚于 2100-12-31,超出农历数据范围`);
|
|
1601
|
+
return {
|
|
1602
|
+
year,
|
|
1603
|
+
month,
|
|
1604
|
+
day
|
|
1605
|
+
};
|
|
1606
|
+
};
|
|
1607
|
+
/** 某一天的公历节日,包含按规则计算的浮动节日 */
|
|
1608
|
+
const solarFestivals = function(y, m, d, sFtv) {
|
|
1609
|
+
const base = festivalList(sFtv, m, d);
|
|
1610
|
+
const extra = [];
|
|
1611
|
+
const add = function(name) {
|
|
1612
|
+
if (base.indexOf(name) === -1 && extra.indexOf(name) === -1) extra.push(name);
|
|
1613
|
+
};
|
|
1614
|
+
if (m === 5 && d === nthWeekdayOfMonth(y, 5, 0, 2)) add("母亲节");
|
|
1615
|
+
if (m === 6 && d === nthWeekdayOfMonth(y, 6, 0, 3)) add("父亲节");
|
|
1616
|
+
if (m === 11 && d === nthWeekdayOfMonth(y, 11, 4, 4)) add("感恩节");
|
|
1617
|
+
return extra.length === 0 ? base : Object.freeze(base.concat(extra));
|
|
1618
|
+
};
|
|
1619
|
+
/**
|
|
1620
|
+
* 某一天的农历节日,除夕按“腊月最后一天”计算,不写死 12-30
|
|
1621
|
+
*
|
|
1622
|
+
* 闰月不过节:闰五月初五不算端午、闰八月十五不算中秋。
|
|
1623
|
+
*/
|
|
1624
|
+
const lunarFestivals = function(lYear, lMonth, lDay, isLeap, lFtv) {
|
|
1625
|
+
if (isLeap) return NO_FESTIVAL;
|
|
1626
|
+
const base = festivalList(lFtv, lMonth, lDay);
|
|
1627
|
+
if (lMonth === 12 && lDay === lMonthDays(lYear, 12) && base.indexOf("除夕") === -1) return Object.freeze(base.concat("除夕"));
|
|
1628
|
+
return base;
|
|
1629
|
+
};
|
|
1630
|
+
/**
|
|
1631
|
+
* 核心换算,入参必须是已经校验过、且在支持范围内的公历日期
|
|
1632
|
+
*/
|
|
1633
|
+
const computeLunar = function(config, sy, sm, sd) {
|
|
1634
|
+
const utc = Date.UTC(sy, sm - 1, sd);
|
|
1635
|
+
const offset = Math.round((utc - MIN_SOLAR_UTC) / DAY_MS);
|
|
1636
|
+
const { year, offsetInYear } = resolveLunarYear(offset);
|
|
1637
|
+
const segments = monthSegmentsOf(year);
|
|
1638
|
+
let segment = segments[0];
|
|
1639
|
+
for (const item of segments) {
|
|
1640
|
+
if (item.start > offsetInYear) break;
|
|
1641
|
+
segment = item;
|
|
1642
|
+
}
|
|
1643
|
+
const month = segment.month;
|
|
1644
|
+
const day = offsetInYear - segment.start + 1;
|
|
1645
|
+
const isLeap = segment.isLeap;
|
|
1646
|
+
const dayOfWeek = weekdayOf(sy, sm, sd);
|
|
1647
|
+
const firstTermDay = getTerm(sy, sm * 2 - 1);
|
|
1648
|
+
const secondTermDay = getTerm(sy, sm * 2);
|
|
1649
|
+
let term = null;
|
|
1650
|
+
if (firstTermDay === sd) term = getSolarTerm(sm * 2 - 2);
|
|
1651
|
+
if (secondTermDay === sd) term = getSolarTerm(sm * 2 - 1);
|
|
1652
|
+
const restState = restOf(config.restIndex, sy, sm, sd);
|
|
1653
|
+
const isXiu = restState === "xiu";
|
|
1654
|
+
const isBan = restState === "ban";
|
|
1655
|
+
const gzDayOffset = utc / DAY_MS - (sd - 1) + 25567 + 10 + sd - 1;
|
|
1656
|
+
const ganzhiYear = sm > 2 || sm === 2 && sd >= getTerm(sy, 3) ? sy : sy - 1;
|
|
1657
|
+
return {
|
|
1658
|
+
xiu: isXiu,
|
|
1659
|
+
ban: isBan,
|
|
1660
|
+
gljr: solarFestivals(sy, sm, sd, config.sFtv),
|
|
1661
|
+
nljr: lunarFestivals(year, month, day, isLeap, config.lFtv),
|
|
1662
|
+
lYear: year,
|
|
1663
|
+
lMonth: month,
|
|
1664
|
+
lDay: day,
|
|
1665
|
+
animal: getAnimal(year),
|
|
1666
|
+
lMonthCn: (isLeap ? "闰" : "") + toChinaMonth(month),
|
|
1667
|
+
lDayCn: toChinaDay(day),
|
|
1668
|
+
cYear: sy,
|
|
1669
|
+
cMonth: sm,
|
|
1670
|
+
cDay: sd,
|
|
1671
|
+
gzYear: toGanZhiYear(ganzhiYear),
|
|
1672
|
+
gzYearLunar: toGanZhiYear(year),
|
|
1673
|
+
gzMonth: toGanZhi((sy - 1900) * 12 + sm + (sd >= firstTermDay ? 12 : 11)),
|
|
1674
|
+
gzDay: toGanZhi(gzDayOffset),
|
|
1675
|
+
gzHour: toHourGanZhi(gzDayOffset),
|
|
1676
|
+
isToday: (function() {
|
|
1677
|
+
const now = /* @__PURE__ */ new Date();
|
|
1678
|
+
return now.getFullYear() === sy && now.getMonth() + 1 === sm && now.getDate() === sd;
|
|
1679
|
+
})(),
|
|
1680
|
+
isLeap,
|
|
1681
|
+
nWeek: dayOfWeek === 0 ? 7 : dayOfWeek,
|
|
1682
|
+
dayOfWeek,
|
|
1683
|
+
ncWeek: "星期" + toChinaNum(dayOfWeek),
|
|
1684
|
+
term,
|
|
1685
|
+
astro: toAstro(sm, sd)
|
|
1686
|
+
};
|
|
1687
|
+
};
|
|
1688
|
+
/** 公历转农历的内部实现,节假日配置由调用方固定 */
|
|
1689
|
+
const solarToLunarWith = function(config, y, m, d) {
|
|
1690
|
+
if (y === void 0 && m === void 0 && d === void 0) {
|
|
1691
|
+
const now = /* @__PURE__ */ new Date();
|
|
1692
|
+
const today = validateSolar(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
1693
|
+
return computeLunar(config, today.year, today.month, today.day);
|
|
1694
|
+
}
|
|
1695
|
+
if (y === void 0 || m === void 0 || d === void 0) failRange("solarToLunar", `年、月、日必须同时传入,或全部不传以取今天(收到 year=${String(y)}, month=${String(m)}, day=${String(d)})`);
|
|
1696
|
+
const date = validateSolar(y, m, d);
|
|
1697
|
+
return computeLunar(config, date.year, date.month, date.day);
|
|
1698
|
+
};
|
|
1699
|
+
/** 农历转公历的内部实现,节假日配置由调用方固定 */
|
|
1700
|
+
const lunarToSolarWith = function(config, y, m, d, isLeapMonth) {
|
|
1701
|
+
const year = assertInteger("lunarToSolar(year)", y, MIN_YEAR, MAX_YEAR);
|
|
1702
|
+
const month = assertInteger("lunarToSolar(month)", m, 1, 12);
|
|
1703
|
+
if (isLeapMonth !== void 0 && typeof isLeapMonth !== "boolean") throw failType("lunarToSolar(isLeapMonth)", `必须是布尔值,收到 ${describe(isLeapMonth)}`);
|
|
1704
|
+
const leap = leapMonth(year);
|
|
1705
|
+
const wantLeap = isLeapMonth === true;
|
|
1706
|
+
if (wantLeap && leap !== month) failRange("lunarToSolar(isLeapMonth)", `${year} 年${leap === 0 ? "没有闰月" : `的闰月是闰${leap}月`},不能按闰${month}月换算`);
|
|
1707
|
+
const maxDay = wantLeap ? leapDays(year) : lMonthDays(year, month);
|
|
1708
|
+
const day = assertInteger("lunarToSolar(day)", d, 1, maxDay);
|
|
1709
|
+
const segment = monthSegmentsOf(year).find((item) => item.month === month && item.isLeap === wantLeap);
|
|
1710
|
+
if (!segment) return failRange("lunarToSolar", `${year} 年${wantLeap ? "闰" : ""}${month}月不存在`);
|
|
1711
|
+
const offset = yearStartOf(year) + segment.start;
|
|
1712
|
+
const utc = MIN_SOLAR_UTC + (offset + day - 1) * DAY_MS;
|
|
1713
|
+
if (utc > MAX_SOLAR_UTC) failRange("lunarToSolar", `农历 ${year}-${month}-${day} 换算后晚于 2100-12-31,超出支持范围`);
|
|
1714
|
+
const date = new Date(utc);
|
|
1715
|
+
return computeLunar(config, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
|
|
1716
|
+
};
|
|
1717
|
+
/**
|
|
1718
|
+
* 创建固定节假日配置的日历实例
|
|
1719
|
+
*
|
|
1720
|
+
* 不读取也不修改任何全局状态;配置在创建时校验并冻结,
|
|
1721
|
+
* 多个实例之间互不影响。不传配置时使用内置数据。
|
|
1722
|
+
*
|
|
1723
|
+
* @param config 节假日 / 调休配置
|
|
1724
|
+
* @param options replace 为 true 时忽略内置数据
|
|
1725
|
+
* @throws TypeError 配置格式不合法(含具体字段路径)
|
|
1726
|
+
* @example
|
|
1727
|
+
* const cc = createCalendar({ setRest: { 2027: { xiu: ["0101"] } } });
|
|
1728
|
+
* cc.solarToLunar(2027, 1, 1).xiu; // true
|
|
1729
|
+
*/
|
|
1730
|
+
const createCalendar = function(config = {}, options = {}) {
|
|
1731
|
+
const { merged, snapshotOwn } = createCalendarConfig(config, options);
|
|
1732
|
+
return {
|
|
1733
|
+
/** 公历转农历,不传参返回今天 */
|
|
1734
|
+
solarToLunar: (y, m, d) => solarToLunarWith(merged, y, m, d),
|
|
1735
|
+
/** 农历转公历 */
|
|
1736
|
+
lunarToSolar: (y, m, d, isLeapMonth) => lunarToSolarWith(merged, y, m, d, isLeapMonth),
|
|
1737
|
+
/** 当前配置的深拷贝;merged 为 false 时只导出用户传入的部分 */
|
|
1738
|
+
getHolidays: (getOptions) => {
|
|
1739
|
+
if (getOptions !== void 0) assertPlainObject(getOptions, "getHolidays(options)");
|
|
1740
|
+
if (getOptions?.merged !== void 0 && typeof getOptions.merged !== "boolean") throw failType("getHolidays(options).merged", `必须是布尔值,收到 ${describe(getOptions.merged)}`);
|
|
1741
|
+
return getOptions?.merged === false ? snapshotOwn() : snapshotOf(merged);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
};
|
|
1745
|
+
const utils = {
|
|
1746
|
+
lYearDays,
|
|
1747
|
+
leapMonth,
|
|
1748
|
+
leapDays,
|
|
1749
|
+
lMonthDays,
|
|
1750
|
+
toChinaMonth,
|
|
1751
|
+
toChinaDay,
|
|
1752
|
+
toChinaNum,
|
|
1753
|
+
solarDays,
|
|
1754
|
+
solarFirstWeek,
|
|
1755
|
+
nthWeekdayOfMonth,
|
|
1756
|
+
toAstro,
|
|
1757
|
+
getAnimal,
|
|
1758
|
+
getTerm,
|
|
1759
|
+
getSolarTerm,
|
|
1760
|
+
toGanZhi,
|
|
1761
|
+
toGanZhiYear,
|
|
1762
|
+
toHourGanZhi
|
|
1763
|
+
};
|
|
1764
|
+
exports.createCalendar = createCalendar;
|
|
1765
|
+
exports.default = createCalendar;
|
|
1766
|
+
exports.utils = utils;
|