@sanguo-dev/calendar 0.0.0-stage → 1.0.0
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 +229 -3
- 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 +68 -6
- 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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sFtv": {
|
|
3
|
+
"1001": ["国庆节", "国际音乐日", "国际老人节"],
|
|
4
|
+
"1002": ["国际和平与民主自由斗争日"],
|
|
5
|
+
"1004": ["世界动物日", "世界住房日", "国际建筑日"],
|
|
6
|
+
"1005": ["世界教师日"],
|
|
7
|
+
"1008": ["世界视觉日", "全国高血压日", "世界视力日"],
|
|
8
|
+
"1009": ["世界邮政日"],
|
|
9
|
+
"1010": ["世界精神卫生日", "辛亥革命纪念日"],
|
|
10
|
+
"1011": ["萝莉节"],
|
|
11
|
+
"1012": ["世界关节炎日"],
|
|
12
|
+
"1013": ["国际减轻自然灾害日", "世界保健日", "国际标准时间日", "中国少年先锋队诞辰日"],
|
|
13
|
+
"1014": ["世界标准日"],
|
|
14
|
+
"1015": ["国际盲人节", "全球洗手日", "世界农村妇女日"],
|
|
15
|
+
"1016": ["世界粮食节"],
|
|
16
|
+
"1017": ["国际消除贫困日", "国家扶贫日"],
|
|
17
|
+
"1019": ["世界镇痛日"],
|
|
18
|
+
"1020": ["世界骨质疏松日", "世界厨师日"],
|
|
19
|
+
"1022": ["世界传统医药日"],
|
|
20
|
+
"1024": ["联合国日", "世界发展信息日"],
|
|
21
|
+
"1025": ["人类天花绝迹日", "抗美援朝纪念日", "台湾光复纪念日"],
|
|
22
|
+
"1028": ["世界男性健康日"],
|
|
23
|
+
"1029": ["圣纪节"],
|
|
24
|
+
"1031": ["世界勤俭日"],
|
|
25
|
+
"1101": ["万圣节"],
|
|
26
|
+
"1108": ["中国记者日", "记者节"],
|
|
27
|
+
"1109": ["消防宣传日"],
|
|
28
|
+
"1111": ["光棍节", "双十一购物狂欢节"],
|
|
29
|
+
"1112": ["吉尼斯世界纪录日"],
|
|
30
|
+
"1114": ["联合国糖尿病日", "屌丝节"],
|
|
31
|
+
"1116": ["国际宽容日"],
|
|
32
|
+
"1117": ["国际大学生节"],
|
|
33
|
+
"1119": ["世界厕所日"],
|
|
34
|
+
"1120": ["世界儿童日"],
|
|
35
|
+
"1121": ["世界电视日", "世界问候日"],
|
|
36
|
+
"1125": ["国际消除对妇女的暴力日"],
|
|
37
|
+
"1129": ["国际声援巴勒斯坦人民日"],
|
|
38
|
+
"1201": ["世界艾滋病日"],
|
|
39
|
+
"1202": ["全国交通安全日"],
|
|
40
|
+
"1203": ["国际残疾人日"],
|
|
41
|
+
"1204": ["全国法制宣传日", "国家宪法日"],
|
|
42
|
+
"1205": ["国际志愿人员日"],
|
|
43
|
+
"1207": ["国际民航日"],
|
|
44
|
+
"1209": ["世界足球日", "国际反腐败日"],
|
|
45
|
+
"1210": ["世界人权日"],
|
|
46
|
+
"1212": ["双十二购物狂欢节", "国际儿童电视广播日"],
|
|
47
|
+
"1213": ["国家公祭日"],
|
|
48
|
+
"1215": ["世界强化免疫日"],
|
|
49
|
+
"1218": ["国际移徙者日"],
|
|
50
|
+
"1219": ["联合国南南合作日"],
|
|
51
|
+
"1220": ["澳门回归纪念"],
|
|
52
|
+
"1221": ["国际篮球日"],
|
|
53
|
+
"1224": ["平安夜", "长津湖战役胜利纪念日"],
|
|
54
|
+
"1225": ["圣诞节"],
|
|
55
|
+
"1226": ["毛泽东诞辰日"],
|
|
56
|
+
"0101": ["元旦"],
|
|
57
|
+
"0110": ["中国人民警察节"],
|
|
58
|
+
"0131": ["国际麻风节"],
|
|
59
|
+
"0126": ["国际海关日"],
|
|
60
|
+
"0127": ["国际大屠杀纪念日"],
|
|
61
|
+
"0202": ["世界湿地日"],
|
|
62
|
+
"0207": ["国际声援南非日"],
|
|
63
|
+
"0210": ["国际气象节"],
|
|
64
|
+
"0214": ["情人节"],
|
|
65
|
+
"0224": ["第三世界青年日"],
|
|
66
|
+
"0204": ["世界抗癌症日", "世界癌症日"],
|
|
67
|
+
"0221": ["国际母语日"],
|
|
68
|
+
"0228": ["国际罕见病日", "世界居住条件调查日"],
|
|
69
|
+
"0301": ["国际海豹日", "国际民防日"],
|
|
70
|
+
"0303": ["全国爱耳日"],
|
|
71
|
+
"0305": ["学雷锋纪念日", "中国青年志愿者服务日"],
|
|
72
|
+
"0308": ["妇女节"],
|
|
73
|
+
"0312": ["植树节", "世界肾脏日"],
|
|
74
|
+
"0314": ["白色情人节", "国际警察日"],
|
|
75
|
+
"0315": ["消费者权益日"],
|
|
76
|
+
"0317": ["国际航海日", "中国国医节"],
|
|
77
|
+
"0320": ["世界无肉日"],
|
|
78
|
+
"0321": ["世界睡眠日", "世界森林日", "世界儿歌日", "国际消除种族歧视日", "世界诗歌日"],
|
|
79
|
+
"0322": ["世界水日"],
|
|
80
|
+
"0323": ["世界气象日"],
|
|
81
|
+
"0311": ["国际尊严尊敬日"],
|
|
82
|
+
"0316": ["手拉手情系贫困小伙伴全国统一行动日", "世界社会工作日"],
|
|
83
|
+
"0324": ["世界防治结核病日"],
|
|
84
|
+
"0307": ["女生节"],
|
|
85
|
+
"0306": ["世界青光眼日"],
|
|
86
|
+
"0327": ["世界戏剧日"],
|
|
87
|
+
"0309": ["保护母亲河日"],
|
|
88
|
+
"0318": ["全国科技人才活动日"],
|
|
89
|
+
"0329": ["全国中小学生安全教育日"],
|
|
90
|
+
"0410": ["耶稣受难日", "非洲环境保护日"],
|
|
91
|
+
"0401": ["愚人节"],
|
|
92
|
+
"0402": ["国际儿童图书日", "世界自闭症日"],
|
|
93
|
+
"0407": ["世界卫生日"],
|
|
94
|
+
"0411": ["世界帕金森病日"],
|
|
95
|
+
"0422": ["世界地球日"],
|
|
96
|
+
"0423": ["世界读书日", "中国海军建军节"],
|
|
97
|
+
"0426": ["世界知识产权日", "世界防治疟疾日"],
|
|
98
|
+
"0404": ["复活节", "清明节"],
|
|
99
|
+
"0417": ["世界血友病日"],
|
|
100
|
+
"0418": ["国际古迹遗址日"],
|
|
101
|
+
"0421": ["秘书节"],
|
|
102
|
+
"0425": ["全国儿童预防接种宣传日"],
|
|
103
|
+
"0428": ["世界安全生产与健康日"],
|
|
104
|
+
"0429": ["化学战受害者纪念日"],
|
|
105
|
+
"0415": ["全民国家安全教育日"],
|
|
106
|
+
"0424": ["中国航天日"],
|
|
107
|
+
"0430": ["全国交通安全反思日"],
|
|
108
|
+
"0501": ["劳动节"],
|
|
109
|
+
"0504": ["青年节", "五四运动纪念日"],
|
|
110
|
+
"0505": ["世界哮喘日", "碘缺乏病防治日"],
|
|
111
|
+
"0508": ["世界红十字日", "世界微笑日"],
|
|
112
|
+
"0512": ["国际护士节", "全国防灾减灾日"],
|
|
113
|
+
"0510": ["中国品牌日"],
|
|
114
|
+
"0515": ["国际家庭日", "防治碘缺乏病日"],
|
|
115
|
+
"0517": ["世界电信日", "全国助残日", "世界高血压日"],
|
|
116
|
+
"0518": ["国际博物馆日"],
|
|
117
|
+
"0520": ["全国学生营养日", "全国母乳喂养宣传日", "世界计量日"],
|
|
118
|
+
"0522": ["国际生物多样性日"],
|
|
119
|
+
"0531": ["世界无烟日"],
|
|
120
|
+
"0513": ["开斋节"],
|
|
121
|
+
"0503": ["世界新闻自由日"],
|
|
122
|
+
"0521": ["世界文化发展日"],
|
|
123
|
+
"0526": ["世界向人体条件挑战日"],
|
|
124
|
+
"0529": ["国际维和人员日"],
|
|
125
|
+
"0509": ["救助贫困母亲日"],
|
|
126
|
+
"0519": ["中国旅游日"],
|
|
127
|
+
"0606": ["全国爱眼日"],
|
|
128
|
+
"0601": ["国际儿童节", "世界牛奶日"],
|
|
129
|
+
"0605": ["世界环境日"],
|
|
130
|
+
"0612": ["中国文化遗产日", "世界无童工日"],
|
|
131
|
+
"0620": ["世界难民日"],
|
|
132
|
+
"0617": ["世界防治荒漠化和干旱日"],
|
|
133
|
+
"0623": ["国际奥林匹克日"],
|
|
134
|
+
"0625": ["全国土地日"],
|
|
135
|
+
"0626": ["国际禁毒日", "联合国宪章日"],
|
|
136
|
+
"0604": ["受侵略戕害的无辜儿童国际日"],
|
|
137
|
+
"0608": ["世界海洋日"],
|
|
138
|
+
"0614": ["世界献血日"],
|
|
139
|
+
"0630": ["世界青年联欢节"],
|
|
140
|
+
"0622": ["中国儿童慈善活动日"],
|
|
141
|
+
"0701": ["香港回归纪念日", "建党节", "世界建筑日"],
|
|
142
|
+
"0703": ["国际合作节"],
|
|
143
|
+
"0711": ["世界人口日", "中国航海日"],
|
|
144
|
+
"0720": ["古尔邦节", "人类月球日"],
|
|
145
|
+
"0702": ["国际体育记者日"],
|
|
146
|
+
"0706": ["国际接吻日"],
|
|
147
|
+
"0707": ["七七事变纪念日"],
|
|
148
|
+
"0708": ["世界过敏性疾病日"],
|
|
149
|
+
"0718": ["纳尔逊·曼德拉国际日"],
|
|
150
|
+
"0728": ["世界肝炎日"],
|
|
151
|
+
"0731": ["非洲妇女节"],
|
|
152
|
+
"0801": ["建军节"],
|
|
153
|
+
"0803": ["男人节"],
|
|
154
|
+
"0808": ["八卦节", "全民健身日"],
|
|
155
|
+
"0809": ["国际土著人日"],
|
|
156
|
+
"0826": ["纳米比亚日", "全国律师咨询日"],
|
|
157
|
+
"0815": ["日本无条件投降日"],
|
|
158
|
+
"0819": ["中国医师节"],
|
|
159
|
+
"0903": ["中国人民抗日战争纪念日", "中国人民抗日战争胜利纪念日"],
|
|
160
|
+
"0908": ["国际扫盲日"],
|
|
161
|
+
"0910": ["教师节", "世界预防自杀日"],
|
|
162
|
+
"0916": ["国际臭氧层保护日"],
|
|
163
|
+
"0918": ["中国国耻日", "世界清洁地球日", "九一八事变", "全民国防教育日"],
|
|
164
|
+
"0920": ["国际爱牙日", "公民道德宣传日"],
|
|
165
|
+
"0921": ["国际和平日", "世界停火日", "世界阿尔茨海默病日"],
|
|
166
|
+
"0927": ["国际聋人节", "世界旅游日", "国际聋人日"],
|
|
167
|
+
"0928": ["孔子诞辰"],
|
|
168
|
+
"0911": ["世界急救日"],
|
|
169
|
+
"0915": ["国际民主日"],
|
|
170
|
+
"0922": ["世界无车日"],
|
|
171
|
+
"0926": ["世界避孕日"],
|
|
172
|
+
"0930": ["国际翻译日", "烈士纪念日"],
|
|
173
|
+
"0905": ["中华慈善日"]
|
|
174
|
+
},
|
|
175
|
+
"lFtv": {
|
|
176
|
+
"0101": ["春节"],
|
|
177
|
+
"0107": ["人日"],
|
|
178
|
+
"0115": ["元宵节"],
|
|
179
|
+
"0202": ["龙抬头", "中和节"],
|
|
180
|
+
"0303": ["上巳节"],
|
|
181
|
+
"0505": ["端午节"],
|
|
182
|
+
"0520": ["分龙节"],
|
|
183
|
+
"0616": ["鲁班节"],
|
|
184
|
+
"0707": ["七夕情人节"],
|
|
185
|
+
"0714": ["鬼节(南方)"],
|
|
186
|
+
"0715": ["中元节", "盂兰节"],
|
|
187
|
+
"0815": ["中秋节"],
|
|
188
|
+
"0909": ["重阳节"],
|
|
189
|
+
"1001": ["寒衣节"],
|
|
190
|
+
"1015": ["下元节"],
|
|
191
|
+
"1208": ["腊八节"],
|
|
192
|
+
"1223": ["小年", "祭灶节"],
|
|
193
|
+
"1230": ["除夕"]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"2026": {
|
|
3
|
+
"xiu": [
|
|
4
|
+
"0101",
|
|
5
|
+
"0102",
|
|
6
|
+
"0103",
|
|
7
|
+
"0215",
|
|
8
|
+
"0216",
|
|
9
|
+
"0217",
|
|
10
|
+
"0218",
|
|
11
|
+
"0219",
|
|
12
|
+
"0220",
|
|
13
|
+
"0221",
|
|
14
|
+
"0222",
|
|
15
|
+
"0223",
|
|
16
|
+
"0404",
|
|
17
|
+
"0405",
|
|
18
|
+
"0406",
|
|
19
|
+
"0501",
|
|
20
|
+
"0502",
|
|
21
|
+
"0503",
|
|
22
|
+
"0504",
|
|
23
|
+
"0505",
|
|
24
|
+
"0619",
|
|
25
|
+
"0620",
|
|
26
|
+
"0621",
|
|
27
|
+
"0925",
|
|
28
|
+
"0926",
|
|
29
|
+
"0927",
|
|
30
|
+
"1001",
|
|
31
|
+
"1002",
|
|
32
|
+
"1003",
|
|
33
|
+
"1004",
|
|
34
|
+
"1005",
|
|
35
|
+
"1006",
|
|
36
|
+
"1007"
|
|
37
|
+
],
|
|
38
|
+
"ban": ["0104", "0214", "0228", "0509", "0920", "1010"]
|
|
39
|
+
},
|
|
40
|
+
"2027": {
|
|
41
|
+
"xiu": [
|
|
42
|
+
"0101",
|
|
43
|
+
"0102",
|
|
44
|
+
"0103",
|
|
45
|
+
"0205",
|
|
46
|
+
"0206",
|
|
47
|
+
"0207",
|
|
48
|
+
"0208",
|
|
49
|
+
"0209",
|
|
50
|
+
"0210",
|
|
51
|
+
"0211",
|
|
52
|
+
"0212",
|
|
53
|
+
"0213",
|
|
54
|
+
"0403",
|
|
55
|
+
"0404",
|
|
56
|
+
"0405",
|
|
57
|
+
"0501",
|
|
58
|
+
"0502",
|
|
59
|
+
"0503",
|
|
60
|
+
"0504",
|
|
61
|
+
"0505",
|
|
62
|
+
"0609",
|
|
63
|
+
"0915",
|
|
64
|
+
"1001",
|
|
65
|
+
"1002",
|
|
66
|
+
"1003",
|
|
67
|
+
"1004",
|
|
68
|
+
"1005",
|
|
69
|
+
"1006",
|
|
70
|
+
"1007"
|
|
71
|
+
],
|
|
72
|
+
"ban": ["0131", "0214", "0508", "0926", "1009"]
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lYearDays,
|
|
3
|
+
leapMonth,
|
|
4
|
+
leapDays,
|
|
5
|
+
lMonthDays,
|
|
6
|
+
yearStartOf,
|
|
7
|
+
monthSegmentsOf,
|
|
8
|
+
resolveLunarYear,
|
|
9
|
+
toGanZhiYear,
|
|
10
|
+
toGanZhi,
|
|
11
|
+
toHourGanZhi,
|
|
12
|
+
toChinaMonth,
|
|
13
|
+
toChinaDay,
|
|
14
|
+
getSolarTerm,
|
|
15
|
+
} from "./utils/LunarFunction";
|
|
16
|
+
import type { LunarMonthSegment } from "./utils/LunarFunction";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
toAstro,
|
|
20
|
+
getTerm,
|
|
21
|
+
getAnimal,
|
|
22
|
+
solarDays,
|
|
23
|
+
solarFirstWeek,
|
|
24
|
+
weekdayOf,
|
|
25
|
+
toChinaNum,
|
|
26
|
+
nthWeekdayOfMonth,
|
|
27
|
+
} from "./utils/SolarFunction";
|
|
28
|
+
|
|
29
|
+
import { assertInteger, assertPlainObject, describe, failRange, failType } from "./utils/Assert";
|
|
30
|
+
import { festivalList, NO_FESTIVAL } from "./utils/SetFestival";
|
|
31
|
+
import { restOf } from "./utils/SetRest";
|
|
32
|
+
import { createCalendarConfig, snapshotOf } from "./config";
|
|
33
|
+
import type { ResolvedConfig } from "./config";
|
|
34
|
+
import type { Calendar, HolidayConfig, LunarResult, SetHolidayOptions } from "./types";
|
|
35
|
+
|
|
36
|
+
export type {
|
|
37
|
+
LunarResult,
|
|
38
|
+
Calendar,
|
|
39
|
+
FestivalMap,
|
|
40
|
+
FestivalConfig,
|
|
41
|
+
RestEntry,
|
|
42
|
+
RestConfig,
|
|
43
|
+
HolidayConfig,
|
|
44
|
+
HolidaySnapshot,
|
|
45
|
+
GetHolidaysOptions,
|
|
46
|
+
SetHolidayOptions,
|
|
47
|
+
} from "./types";
|
|
48
|
+
|
|
49
|
+
/** 支持的公历 / 农历年份范围 */
|
|
50
|
+
const MIN_YEAR = 1900;
|
|
51
|
+
const MAX_YEAR = 2100;
|
|
52
|
+
const DAY_MS = 86400000;
|
|
53
|
+
/** 农历数据起点:1900-01-31,即农历 1900 年正月初一 */
|
|
54
|
+
const MIN_SOLAR_UTC = Date.UTC(MIN_YEAR, 0, 31);
|
|
55
|
+
/** 支持的最后一个公历日 */
|
|
56
|
+
const MAX_SOLAR_UTC = Date.UTC(MAX_YEAR, 11, 31);
|
|
57
|
+
|
|
58
|
+
/** 把公历年月日校验成合法的、在支持范围内的日期 */
|
|
59
|
+
const validateSolar = function (
|
|
60
|
+
y: unknown,
|
|
61
|
+
m: unknown,
|
|
62
|
+
d: unknown,
|
|
63
|
+
): { year: number; month: number; day: number } {
|
|
64
|
+
const year = assertInteger("solarToLunar(year)", y, MIN_YEAR, MAX_YEAR);
|
|
65
|
+
const month = assertInteger("solarToLunar(month)", m, 1, 12);
|
|
66
|
+
const day = assertInteger("solarToLunar(day)", d, 1, solarDays(year, month));
|
|
67
|
+
const utc = Date.UTC(year, month - 1, day);
|
|
68
|
+
if (utc < MIN_SOLAR_UTC) {
|
|
69
|
+
failRange("solarToLunar", `${year}-${month}-${day} 早于 1900-01-31,超出农历数据范围`);
|
|
70
|
+
}
|
|
71
|
+
if (utc > MAX_SOLAR_UTC) {
|
|
72
|
+
failRange("solarToLunar", `${year}-${month}-${day} 晚于 2100-12-31,超出农历数据范围`);
|
|
73
|
+
}
|
|
74
|
+
return { year, month, day };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** 某一天的公历节日,包含按规则计算的浮动节日 */
|
|
78
|
+
const solarFestivals = function (
|
|
79
|
+
y: number,
|
|
80
|
+
m: number,
|
|
81
|
+
d: number,
|
|
82
|
+
sFtv: Record<string, string[]>,
|
|
83
|
+
): readonly string[] {
|
|
84
|
+
const base = festivalList(sFtv, m, d);
|
|
85
|
+
const extra: string[] = [];
|
|
86
|
+
|
|
87
|
+
const add = function (name: string) {
|
|
88
|
+
if (base.indexOf(name) === -1 && extra.indexOf(name) === -1) extra.push(name);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (m === 5 && d === nthWeekdayOfMonth(y, 5, 0, 2)) add("母亲节");
|
|
92
|
+
if (m === 6 && d === nthWeekdayOfMonth(y, 6, 0, 3)) add("父亲节");
|
|
93
|
+
if (m === 11 && d === nthWeekdayOfMonth(y, 11, 4, 4)) add("感恩节");
|
|
94
|
+
|
|
95
|
+
// 没有浮动节日时直接返回配置里的只读数组,省掉一次拷贝
|
|
96
|
+
return extra.length === 0 ? base : Object.freeze(base.concat(extra));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 某一天的农历节日,除夕按“腊月最后一天”计算,不写死 12-30
|
|
101
|
+
*
|
|
102
|
+
* 闰月不过节:闰五月初五不算端午、闰八月十五不算中秋。
|
|
103
|
+
*/
|
|
104
|
+
const lunarFestivals = function (
|
|
105
|
+
lYear: number,
|
|
106
|
+
lMonth: number,
|
|
107
|
+
lDay: number,
|
|
108
|
+
isLeap: boolean,
|
|
109
|
+
lFtv: Record<string, string[]>,
|
|
110
|
+
): readonly string[] {
|
|
111
|
+
if (isLeap) return NO_FESTIVAL;
|
|
112
|
+
|
|
113
|
+
const base = festivalList(lFtv, lMonth, lDay);
|
|
114
|
+
if (lMonth === 12 && lDay === lMonthDays(lYear, 12) && base.indexOf("除夕") === -1) {
|
|
115
|
+
return Object.freeze(base.concat("除夕"));
|
|
116
|
+
}
|
|
117
|
+
return base;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 核心换算,入参必须是已经校验过、且在支持范围内的公历日期
|
|
122
|
+
*/
|
|
123
|
+
const computeLunar = function (
|
|
124
|
+
config: ResolvedConfig,
|
|
125
|
+
sy: number,
|
|
126
|
+
sm: number,
|
|
127
|
+
sd: number,
|
|
128
|
+
): LunarResult {
|
|
129
|
+
const utc = Date.UTC(sy, sm - 1, sd);
|
|
130
|
+
const offset = Math.round((utc - MIN_SOLAR_UTC) / DAY_MS);
|
|
131
|
+
const { year, offsetInYear } = resolveLunarYear(offset);
|
|
132
|
+
|
|
133
|
+
// 月份段(闰月单独一段)按顺序扫描,最多 13 段
|
|
134
|
+
const segments = monthSegmentsOf(year);
|
|
135
|
+
let segment = segments[0] as LunarMonthSegment;
|
|
136
|
+
for (const item of segments) {
|
|
137
|
+
if (item.start > offsetInYear) break;
|
|
138
|
+
segment = item;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const month = segment.month;
|
|
142
|
+
const day = offsetInYear - segment.start + 1;
|
|
143
|
+
const isLeap = segment.isLeap;
|
|
144
|
+
|
|
145
|
+
const dayOfWeek = weekdayOf(sy, sm, sd);
|
|
146
|
+
const firstTermDay = getTerm(sy, sm * 2 - 1);
|
|
147
|
+
const secondTermDay = getTerm(sy, sm * 2);
|
|
148
|
+
|
|
149
|
+
let term: string | null = null;
|
|
150
|
+
if (firstTermDay === sd) term = getSolarTerm(sm * 2 - 2);
|
|
151
|
+
if (secondTermDay === sd) term = getSolarTerm(sm * 2 - 1);
|
|
152
|
+
|
|
153
|
+
// xiu / ban 只由 rest.json 决定,与周末无关;同一天不会既放假又补班,两种状态互斥
|
|
154
|
+
const restState = restOf(config.restIndex, sy, sm, sd);
|
|
155
|
+
const isXiu = restState === "xiu";
|
|
156
|
+
const isBan = restState === "ban";
|
|
157
|
+
|
|
158
|
+
const dayCyclical = utc / DAY_MS - (sd - 1) + 25567 + 10;
|
|
159
|
+
const gzDayOffset = dayCyclical + sd - 1;
|
|
160
|
+
// 只有 2 月才需要立春当天的日期,其它月份不去查节气表
|
|
161
|
+
const ganzhiYear = sm > 2 || (sm === 2 && sd >= getTerm(sy, 3)) ? sy : sy - 1;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
xiu: isXiu,
|
|
165
|
+
ban: isBan,
|
|
166
|
+
gljr: solarFestivals(sy, sm, sd, config.sFtv),
|
|
167
|
+
nljr: lunarFestivals(year, month, day, isLeap, config.lFtv),
|
|
168
|
+
lYear: year,
|
|
169
|
+
lMonth: month,
|
|
170
|
+
lDay: day,
|
|
171
|
+
animal: getAnimal(year),
|
|
172
|
+
lMonthCn: (isLeap ? "闰" : "") + toChinaMonth(month),
|
|
173
|
+
lDayCn: toChinaDay(day),
|
|
174
|
+
cYear: sy,
|
|
175
|
+
cMonth: sm,
|
|
176
|
+
cDay: sd,
|
|
177
|
+
gzYear: toGanZhiYear(ganzhiYear),
|
|
178
|
+
gzYearLunar: toGanZhiYear(year),
|
|
179
|
+
gzMonth: toGanZhi((sy - 1900) * 12 + sm + (sd >= firstTermDay ? 12 : 11)),
|
|
180
|
+
gzDay: toGanZhi(gzDayOffset),
|
|
181
|
+
gzHour: toHourGanZhi(gzDayOffset),
|
|
182
|
+
isToday: (function () {
|
|
183
|
+
const now = new Date();
|
|
184
|
+
return now.getFullYear() === sy && now.getMonth() + 1 === sm && now.getDate() === sd;
|
|
185
|
+
})(),
|
|
186
|
+
isLeap,
|
|
187
|
+
nWeek: dayOfWeek === 0 ? 7 : dayOfWeek,
|
|
188
|
+
dayOfWeek,
|
|
189
|
+
ncWeek: "星期" + toChinaNum(dayOfWeek),
|
|
190
|
+
term,
|
|
191
|
+
astro: toAstro(sm, sd),
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/** 公历转农历的内部实现,节假日配置由调用方固定 */
|
|
196
|
+
const solarToLunarWith = function (
|
|
197
|
+
config: ResolvedConfig,
|
|
198
|
+
y?: number,
|
|
199
|
+
m?: number,
|
|
200
|
+
d?: number,
|
|
201
|
+
): LunarResult {
|
|
202
|
+
if (y === undefined && m === undefined && d === undefined) {
|
|
203
|
+
const now = new Date();
|
|
204
|
+
const today = validateSolar(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
205
|
+
return computeLunar(config, today.year, today.month, today.day);
|
|
206
|
+
}
|
|
207
|
+
if (y === undefined || m === undefined || d === undefined) {
|
|
208
|
+
failRange(
|
|
209
|
+
"solarToLunar",
|
|
210
|
+
`年、月、日必须同时传入,或全部不传以取今天(收到 year=${String(y)}, month=${String(m)}, day=${String(d)})`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const date = validateSolar(y, m, d);
|
|
214
|
+
return computeLunar(config, date.year, date.month, date.day);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/** 农历转公历的内部实现,节假日配置由调用方固定 */
|
|
218
|
+
const lunarToSolarWith = function (
|
|
219
|
+
config: ResolvedConfig,
|
|
220
|
+
y: number,
|
|
221
|
+
m: number,
|
|
222
|
+
d: number,
|
|
223
|
+
isLeapMonth?: boolean,
|
|
224
|
+
): LunarResult {
|
|
225
|
+
const year = assertInteger("lunarToSolar(year)", y, MIN_YEAR, MAX_YEAR);
|
|
226
|
+
const month = assertInteger("lunarToSolar(month)", m, 1, 12);
|
|
227
|
+
if (isLeapMonth !== undefined && typeof isLeapMonth !== "boolean") {
|
|
228
|
+
throw failType("lunarToSolar(isLeapMonth)", `必须是布尔值,收到 ${describe(isLeapMonth)}`);
|
|
229
|
+
}
|
|
230
|
+
const leap = leapMonth(year);
|
|
231
|
+
const wantLeap = isLeapMonth === true;
|
|
232
|
+
if (wantLeap && leap !== month) {
|
|
233
|
+
failRange(
|
|
234
|
+
"lunarToSolar(isLeapMonth)",
|
|
235
|
+
`${year} 年${leap === 0 ? "没有闰月" : `的闰月是闰${leap}月`},不能按闰${month}月换算`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const maxDay = wantLeap ? leapDays(year) : lMonthDays(year, month);
|
|
239
|
+
const day = assertInteger("lunarToSolar(day)", d, 1, maxDay);
|
|
240
|
+
|
|
241
|
+
// 直接查表定位到该月第一天的绝对偏移,不再逐年逐月累加
|
|
242
|
+
const segments = monthSegmentsOf(year);
|
|
243
|
+
const segment = segments.find((item) => item.month === month && item.isLeap === wantLeap);
|
|
244
|
+
if (!segment) {
|
|
245
|
+
return failRange("lunarToSolar", `${year} 年${wantLeap ? "闰" : ""}${month}月不存在`);
|
|
246
|
+
}
|
|
247
|
+
const offset = yearStartOf(year) + segment.start;
|
|
248
|
+
|
|
249
|
+
const utc = MIN_SOLAR_UTC + (offset + day - 1) * DAY_MS;
|
|
250
|
+
if (utc > MAX_SOLAR_UTC) {
|
|
251
|
+
failRange("lunarToSolar", `农历 ${year}-${month}-${day} 换算后晚于 2100-12-31,超出支持范围`);
|
|
252
|
+
}
|
|
253
|
+
const date = new Date(utc);
|
|
254
|
+
return computeLunar(config, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 创建固定节假日配置的日历实例
|
|
259
|
+
*
|
|
260
|
+
* 不读取也不修改任何全局状态;配置在创建时校验并冻结,
|
|
261
|
+
* 多个实例之间互不影响。不传配置时使用内置数据。
|
|
262
|
+
*
|
|
263
|
+
* @param config 节假日 / 调休配置
|
|
264
|
+
* @param options replace 为 true 时忽略内置数据
|
|
265
|
+
* @throws TypeError 配置格式不合法(含具体字段路径)
|
|
266
|
+
* @example
|
|
267
|
+
* const cc = createCalendar({ setRest: { 2027: { xiu: ["0101"] } } });
|
|
268
|
+
* cc.solarToLunar(2027, 1, 1).xiu; // true
|
|
269
|
+
*/
|
|
270
|
+
const createCalendar = function (
|
|
271
|
+
config: HolidayConfig = {},
|
|
272
|
+
options: SetHolidayOptions = {},
|
|
273
|
+
): Calendar {
|
|
274
|
+
// 用户配置只校验一次;只有一份可查询的配置,own 只在 getHolidays 需要时生成快照
|
|
275
|
+
const { merged, snapshotOwn } = createCalendarConfig(config, options);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
/** 公历转农历,不传参返回今天 */
|
|
279
|
+
solarToLunar: (y, m, d) => solarToLunarWith(merged, y, m, d),
|
|
280
|
+
/** 农历转公历 */
|
|
281
|
+
lunarToSolar: (y, m, d, isLeapMonth) => lunarToSolarWith(merged, y, m, d, isLeapMonth),
|
|
282
|
+
/** 当前配置的深拷贝;merged 为 false 时只导出用户传入的部分 */
|
|
283
|
+
getHolidays: (getOptions) => {
|
|
284
|
+
if (getOptions !== undefined) assertPlainObject(getOptions, "getHolidays(options)");
|
|
285
|
+
if (getOptions?.merged !== undefined && typeof getOptions.merged !== "boolean") {
|
|
286
|
+
throw failType(
|
|
287
|
+
"getHolidays(options).merged",
|
|
288
|
+
`必须是布尔值,收到 ${describe(getOptions.merged)}`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return getOptions?.merged === false ? snapshotOwn() : snapshotOf(merged);
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const utils = {
|
|
297
|
+
lYearDays,
|
|
298
|
+
leapMonth,
|
|
299
|
+
leapDays,
|
|
300
|
+
lMonthDays,
|
|
301
|
+
toChinaMonth,
|
|
302
|
+
toChinaDay,
|
|
303
|
+
toChinaNum,
|
|
304
|
+
solarDays,
|
|
305
|
+
solarFirstWeek,
|
|
306
|
+
nthWeekdayOfMonth,
|
|
307
|
+
toAstro,
|
|
308
|
+
getAnimal,
|
|
309
|
+
getTerm,
|
|
310
|
+
getSolarTerm,
|
|
311
|
+
toGanZhi,
|
|
312
|
+
toGanZhiYear,
|
|
313
|
+
toHourGanZhi,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export { createCalendar, utils };
|
|
317
|
+
export default createCalendar;
|