@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/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanguo-dev/calendar",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "ts版本的日历生成器 LTS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"calendar",
|
|
7
|
+
"lunar",
|
|
8
|
+
"农历",
|
|
9
|
+
"日历",
|
|
10
|
+
"阳历"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/sanguo-dev/calendar#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sanguo-dev/calendar/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "sanguogege@outlook.com",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/sanguo-dev/calendar.git",
|
|
21
|
+
"directory": "packages/calendar"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.mjs",
|
|
31
|
+
"types": "./dist/index.d.cts",
|
|
32
|
+
"unpkg": "./dist/index.iife.js",
|
|
33
|
+
"jsdelivr": "./dist/index.iife.js",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"development": {
|
|
37
|
+
"types": "./src/index.ts",
|
|
38
|
+
"default": "./src/index.ts"
|
|
39
|
+
},
|
|
40
|
+
"import": {
|
|
41
|
+
"types": "./dist/index.d.mts",
|
|
42
|
+
"default": "./dist/index.mjs"
|
|
43
|
+
},
|
|
44
|
+
"require": {
|
|
45
|
+
"types": "./dist/index.d.cts",
|
|
46
|
+
"default": "./dist/index.cjs"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"registry": "https://registry.npmjs.org"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "vp pack",
|
|
57
|
+
"dev": "vp pack --watch",
|
|
58
|
+
"test": "vp test",
|
|
59
|
+
"check": "vp check",
|
|
60
|
+
"prepublishOnly": "vp run build"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "catalog:",
|
|
64
|
+
"typescript": "catalog:",
|
|
65
|
+
"vite": "catalog:",
|
|
66
|
+
"vite-plus": "catalog:"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import festivalJson from "./data/festival.json";
|
|
2
|
+
import restJson from "./data/rest.json";
|
|
3
|
+
import {
|
|
4
|
+
assertKnownKeys,
|
|
5
|
+
assertPlainObject,
|
|
6
|
+
describe,
|
|
7
|
+
failType,
|
|
8
|
+
normalizeFestivalMap,
|
|
9
|
+
normalizeRestMap,
|
|
10
|
+
} from "./utils/Assert";
|
|
11
|
+
import { freezeFestivalMap, mergeFestivalMap } from "./utils/SetFestival";
|
|
12
|
+
import { buildRestIndex, freezeRestMap, mergeRestMap } from "./utils/SetRest";
|
|
13
|
+
import type {
|
|
14
|
+
FestivalMap,
|
|
15
|
+
HolidayConfig,
|
|
16
|
+
HolidaySnapshot,
|
|
17
|
+
NormalizedRestEntry,
|
|
18
|
+
RestMap,
|
|
19
|
+
SetHolidayOptions,
|
|
20
|
+
} from "./types";
|
|
21
|
+
import type { RestIndex } from "./utils/SetRest";
|
|
22
|
+
/** 真正用于查询的、已经合并并预建索引的配置 */
|
|
23
|
+
export interface ResolvedConfig {
|
|
24
|
+
sFtv: FestivalMap;
|
|
25
|
+
lFtv: FestivalMap;
|
|
26
|
+
rest: RestMap;
|
|
27
|
+
restIndex: RestIndex;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 冻结并预建索引;rest 没变时复用调用方给的 restIndex,避免重复建 Set
|
|
32
|
+
*/
|
|
33
|
+
const createResolved = function (
|
|
34
|
+
sFtv: FestivalMap,
|
|
35
|
+
lFtv: FestivalMap,
|
|
36
|
+
rest: RestMap,
|
|
37
|
+
restIndex?: RestIndex,
|
|
38
|
+
): ResolvedConfig {
|
|
39
|
+
const frozenRest = freezeRestMap(rest);
|
|
40
|
+
return {
|
|
41
|
+
sFtv: freezeFestivalMap(sFtv),
|
|
42
|
+
lFtv: freezeFestivalMap(lFtv),
|
|
43
|
+
rest: frozenRest,
|
|
44
|
+
restIndex: restIndex ?? buildRestIndex(frozenRest),
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 内置数据在模块加载时就校验一遍,数据写错会立刻抛错而不是在运行时返回脏值
|
|
50
|
+
*/
|
|
51
|
+
const FESTIVAL_DATA = festivalJson as unknown as Record<string, unknown>;
|
|
52
|
+
|
|
53
|
+
const BASE = createResolved(
|
|
54
|
+
normalizeFestivalMap(FESTIVAL_DATA.sFtv, "data/festival.json.sFtv"),
|
|
55
|
+
normalizeFestivalMap(FESTIVAL_DATA.lFtv, "data/festival.json.lFtv", true),
|
|
56
|
+
normalizeRestMap(restJson, "data/rest.json"),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const EMPTY = createResolved({}, {}, {});
|
|
60
|
+
|
|
61
|
+
const FESTIVAL_FIELDS = ["sFtv", "lFtv"];
|
|
62
|
+
const HOLIDAY_FIELDS = ["setFestival", "setRest"];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 用户配置校验一次之后的片段:字段已归一化、去重,可以直接合并。
|
|
66
|
+
*
|
|
67
|
+
* replace 由 options 决定,单独记下来,后面合并时不再重复判断。
|
|
68
|
+
*/
|
|
69
|
+
interface HolidayPatch {
|
|
70
|
+
replace: boolean;
|
|
71
|
+
sFtv?: FestivalMap;
|
|
72
|
+
lFtv?: FestivalMap;
|
|
73
|
+
rest?: RestMap;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 校验并规范用户传入的配置,整份配置只在这里检查一次。
|
|
78
|
+
*
|
|
79
|
+
* 返回的片段是全新的数组 / 对象,不和入参共享引用;
|
|
80
|
+
* 合并、冻结、建索引这些后续步骤都直接信任这里的结果,不再重复校验。
|
|
81
|
+
*/
|
|
82
|
+
const normalizeHoliday = function (
|
|
83
|
+
config: HolidayConfig = {},
|
|
84
|
+
options: SetHolidayOptions = {},
|
|
85
|
+
path = "createCalendar",
|
|
86
|
+
): HolidayPatch {
|
|
87
|
+
assertPlainObject(config, path);
|
|
88
|
+
assertKnownKeys(config, HOLIDAY_FIELDS, path);
|
|
89
|
+
assertPlainObject(options, `${path}(options)`, "第二个参数必须是对象");
|
|
90
|
+
if (options.replace !== undefined && typeof options.replace !== "boolean") {
|
|
91
|
+
failType(`${path}(options).replace`, `必须是布尔值,收到 ${describe(options.replace)}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let sFtv: FestivalMap | undefined;
|
|
95
|
+
let lFtv: FestivalMap | undefined;
|
|
96
|
+
if (config.setFestival !== undefined) {
|
|
97
|
+
const festivalConfig = config.setFestival;
|
|
98
|
+
assertPlainObject(festivalConfig, `${path}.setFestival`);
|
|
99
|
+
assertKnownKeys(festivalConfig, FESTIVAL_FIELDS, `${path}.setFestival`);
|
|
100
|
+
if (festivalConfig.sFtv !== undefined) {
|
|
101
|
+
sFtv = normalizeFestivalMap(festivalConfig.sFtv, `${path}.setFestival.sFtv`);
|
|
102
|
+
}
|
|
103
|
+
if (festivalConfig.lFtv !== undefined) {
|
|
104
|
+
lFtv = normalizeFestivalMap(festivalConfig.lFtv, `${path}.setFestival.lFtv`, true);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
replace: options.replace === true,
|
|
110
|
+
sFtv,
|
|
111
|
+
lFtv,
|
|
112
|
+
rest:
|
|
113
|
+
config.setRest === undefined
|
|
114
|
+
? undefined
|
|
115
|
+
: normalizeRestMap(config.setRest, `${path}.setRest`),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 把补丁合并到 base 上;replace 为 true 时从空配置开始。
|
|
121
|
+
*
|
|
122
|
+
* 补丁来自 normalizeHoliday,已经是干净数据,这里只做合并、冻结和建索引。
|
|
123
|
+
*/
|
|
124
|
+
const applyHoliday = function (base: ResolvedConfig, patch: HolidayPatch): ResolvedConfig {
|
|
125
|
+
const from = patch.replace ? EMPTY : base;
|
|
126
|
+
if (patch.sFtv === undefined && patch.lFtv === undefined && patch.rest === undefined) {
|
|
127
|
+
// 没有用户数据:空补丁直接用 from,省掉一次冻结 + 建索引
|
|
128
|
+
return from;
|
|
129
|
+
}
|
|
130
|
+
return createResolved(
|
|
131
|
+
patch.sFtv === undefined ? from.sFtv : mergeFestivalMap(from.sFtv, patch.sFtv),
|
|
132
|
+
patch.lFtv === undefined ? from.lFtv : mergeFestivalMap(from.lFtv, patch.lFtv),
|
|
133
|
+
patch.rest === undefined ? from.rest : mergeRestMap(from.rest, patch.rest),
|
|
134
|
+
// 调休没变时直接沿用 from 的索引,省掉一次 buildRestIndex
|
|
135
|
+
patch.rest === undefined ? from.restIndex : undefined,
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** 复制一份节日表,返回值可以安全修改 */
|
|
140
|
+
const cloneFestivalMap = function (map: FestivalMap): FestivalMap {
|
|
141
|
+
const result: FestivalMap = {};
|
|
142
|
+
Object.keys(map).forEach(function (key) {
|
|
143
|
+
result[key] = map[key].slice();
|
|
144
|
+
});
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** 复制一份调休表,返回值可以安全修改 */
|
|
149
|
+
const cloneRestMap = function (rest: RestMap): Record<string, NormalizedRestEntry> {
|
|
150
|
+
const result: Record<string, NormalizedRestEntry> = {};
|
|
151
|
+
Object.keys(rest).forEach(function (year) {
|
|
152
|
+
result[year] = { xiu: rest[year].xiu.slice(), ban: rest[year].ban.slice() };
|
|
153
|
+
});
|
|
154
|
+
return result;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** 把内部配置转成可再次使用的快照 */
|
|
158
|
+
const snapshotOf = function (resolved: ResolvedConfig): HolidaySnapshot {
|
|
159
|
+
return {
|
|
160
|
+
setFestival: {
|
|
161
|
+
sFtv: cloneFestivalMap(resolved.sFtv),
|
|
162
|
+
lFtv: cloneFestivalMap(resolved.lFtv),
|
|
163
|
+
},
|
|
164
|
+
setRest: cloneRestMap(resolved.rest),
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** 只含用户传入部分的快照:own 不参与查询,不需要冻结 / 建索引 */
|
|
169
|
+
const snapshotOfPatch = function (patch: HolidayPatch): HolidaySnapshot {
|
|
170
|
+
return {
|
|
171
|
+
setFestival: {
|
|
172
|
+
sFtv: cloneFestivalMap(patch.sFtv ?? {}),
|
|
173
|
+
lFtv: cloneFestivalMap(patch.lFtv ?? {}),
|
|
174
|
+
},
|
|
175
|
+
setRest: cloneRestMap(patch.rest ?? {}),
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* createCalendar 用的配置。
|
|
181
|
+
*
|
|
182
|
+
* 只有一份可查询的 merged;getHolidays({ merged: false }) 要的"用户传入的那部分"
|
|
183
|
+
* 不参与查询,所以不另外建 ResolvedConfig,需要时直接从 patch 生成快照。
|
|
184
|
+
* replace 为 true 时 merged 本身就是用户那份,两条路径共用同一份配置。
|
|
185
|
+
*/
|
|
186
|
+
const createCalendarConfig = function (
|
|
187
|
+
config: HolidayConfig = {},
|
|
188
|
+
options: SetHolidayOptions = {},
|
|
189
|
+
): { merged: ResolvedConfig; snapshotOwn: () => HolidaySnapshot } {
|
|
190
|
+
const patch = normalizeHoliday(config, options);
|
|
191
|
+
const merged = applyHoliday(BASE, patch);
|
|
192
|
+
return {
|
|
193
|
+
merged,
|
|
194
|
+
snapshotOwn: patch.replace ? () => snapshotOf(merged) : () => snapshotOfPatch(patch),
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export { createCalendarConfig, snapshotOf };
|