dsh-metaphysics 0.18.4 → 0.20.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/CHANGELOG.md +22 -0
- package/README.md +16 -13
- package/client.js +754 -67
- package/engine/almanac.js +137 -3
- package/engine/bazi.js +2 -1
- package/engine/convert.js +137 -0
- package/engine/geju.js +67 -46
- package/engine/geo.js +1 -1
- package/engine/index.js +52 -5
- package/engine/interpret.js +1365 -1356
- package/engine/tiaohou.js +228 -0
- package/engine/zeri.js +29 -2
- package/index.js +15 -5
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
package/engine/almanac.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// 彭祖百忌、胎神等全量黄历数据未收录——输出里如实标注「未收录」,不臆造。
|
|
8
8
|
// 宜忌只按「建除十二神」通行简表给出(传统择日的第一层判据),不是逐日全量黄历。
|
|
9
9
|
import { solarToLunar } from './lunar.js';
|
|
10
|
+
import { nayinOf } from './nayin.js';
|
|
10
11
|
import {
|
|
11
12
|
toJDN, fromJDN, dayGanzhiIndex, ganzhiName, termWindow, termInstantJd, partsToJd, STEMS, BRANCHES, TERM_NAMES,
|
|
12
13
|
} from './calendar.js';
|
|
@@ -89,6 +90,89 @@ export function lunarMonthText(month, isLeap = false) {
|
|
|
89
90
|
return `${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月`;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/** 黄道/黑道十二神(青龙、明堂…勾陈),值日与时家共用。 */
|
|
94
|
+
export const TIAN_SHEN = ['青龙', '明堂', '天刑', '朱雀', '金匮', '天德', '白虎', '玉堂', '天牢', '玄武', '司命', '勾陈'];
|
|
95
|
+
/** 六黄道(吉)/ 六黑道(凶)。 */
|
|
96
|
+
export const TIAN_SHEN_TYPE = {
|
|
97
|
+
青龙: '黄道', 明堂: '黄道', 金匮: '黄道', 天德: '黄道', 玉堂: '黄道', 司命: '黄道',
|
|
98
|
+
天刑: '黑道', 朱雀: '黑道', 白虎: '黑道', 天牢: '黑道', 玄武: '黑道', 勾陈: '黑道',
|
|
99
|
+
};
|
|
100
|
+
/** 十二神起例:以月支(日家)或日支(时家)为锚的偏移。 */
|
|
101
|
+
const TIAN_SHEN_OFFSET = { 子: 4, 丑: 2, 寅: 0, 卯: 10, 辰: 8, 巳: 6, 午: 4, 未: 2, 申: 0, 酉: 10, 戌: 8, 亥: 6 };
|
|
102
|
+
|
|
103
|
+
/** 日家十二神:日支 + 月支偏移;时家十二神:时支 + 日支偏移。 */
|
|
104
|
+
export function tianShenOf(branch, anchorBranch) {
|
|
105
|
+
const b = BRANCHES.indexOf(branch);
|
|
106
|
+
const off = TIAN_SHEN_OFFSET[anchorBranch];
|
|
107
|
+
if (b < 0 || off == null) return null;
|
|
108
|
+
const name = TIAN_SHEN[(b + off) % 12];
|
|
109
|
+
const type = TIAN_SHEN_TYPE[name];
|
|
110
|
+
return { name, type, luck: type === '黄道' ? '吉' : '凶' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 二十八宿:值日宿按「日支 + 星期」轮转(四组各七宿)。 */
|
|
114
|
+
export const MANSIONS = {
|
|
115
|
+
角: { luck: '吉', zheng: '木', animal: '蛟', gong: '东' }, 亢: { luck: '凶', zheng: '金', animal: '龙', gong: '东' },
|
|
116
|
+
氐: { luck: '凶', zheng: '土', animal: '貉', gong: '东' }, 房: { luck: '吉', zheng: '日', animal: '兔', gong: '东' },
|
|
117
|
+
心: { luck: '凶', zheng: '月', animal: '狐', gong: '东' }, 尾: { luck: '吉', zheng: '火', animal: '虎', gong: '东' },
|
|
118
|
+
箕: { luck: '吉', zheng: '水', animal: '豹', gong: '东' }, 斗: { luck: '吉', zheng: '木', animal: '獬', gong: '北' },
|
|
119
|
+
牛: { luck: '凶', zheng: '金', animal: '牛', gong: '北' }, 女: { luck: '凶', zheng: '土', animal: '蝠', gong: '北' },
|
|
120
|
+
虚: { luck: '凶', zheng: '日', animal: '鼠', gong: '北' }, 危: { luck: '凶', zheng: '月', animal: '燕', gong: '北' },
|
|
121
|
+
室: { luck: '吉', zheng: '火', animal: '猪', gong: '北' }, 壁: { luck: '吉', zheng: '水', animal: '獝', gong: '北' },
|
|
122
|
+
奎: { luck: '凶', zheng: '木', animal: '狼', gong: '西' }, 娄: { luck: '吉', zheng: '金', animal: '狗', gong: '西' },
|
|
123
|
+
胃: { luck: '吉', zheng: '土', animal: '彘', gong: '西' }, 昴: { luck: '凶', zheng: '日', animal: '鸡', gong: '西' },
|
|
124
|
+
毕: { luck: '吉', zheng: '月', animal: '乌', gong: '西' }, 觜: { luck: '凶', zheng: '火', animal: '猴', gong: '西' },
|
|
125
|
+
参: { luck: '吉', zheng: '水', animal: '猿', gong: '西' }, 井: { luck: '吉', zheng: '木', animal: '犴', gong: '南' },
|
|
126
|
+
鬼: { luck: '凶', zheng: '金', animal: '羊', gong: '南' }, 柳: { luck: '凶', zheng: '土', animal: '獐', gong: '南' },
|
|
127
|
+
星: { luck: '凶', zheng: '日', animal: '马', gong: '南' }, 张: { luck: '吉', zheng: '月', animal: '鹿', gong: '南' },
|
|
128
|
+
翼: { luck: '凶', zheng: '火', animal: '蛇', gong: '南' }, 轸: { luck: '吉', zheng: '水', animal: '蚓', gong: '南' },
|
|
129
|
+
};
|
|
130
|
+
const XIU_GROUPS = {
|
|
131
|
+
A: ['虚', '毕', '翼', '箕', '奎', '鬼', '氐'],
|
|
132
|
+
B: ['房', '危', '觜', '轸', '斗', '娄', '柳'],
|
|
133
|
+
C: ['星', '心', '室', '参', '角', '牛', '胃'],
|
|
134
|
+
D: ['昴', '张', '尾', '壁', '井', '亢', '女'],
|
|
135
|
+
};
|
|
136
|
+
const XIU_GROUP_OF = { 子: 'A', 辰: 'A', 申: 'A', 丑: 'B', 巳: 'B', 酉: 'B', 寅: 'C', 午: 'C', 戌: 'C', 卯: 'D', 未: 'D', 亥: 'D' };
|
|
137
|
+
|
|
138
|
+
/** 值日宿:weekday 0=周日。 */
|
|
139
|
+
export function xiuOf(dayBranch, weekdayIndex) {
|
|
140
|
+
const g = XIU_GROUP_OF[dayBranch];
|
|
141
|
+
if (!g) return null;
|
|
142
|
+
const name = XIU_GROUPS[g][weekdayIndex];
|
|
143
|
+
const info = MANSIONS[name];
|
|
144
|
+
return info ? { name, ...info } : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 吉神方位(按日干;福神取通行一变,另一变注明差异)。 */
|
|
148
|
+
const DIR_OF = { 坎: '正北', 艮: '东北', 震: '正东', 巽: '东南', 离: '正南', 坤: '西南', 兑: '正西', 乾: '西北' };
|
|
149
|
+
const POS_XI = { 甲: '艮', 乙: '乾', 丙: '坤', 丁: '离', 戊: '巽', 己: '艮', 庚: '乾', 辛: '坤', 壬: '离', 癸: '巽' };
|
|
150
|
+
const POS_CAI = { 甲: '艮', 乙: '艮', 丙: '坤', 丁: '坤', 戊: '坎', 己: '坎', 庚: '震', 辛: '震', 壬: '离', 癸: '离' };
|
|
151
|
+
const POS_FU = { 甲: '坎', 乙: '坤', 丙: '乾', 丁: '巽', 戊: '艮', 己: '坎', 庚: '坤', 辛: '乾', 壬: '巽', 癸: '艮' };
|
|
152
|
+
const POS_YANG_GUI = { 甲: '坤', 乙: '坤', 丙: '兑', 丁: '乾', 戊: '艮', 己: '坎', 庚: '离', 辛: '艮', 壬: '震', 癸: '巽' };
|
|
153
|
+
const POS_YIN_GUI = { 甲: '艮', 乙: '坎', 丙: '乾', 丁: '兑', 戊: '坤', 己: '坤', 庚: '艮', 辛: '离', 壬: '巽', 癸: '震' };
|
|
154
|
+
const posOf = (map, stem) => (map[stem] ? { gua: map[stem], dir: DIR_OF[map[stem]] } : null);
|
|
155
|
+
|
|
156
|
+
/** 胎神(逐日干支,通行表)。 */
|
|
157
|
+
const TAI_SHEN = {
|
|
158
|
+
甲子: '占门碓 外东南', 乙丑: '碓磨厕 外东南', 丙寅: '厨灶炉 外正南', 丁卯: '仓库门 外正南', 戊辰: '房床栖 外正南',
|
|
159
|
+
己巳: '占门床 外正南', 庚午: '占碓磨 外正南', 辛未: '厨灶厕 外西南', 壬申: '仓库炉 外西南', 癸酉: '房床门 外西南',
|
|
160
|
+
甲戌: '占门栖 外西南', 乙亥: '碓磨床 外西南', 丙子: '厨灶碓 外西南', 丁丑: '仓库厕 外正西', 戊寅: '房床炉 外正西',
|
|
161
|
+
己卯: '占大门 外正西', 庚辰: '碓磨栖 外正西', 辛巳: '厨灶床 外正西', 壬午: '仓库碓 外西北', 癸未: '房床厕 外西北',
|
|
162
|
+
甲申: '占门炉 外西北', 乙酉: '碓磨门 外西北', 丙戌: '厨灶栖 外西北', 丁亥: '仓库床 外西北', 戊子: '房床碓 外正北',
|
|
163
|
+
己丑: '占门厕 外正北', 庚寅: '碓磨炉 外正北', 辛卯: '厨灶门 外正北', 壬辰: '仓库栖 外正北', 癸巳: '占房床 房内北',
|
|
164
|
+
甲午: '占门碓 房内北', 乙未: '碓磨厕 房内北', 丙申: '厨灶炉 房内北', 丁酉: '仓库门 房内北', 戊戌: '房床栖 房内中',
|
|
165
|
+
己亥: '占门床 房内中', 庚子: '占碓磨 房内南', 辛丑: '厨灶厕 房内南', 壬寅: '仓库炉 房内南', 癸卯: '房床门 房内西',
|
|
166
|
+
甲辰: '占门栖 房内东', 乙巳: '碓磨床 房内东', 丙午: '厨灶碓 房内东', 丁未: '仓库厕 房内东', 戊申: '房床炉 房内中',
|
|
167
|
+
己酉: '占大门 外东北', 庚戌: '碓磨栖 外东北', 辛亥: '厨灶床 外东北', 壬子: '仓库碓 外东北', 癸丑: '房床厕 外东北',
|
|
168
|
+
甲寅: '占门炉 外东北', 乙卯: '碓磨门 外正东', 丙辰: '厨灶栖 外正东', 丁巳: '仓库床 外正东', 戊午: '房床碓 外正东',
|
|
169
|
+
己未: '占门厕 外正东', 庚申: '碓磨炉 外东南', 辛酉: '厨灶门 外东南', 壬戌: '仓库栖 外东南', 癸亥: '占房床 外东南',
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** 六合 / 三合(对日支取吉配生肖)。 */
|
|
173
|
+
const LIUHE = { 子: '丑', 丑: '子', 寅: '亥', 亥: '寅', 卯: '戌', 戌: '卯', 辰: '酉', 酉: '辰', 巳: '申', 申: '巳', 午: '未', 未: '午' };
|
|
174
|
+
const SANHE = { 申: '申子辰', 子: '申子辰', 辰: '申子辰', 亥: '亥卯未', 卯: '亥卯未', 未: '亥卯未', 寅: '寅午戌', 午: '寅午戌', 戌: '寅午戌', 巳: '巳酉丑', 酉: '巳酉丑', 丑: '巳酉丑' };
|
|
175
|
+
|
|
92
176
|
/** 每日一卦:日期哈希确定性映射到通行本 64 卦(同日同卦)。 */
|
|
93
177
|
export function dailyHexagram(year, month, day) {
|
|
94
178
|
const names = Object.keys(HEX64);
|
|
@@ -153,7 +237,42 @@ export function buildAlmanac(input) {
|
|
|
153
237
|
return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
|
|
154
238
|
};
|
|
155
239
|
|
|
156
|
-
const
|
|
240
|
+
const weekdayIdx = (jdn + 1) % 7;
|
|
241
|
+
const weekday = ['日', '一', '二', '三', '四', '五', '六'][weekdayIdx];
|
|
242
|
+
// 值日十二神(黄道/黑道)、二十八宿、胎神、纳音、吉神方位、时辰吉凶
|
|
243
|
+
const dayGod = tianShenOf(dayBranch, BRANCHES[monthBranchIdx]);
|
|
244
|
+
const xiu = xiuOf(dayBranch, weekdayIdx);
|
|
245
|
+
const taiShen = TAI_SHEN[dayGz.name] || null;
|
|
246
|
+
const hourBase = (STEMS.indexOf(dayGz.stem) % 5) * 2;
|
|
247
|
+
const hours = BRANCHES.map((zhi, i) => {
|
|
248
|
+
const gan = STEMS[(hourBase + i) % 10];
|
|
249
|
+
const gz = ganzhiName(hourComposite((hourBase + i) % 10, i));
|
|
250
|
+
const shen = tianShenOf(zhi, dayBranch);
|
|
251
|
+
return {
|
|
252
|
+
zhi,
|
|
253
|
+
name: `${zhi}时`,
|
|
254
|
+
ganZhi: gz.name,
|
|
255
|
+
gan,
|
|
256
|
+
shen: shen ? shen.name : null,
|
|
257
|
+
type: shen ? shen.type : null,
|
|
258
|
+
luck: shen ? shen.luck : null,
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
const sanheGroup = SANHE[dayBranch] || '';
|
|
262
|
+
const liuheBranch = LIUHE[dayBranch] || null;
|
|
263
|
+
const lucky = {
|
|
264
|
+
liuhe: liuheBranch ? SHENGXIAO[BRANCHES.indexOf(liuheBranch)] : null,
|
|
265
|
+
sanhe: [...sanheGroup].filter((b) => b !== dayBranch).map((b) => SHENGXIAO[BRANCHES.indexOf(b)]),
|
|
266
|
+
};
|
|
267
|
+
const positions = {
|
|
268
|
+
caiShen: CAI_SHEN[dayGz.stem] || null,
|
|
269
|
+
xiShen: XI_SHEN[dayGz.stem] || null,
|
|
270
|
+
fuShen: posOf(POS_FU, dayGz.stem),
|
|
271
|
+
yangGui: posOf(POS_YANG_GUI, dayGz.stem),
|
|
272
|
+
yinGui: posOf(POS_YIN_GUI, dayGz.stem),
|
|
273
|
+
xiShenFull: posOf(POS_XI, dayGz.stem),
|
|
274
|
+
caiShenFull: posOf(POS_CAI, dayGz.stem),
|
|
275
|
+
};
|
|
157
276
|
const lunarText = `农历${springGz.name}年${lunarMonthText(lunar.month, lunar.isLeap)}${dayText(lunar.day)}`;
|
|
158
277
|
const boundaryNote = yearGz.name !== springGz.name
|
|
159
278
|
? `年界:本条按立春界作${yearGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(yearGz.branch)]}(命理排盘用);春节界为${springGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(springGz.branch)]},两种都对,取用场合不同`
|
|
@@ -165,7 +284,7 @@ export function buildAlmanac(input) {
|
|
|
165
284
|
date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
|
166
285
|
weekday: `星期${weekday}`,
|
|
167
286
|
...(boundaryNote ? { boundaryNote } : {}),
|
|
168
|
-
scope: '
|
|
287
|
+
scope: '通行口径:宜忌按建除十二神简表;已收录值日十二神(黄道/黑道)、时辰吉凶、二十八宿、胎神、吉神方位、纳音、六合三合吉配;逐日全量吉神凶煞与彭祖百忌未收录',
|
|
169
288
|
},
|
|
170
289
|
lunar: {
|
|
171
290
|
year: springYear,
|
|
@@ -186,7 +305,13 @@ export function buildAlmanac(input) {
|
|
|
186
305
|
prev: prevTerm ? { name: termName(prevTerm), date: fmtJdDate(prevTerm.jd) } : null,
|
|
187
306
|
next: nextTerm ? { name: termName(nextTerm), date: fmtJdDate(nextTerm.jd) } : null,
|
|
188
307
|
},
|
|
189
|
-
positions
|
|
308
|
+
positions,
|
|
309
|
+
dayGod,
|
|
310
|
+
xiu,
|
|
311
|
+
taiShen,
|
|
312
|
+
nayin: { day: nayinOf(dayGanzhiIndex(jdn)) },
|
|
313
|
+
lucky,
|
|
314
|
+
hours,
|
|
190
315
|
zhiXing,
|
|
191
316
|
yi: ZHIXING_YI[zhiXing].slice(),
|
|
192
317
|
ji: ZHIXING_JI[zhiXing].slice(),
|
|
@@ -199,6 +324,7 @@ export function buildAlmanac(input) {
|
|
|
199
324
|
sources: [
|
|
200
325
|
'农历与节气:本地天文历法(定朔 + 节气黄经)',
|
|
201
326
|
'建除十二神与冲煞:传统通行口径',
|
|
327
|
+
'值日/时家十二神(青龙…勾陈)、二十八宿、胎神、吉神方位:通行表',
|
|
202
328
|
'宜忌:建除十二神通行简表(非逐日全量黄历)',
|
|
203
329
|
'每日一卦:按公历日期确定性取卦(同日同卦)',
|
|
204
330
|
],
|
|
@@ -213,3 +339,11 @@ function monthComposite(stemIdx, branchIdx) {
|
|
|
213
339
|
}
|
|
214
340
|
return 0;
|
|
215
341
|
}
|
|
342
|
+
|
|
343
|
+
/** 由时干序与时支序求六十甲子序。 */
|
|
344
|
+
function hourComposite(stemIdx, branchIdx) {
|
|
345
|
+
for (let i = 0; i < 60; i++) {
|
|
346
|
+
if (i % 10 === stemIdx && i % 12 === branchIdx) return i;
|
|
347
|
+
}
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
package/engine/bazi.js
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// 公农历互转(本地确定性,不依赖外部历法库)
|
|
2
|
+
//
|
|
3
|
+
// 两种方向:
|
|
4
|
+
// · 公历 → 农历:直接查本地定朔历(engine/lunar.js),给出农历年月日、闰月标记、干支、生肖。
|
|
5
|
+
// · 农历 → 公历:按目标农历年的「月首」序列反查(每月 29/30 天,闰月单独一轮),
|
|
6
|
+
// 逐月定位后再加日偏移;不做「节气月」换算,农历就是朔望月口径。
|
|
7
|
+
//
|
|
8
|
+
// 诚实边界:只处理 1900–2100 的本地历法覆盖范围(与 lunar.js 一致);闰月必须显式给出,
|
|
9
|
+
// 不猜「第一个同名月」;农历日期越界(如小月三十)如实报错,不静默回退。
|
|
10
|
+
import { solarToLunar } from './lunar.js';
|
|
11
|
+
import { fromJDN, toJDN } from './calendar.js';
|
|
12
|
+
import { buildAlmanac } from './almanac.js';
|
|
13
|
+
import { assertCivil, daysInMonth } from './validate.js';
|
|
14
|
+
|
|
15
|
+
const WEEK = ['日', '一', '二', '三', '四', '五', '六'];
|
|
16
|
+
const MONTH_TEXT = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
|
|
17
|
+
const DAY_TENS = ['初', '十', '廿'];
|
|
18
|
+
const DAY_UNITS = ['十', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
|
19
|
+
|
|
20
|
+
function dayText(day) {
|
|
21
|
+
if (day === 10) return '初十';
|
|
22
|
+
if (day === 20) return '二十';
|
|
23
|
+
if (day === 30) return '三十';
|
|
24
|
+
return `${DAY_TENS[Math.floor(day / 10)]}${DAY_UNITS[day % 10]}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function lunarText(year, month, day, isLeap) {
|
|
28
|
+
return `农历${year}年${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月${dayText(day)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 找某个农历年的正月初一(公历 1/20–2/25 之间)。 */
|
|
32
|
+
function lunarNewYearJdn(lunarYear) {
|
|
33
|
+
const from = toJDN(lunarYear, 1, 20);
|
|
34
|
+
const to = toJDN(lunarYear, 2, 25);
|
|
35
|
+
for (let jdn = from; jdn <= to; jdn++) {
|
|
36
|
+
const p = fromJDN(jdn);
|
|
37
|
+
const l = solarToLunar(p.year, p.month, p.day);
|
|
38
|
+
if (l.lunarYear === lunarYear && l.month === 1 && l.day === 1) return jdn;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 某农历年的月首序列(含闰月)。 */
|
|
44
|
+
function lunarMonths(lunarYear) {
|
|
45
|
+
const first = lunarNewYearJdn(lunarYear);
|
|
46
|
+
if (first == null) return [];
|
|
47
|
+
const out = [];
|
|
48
|
+
let cur = first;
|
|
49
|
+
for (let i = 0; i < 14; i++) {
|
|
50
|
+
const p = fromJDN(cur);
|
|
51
|
+
const l = solarToLunar(p.year, p.month, p.day);
|
|
52
|
+
if (i > 0 && l.month === 1 && !l.isLeap) break;
|
|
53
|
+
let next = null;
|
|
54
|
+
for (let k = 29; k <= 31; k++) {
|
|
55
|
+
const d = fromJDN(cur + k);
|
|
56
|
+
const l2 = solarToLunar(d.year, d.month, d.day);
|
|
57
|
+
if (l2.day === 1) { next = cur + k; break; }
|
|
58
|
+
}
|
|
59
|
+
if (next == null) break;
|
|
60
|
+
out.push({ month: l.month, isLeap: l.isLeap, startJdn: cur, length: next - cur });
|
|
61
|
+
cur = next;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 公农历互转。
|
|
68
|
+
* @param {{mode?:'solar2lunar'|'lunar2solar', year?:number, month?:number, day?:number,
|
|
69
|
+
* lunarYear?:number, lunarMonth?:number, lunarDay?:number, isLeapMonth?:boolean}} input
|
|
70
|
+
*/
|
|
71
|
+
export function buildConvert(input) {
|
|
72
|
+
const mode = input.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
|
|
73
|
+
let solar;
|
|
74
|
+
let lunar;
|
|
75
|
+
let note = null;
|
|
76
|
+
if (mode === 'solar2lunar') {
|
|
77
|
+
solar = { year: input.year, month: input.month, day: input.day };
|
|
78
|
+
assertCivil(solar, '公农历转换');
|
|
79
|
+
if (solar.day > daysInMonth(solar.year, solar.month)) {
|
|
80
|
+
throw new Error(`非法日期:${solar.year} 年 ${solar.month} 月没有 ${solar.day} 日`);
|
|
81
|
+
}
|
|
82
|
+
lunar = solarToLunar(solar.year, solar.month, solar.day);
|
|
83
|
+
} else {
|
|
84
|
+
const y = Number(input.lunarYear);
|
|
85
|
+
const m = Number(input.lunarMonth);
|
|
86
|
+
const d = Number(input.lunarDay);
|
|
87
|
+
if (![y, m, d].every(Number.isInteger)) throw new Error('农历转换需要 lunarYear / lunarMonth / lunarDay');
|
|
88
|
+
if (m < 1 || m > 12) throw new Error('农历月份须在 1–12');
|
|
89
|
+
const months = lunarMonths(y);
|
|
90
|
+
if (!months.length) throw new Error(`农历 ${y} 年超出本地历法覆盖范围(1900–2100)`);
|
|
91
|
+
const isLeap = input.isLeapMonth === true;
|
|
92
|
+
const hit = months.find((x) => x.month === m && x.isLeap === isLeap);
|
|
93
|
+
if (!hit) {
|
|
94
|
+
const leapMonths = months.filter((x) => x.isLeap).map((x) => `闰${MONTH_TEXT[x.month - 1]}月`);
|
|
95
|
+
throw new Error(`农历 ${y} 年没有${leapMonths.length ? `该月(该年闰月为 ${leapMonths.join('、')})` : '闰月'}:${isLeap ? '请确认闰月标记' : '请确认月份'}`);
|
|
96
|
+
}
|
|
97
|
+
if (d < 1 || d > hit.length) throw new Error(`农历 ${y} 年${isLeap ? '闰' : ''}${MONTH_TEXT[m - 1]}月只有 ${hit.length} 天,没有 ${d} 日`);
|
|
98
|
+
const p = fromJDN(hit.startJdn + d - 1);
|
|
99
|
+
solar = { year: p.year, month: p.month, day: p.day };
|
|
100
|
+
const check = solarToLunar(p.year, p.month, p.day);
|
|
101
|
+
if (check.month !== m || check.day !== d || check.isLeap !== isLeap) {
|
|
102
|
+
note = '反查结果与目标农历日不完全一致,已按本地历法实际月首回推';
|
|
103
|
+
}
|
|
104
|
+
lunar = check;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const a = buildAlmanac(solar);
|
|
108
|
+
const jdn = toJDN(solar.year, solar.month, solar.day);
|
|
109
|
+
const solarText = `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`;
|
|
110
|
+
return {
|
|
111
|
+
system: 'convert',
|
|
112
|
+
meta: {
|
|
113
|
+
mode,
|
|
114
|
+
solarDate: solarText,
|
|
115
|
+
weekday: `星期${WEEK[(jdn + 1) % 7]}`,
|
|
116
|
+
...(note ? { note } : {}),
|
|
117
|
+
scope: '公农历互转按本地定朔历(1900–2100);农历闰月须显式指定,不自动猜同名月',
|
|
118
|
+
},
|
|
119
|
+
solar: { year: solar.year, month: solar.month, day: solar.day, text: solarText },
|
|
120
|
+
lunar: {
|
|
121
|
+
year: lunar.lunarYear,
|
|
122
|
+
month: lunar.month,
|
|
123
|
+
day: lunar.day,
|
|
124
|
+
isLeap: lunar.isLeap,
|
|
125
|
+
text: lunarText(lunar.lunarYear, lunar.month, lunar.day, lunar.isLeap),
|
|
126
|
+
},
|
|
127
|
+
ganzhi: {
|
|
128
|
+
year: a.ganzhi.year,
|
|
129
|
+
month: a.ganzhi.month,
|
|
130
|
+
day: a.ganzhi.day,
|
|
131
|
+
shengXiao: a.ganzhi.shengXiao,
|
|
132
|
+
springYear: a.ganzhi.springYear,
|
|
133
|
+
},
|
|
134
|
+
sources: ['农历:本地定朔 + 节气黄经历法(engine/lunar.js)', '干支与生肖:与黄历同口径(年按立春界,另列春节界)'],
|
|
135
|
+
disclaimer: '历法换算为确定性计算;民俗历法各版本在极少数日期(交节当日、闰月边界)存在日界差异,已注明口径。',
|
|
136
|
+
};
|
|
137
|
+
}
|
package/engine/geju.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 定位:在 bazi.js 算出四柱、五行、旺衰之后,给出
|
|
4
4
|
// 1) 格局(月令格 + 特殊格 + 常见组合);
|
|
5
|
-
// 2) 用神 /
|
|
6
|
-
// 3)
|
|
5
|
+
// 2) 用神 / 喜忌(调候优先 + 扶抑校验;气候平和时仍以扶抑为主);
|
|
6
|
+
// 3) 大运、流年的吉凶倾向评分(含调候修正项)。
|
|
7
7
|
// 取舍:格局派别差异极大,这里只给「规则可复核」的判定,evidence 写明依据;
|
|
8
|
-
//
|
|
8
|
+
// 调候由 tiaohou.js 独立判定,与扶抑冲突时显式裁决并写明理由,不静默取用。
|
|
9
|
+
|
|
10
|
+
import { judgeClimate, arbitrate } from './tiaohou.js';
|
|
9
11
|
|
|
10
12
|
const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
11
13
|
const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
@@ -197,38 +199,37 @@ function specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods)
|
|
|
197
199
|
|
|
198
200
|
// ── 用神 / 喜忌 ──────────────────────────────────────────────
|
|
199
201
|
|
|
200
|
-
const TIAOHOU = {
|
|
201
|
-
亥: ['火', '冬月寒水当令,最喜火暖局'],
|
|
202
|
-
子: ['火', '仲冬水旺,最喜火暖局'],
|
|
203
|
-
丑: ['火', '季冬湿寒,喜火解冻'],
|
|
204
|
-
巳: ['水', '初夏火旺,喜水制衡'],
|
|
205
|
-
午: ['水', '仲夏火炎,最喜水调候'],
|
|
206
|
-
未: ['水', '季夏燥土,喜水润局'],
|
|
207
|
-
寅: ['火', '初春尚寒,得火而发'],
|
|
208
|
-
卯: ['火', '仲春木旺,喜火吐秀'],
|
|
209
|
-
辰: ['金', '季春湿土,喜金水疏泄'],
|
|
210
|
-
申: ['火', '初秋金旺,喜火炼金'],
|
|
211
|
-
酉: ['火', '仲秋金锐,喜火制'],
|
|
212
|
-
戌: ['水', '季秋燥土,喜水润泽'],
|
|
213
|
-
};
|
|
214
202
|
|
|
215
|
-
function yongshenOf(dayStem, monthBranch, strength, wuxing) {
|
|
203
|
+
function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
|
|
216
204
|
const roles = elementRoles(dayStem);
|
|
217
205
|
const me = roles.me;
|
|
218
206
|
const weak = strength.level === '身弱' || strength.level === '偏弱';
|
|
219
207
|
const strong = strength.level === '身强' || strength.level === '偏强';
|
|
220
208
|
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
209
|
+
const baseFavor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
|
|
210
|
+
const baseAvoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
|
|
211
|
+
let favor = baseFavor.slice();
|
|
212
|
+
let avoid = baseAvoid.slice();
|
|
213
|
+
|
|
214
|
+
// 先判气候,再决定要不要让调候压过扶抑
|
|
215
|
+
const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
|
|
216
|
+
const arb = arbitrate(climate, baseFavor, baseAvoid);
|
|
217
|
+
if (climate.need && climate.priority === '调候优先') {
|
|
218
|
+
const needEl = climate.need.element;
|
|
219
|
+
if (!favor.includes(needEl)) favor.unshift(needEl);
|
|
220
|
+
avoid = avoid.filter((e) => e !== needEl);
|
|
221
|
+
if (climate.avoidElement && !avoid.includes(climate.avoidElement)) avoid.unshift(climate.avoidElement);
|
|
222
|
+
}
|
|
229
223
|
|
|
230
224
|
const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
|
|
231
225
|
: el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
|
|
226
|
+
const expand = {
|
|
227
|
+
比劫: ['比肩', '劫财'], 印: ['正印', '偏印'], 食伤: ['食神', '伤官'],
|
|
228
|
+
财: ['正财', '偏财'], 官杀: ['正官', '七杀'],
|
|
229
|
+
};
|
|
230
|
+
const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
|
|
231
|
+
const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
|
|
232
|
+
|
|
232
233
|
const detail = [
|
|
233
234
|
`日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
|
|
234
235
|
weak
|
|
@@ -236,24 +237,22 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing) {
|
|
|
236
237
|
: strong
|
|
237
238
|
? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
|
|
238
239
|
: '身中和,先取调候与通关,喜忌随大运流年动态变化。',
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
`调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
|
|
241
|
+
arb.text,
|
|
242
|
+
arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
|
|
241
243
|
].filter(Boolean).join('');
|
|
242
244
|
|
|
243
245
|
return {
|
|
244
|
-
method: '扶抑为主 +
|
|
246
|
+
method: climate.priority === '调候优先' ? '调候优先 + 扶抑校验' : '扶抑为主 + 调候校验',
|
|
245
247
|
level: strength.level,
|
|
246
248
|
favor,
|
|
247
249
|
avoid,
|
|
248
250
|
favorRoles: favor.map(roleName),
|
|
249
251
|
avoidRoles: avoid.map(roleName),
|
|
250
|
-
favorTenGods
|
|
251
|
-
avoidTenGods
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
? ['比肩', '劫财', '正印', '偏印']
|
|
255
|
-
: ['正官', '七杀', '正财', '偏财'],
|
|
256
|
-
tiaohou,
|
|
252
|
+
favorTenGods,
|
|
253
|
+
avoidTenGods,
|
|
254
|
+
tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
|
|
255
|
+
climate,
|
|
257
256
|
detail,
|
|
258
257
|
};
|
|
259
258
|
}
|
|
@@ -274,33 +273,55 @@ export function scoreLuck(ganzhi, frame) {
|
|
|
274
273
|
const branch = ganzhi[1];
|
|
275
274
|
const stemEl = STEM_ELEMENT[stem];
|
|
276
275
|
const branchEl = BRANCH_ELEMENT[branch];
|
|
276
|
+
const th = ys.tiaohou || ys.climate || null;
|
|
277
277
|
const scoreEl = (el) => {
|
|
278
278
|
let s = 0;
|
|
279
279
|
if (ys.favor.includes(el)) s += 1;
|
|
280
280
|
if (ys.avoid.includes(el)) s -= 1;
|
|
281
|
-
if (ys.tiaohou && el === ys.tiaohou.element) s += 0.4;
|
|
282
281
|
return s;
|
|
283
282
|
};
|
|
284
283
|
let score = scoreEl(stemEl) * 1.0 + scoreEl(branchEl) * 0.8;
|
|
285
284
|
for (const h of (HIDDEN[branch] || []).slice(1)) {
|
|
286
285
|
score += scoreEl(STEM_ELEMENT[h]) * 0.2;
|
|
287
286
|
}
|
|
288
|
-
score = Math.round(score * 10) / 10;
|
|
289
|
-
|
|
290
|
-
const godStem = tenGod(frame.dayStem, stem);
|
|
291
|
-
const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
|
|
292
|
-
const total = Math.round((score + bonus) * 10) / 10;
|
|
293
287
|
|
|
294
|
-
const tier = TIERS.find(([min]) => total >= min)[1];
|
|
295
288
|
const reasons = [];
|
|
296
289
|
if (ys.favor.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为用神`);
|
|
297
290
|
else if (ys.avoid.includes(stemEl)) reasons.push(`天干${stem}${stemEl}为忌神`);
|
|
298
291
|
else reasons.push(`天干${stem}${stemEl}不喜不忌`);
|
|
299
292
|
if (ys.favor.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助用`);
|
|
300
293
|
else if (ys.avoid.includes(branchEl)) reasons.push(`地支${branch}${branchEl}助忌`);
|
|
301
|
-
|
|
302
|
-
|
|
294
|
+
|
|
295
|
+
// 调候修正:不是"有没有这个五行",而是"这一年是给命局添了一把火,还是泼了一盆水"
|
|
296
|
+
if (th && th.need && th.priority === '调候优先') {
|
|
297
|
+
const needEl = th.need.element;
|
|
298
|
+
const oppEl = th.avoidElement;
|
|
299
|
+
const cold = String(th.kind).includes('寒');
|
|
300
|
+
if (stemEl === needEl) {
|
|
301
|
+
score += 1.1;
|
|
302
|
+
reasons.push(`${th.label}之局得${stem}${stemEl},${cold ? '解冻' : '得润'}`);
|
|
303
|
+
} else if (stemEl === oppEl) {
|
|
304
|
+
score -= 0.9;
|
|
305
|
+
reasons.push(`${th.label}之局再逢${stem}${stemEl},${cold ? '冻木难生' : '燥上加燥'}`);
|
|
306
|
+
}
|
|
307
|
+
if (branchEl === needEl) {
|
|
308
|
+
score += 0.9;
|
|
309
|
+
reasons.push(`地支${branch}${branchEl}合调候`);
|
|
310
|
+
} else if (branchEl === oppEl) {
|
|
311
|
+
score -= 1.0;
|
|
312
|
+
reasons.push(`地支${branch}${branchEl}加重${th.label}`);
|
|
313
|
+
}
|
|
314
|
+
if (cold && (stemEl === '水' || branchEl === '水')) score -= 0.3;
|
|
315
|
+
} else if (th && th.need && (stemEl === th.need.element || branchEl === th.need.element)) {
|
|
316
|
+
reasons.push(`合调候(${th.need.element})`);
|
|
303
317
|
}
|
|
318
|
+
|
|
319
|
+
const godStem = tenGod(frame.dayStem, stem);
|
|
320
|
+
const bonus = ys.favorTenGods.includes(godStem) ? 0.3 : 0;
|
|
321
|
+
let total = Math.round((score + bonus) * 10) / 10;
|
|
322
|
+
total = Math.max(-4, Math.min(4, total));
|
|
323
|
+
|
|
324
|
+
const tier = TIERS.find(([min]) => total >= min)[1];
|
|
304
325
|
return { ganzhi, score: total, tier, stemTenGod: godStem, reasons, note: `${godStem}运,${tier}(${total})` };
|
|
305
326
|
}
|
|
306
327
|
|
|
@@ -319,7 +340,7 @@ export function analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxi
|
|
|
319
340
|
const month = monthPattern(pillars, dayStem, monthBranch);
|
|
320
341
|
const special = specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods);
|
|
321
342
|
const geju = [month, ...special];
|
|
322
|
-
const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing);
|
|
343
|
+
const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing, Object.values(pillars).map((p) => p.branch));
|
|
323
344
|
return {
|
|
324
345
|
geju,
|
|
325
346
|
primary: month,
|
package/engine/geo.js
CHANGED
|
@@ -143,7 +143,7 @@ const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
|
143
143
|
|
|
144
144
|
/** OpenStreetMap Nominatim:免密钥、支持中文。失败返回 null。
|
|
145
145
|
* UA 按 OSM 使用政策要求写明应用与联系方式(改版本时同步这里)。 */
|
|
146
|
-
const USER_AGENT = 'dsh-metaphysics/0.
|
|
146
|
+
const USER_AGENT = 'dsh-metaphysics/0.20.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
147
147
|
|
|
148
148
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
149
149
|
if (typeof fetch !== 'function') return null;
|
package/engine/index.js
CHANGED
|
@@ -21,7 +21,8 @@ import { buildZeri } from './zeri.js';
|
|
|
21
21
|
import { buildChenggu } from './chenggu.js';
|
|
22
22
|
import { buildXiaoliuren } from './xiaoliuren.js';
|
|
23
23
|
import { buildStar } from './star.js';
|
|
24
|
-
import { buildNumerology } from './numerology.js';
|
|
24
|
+
import { buildNumerology } from './numerology.js';
|
|
25
|
+
import { buildConvert } from './convert.js';
|
|
25
26
|
import { buildTrend } from './trend.js';
|
|
26
27
|
import { buildFengshui } from './fengshui.js';
|
|
27
28
|
import { buildName } from './name.js';
|
|
@@ -109,6 +110,7 @@ function baziText(r) {
|
|
|
109
110
|
` ${r.frame.geju.map((g) => `${g.name}[${g.kind}]`).join(' ')}`,
|
|
110
111
|
`用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')}) ${r.frame.yongshen.method}`,
|
|
111
112
|
` ${r.frame.yongshen.detail}`,
|
|
113
|
+
`寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}` : ''}` : '—'}`,
|
|
112
114
|
`十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
|
|
113
115
|
] : [];
|
|
114
116
|
return [
|
|
@@ -617,6 +619,26 @@ function computeRaw(args = {}) {
|
|
|
617
619
|
const result = buildChenggu({ year: dt.year, month: dt.month, day: dt.day, hour, minute: hasTime ? dt.minute : 0 });
|
|
618
620
|
return { ...result, summary: chengguText(result) };
|
|
619
621
|
}
|
|
622
|
+
if (system === 'convert') {
|
|
623
|
+
const mode = args.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
|
|
624
|
+
let result;
|
|
625
|
+
if (mode === 'lunar2solar') {
|
|
626
|
+
if (args.lunarYear == null || args.lunarMonth == null || args.lunarDay == null) {
|
|
627
|
+
throw fail('E_INPUT', '农历转公历需要 lunarYear / lunarMonth / lunarDay(闰月另加 isLeapMonth: true)');
|
|
628
|
+
}
|
|
629
|
+
result = buildConvert({
|
|
630
|
+
mode,
|
|
631
|
+
lunarYear: Number(args.lunarYear),
|
|
632
|
+
lunarMonth: Number(args.lunarMonth),
|
|
633
|
+
lunarDay: Number(args.lunarDay),
|
|
634
|
+
isLeapMonth: args.isLeapMonth === true,
|
|
635
|
+
});
|
|
636
|
+
} else {
|
|
637
|
+
const dt = parseDateTime(args.datetime);
|
|
638
|
+
result = buildConvert({ mode, year: dt.year, month: dt.month, day: dt.day });
|
|
639
|
+
}
|
|
640
|
+
return { ...result, summary: convertText(result) };
|
|
641
|
+
}
|
|
620
642
|
if (system === 'xiaoliuren') {
|
|
621
643
|
const dt = args.datetime ? parseDateTime(args.datetime) : null;
|
|
622
644
|
const result = buildXiaoliuren({
|
|
@@ -700,22 +722,35 @@ function computeRaw(args = {}) {
|
|
|
700
722
|
return { ...result, summary: nameText(result) };
|
|
701
723
|
}
|
|
702
724
|
if (!system) {
|
|
703
|
-
throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name
|
|
725
|
+
throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
|
|
704
726
|
}
|
|
705
|
-
throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name
|
|
727
|
+
throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
|
|
706
728
|
}
|
|
707
729
|
|
|
708
730
|
/** 黄历文本 */
|
|
709
731
|
function almanacText(r) {
|
|
710
732
|
const g = r.ganzhi;
|
|
711
733
|
const jq = r.jieQi && r.jieQi.next ? ` 下一节:${r.jieQi.next.name} ${r.jieQi.next.date}` : '';
|
|
734
|
+
const god = r.dayGod ? `值日:${r.dayGod.name}(${r.dayGod.type}·${r.dayGod.luck})` : '';
|
|
735
|
+
const xiu = r.xiu ? `二十八宿:${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
|
|
736
|
+
const tai = r.taiShen ? `胎神:${r.taiShen}` : '';
|
|
737
|
+
const pos = r.positions
|
|
738
|
+
? `吉神方位:喜神${r.positions.xiShen || '—'} 财神${r.positions.caiShen || '—'} 福神${r.positions.fuShen ? r.positions.fuShen.dir : '—'} 阳贵${r.positions.yangGui ? r.positions.yangGui.dir : '—'} 阴贵${r.positions.yinGui ? r.positions.yinGui.dir : '—'}`
|
|
739
|
+
: '';
|
|
740
|
+
const lucky = r.lucky ? `吉配生肖:六合${r.lucky.liuhe || '—'} 三合${(r.lucky.sanhe || []).join('、') || '—'}` : '';
|
|
741
|
+
const nayin = r.nayin && r.nayin.day ? `日柱纳音:${r.nayin.day.name}` : '';
|
|
742
|
+
const hours = (r.hours || []).map((h) => `${h.zhi}${h.ganZhi.slice(0, 1)}·${h.shen}${h.luck === '吉' ? '吉' : '凶'}`).join(' ');
|
|
712
743
|
return [
|
|
713
744
|
`【黄历】${r.meta.date} ${r.meta.weekday} ${r.lunar.text}`,
|
|
714
745
|
`干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}`,
|
|
715
746
|
...(r.meta.boundaryNote ? [` ${r.meta.boundaryNote}`] : []),
|
|
716
|
-
`建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''}
|
|
747
|
+
`建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} ${god}`,
|
|
748
|
+
[xiu, nayin, tai].filter(Boolean).join(' '),
|
|
717
749
|
`宜:${r.yi.join('、')}`,
|
|
718
750
|
`忌:${r.ji.join('、')}`,
|
|
751
|
+
pos,
|
|
752
|
+
lucky,
|
|
753
|
+
hours ? `时辰吉凶:${hours}` : '',
|
|
719
754
|
`节气:上一节 ${r.jieQi.prev ? `${r.jieQi.prev.name} ${r.jieQi.prev.date}` : '—'}${jq}`,
|
|
720
755
|
r.dailyHexagram ? `每日一卦:${r.dailyHexagram.name} ${r.dailyHexagram.meaning || ''}` : '',
|
|
721
756
|
`口径:${r.meta.scope}`,
|
|
@@ -775,6 +810,18 @@ function starText(r) {
|
|
|
775
810
|
].join('\n');
|
|
776
811
|
}
|
|
777
812
|
|
|
813
|
+
/** 公农历互转文本 */
|
|
814
|
+
function convertText(r) {
|
|
815
|
+
const g = r.ganzhi;
|
|
816
|
+
const dir = r.meta.mode === 'lunar2solar' ? '农历 → 公历' : '公历 → 农历';
|
|
817
|
+
return [
|
|
818
|
+
`【公农历转换·${dir}】${r.lunar.text} ⇄ ${r.solar.text} ${r.meta.weekday}`,
|
|
819
|
+
`干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}${g.springYear && g.springYear !== g.year ? `(春节界为${g.springYear}年)` : ''}`,
|
|
820
|
+
...(r.meta.note ? [`注:${r.meta.note}`] : []),
|
|
821
|
+
`口径:${r.meta.scope}`,
|
|
822
|
+
].join('\n');
|
|
823
|
+
}
|
|
824
|
+
|
|
778
825
|
/** 生命数字文本 */
|
|
779
826
|
function numerologyText(r) {
|
|
780
827
|
return [
|
|
@@ -883,7 +930,7 @@ export { buildBazi, buildLiuyao, tossLines };
|
|
|
883
930
|
export { followup };
|
|
884
931
|
export { pastSignals, verifyFit, rectifyBirth };
|
|
885
932
|
export { crossCheck, crossText };
|
|
886
|
-
export { buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology };
|
|
933
|
+
export { buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert };
|
|
887
934
|
export { buildTrend, buildFengshui, buildName };
|
|
888
935
|
|
|
889
936
|
|