dsh-metaphysics 0.18.4 → 0.19.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/client.js CHANGED
@@ -746,7 +746,7 @@ function resolveAddress(input) {
746
746
 
747
747
  const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
748
748
 
749
- const USER_AGENT = 'dsh-metaphysics/0.18.4 (+https://github.com/xuanfu-web/dsh-metaphysics)';
749
+ const USER_AGENT = 'dsh-metaphysics/0.19.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
750
750
 
751
751
  async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
752
752
  if (typeof fetch !== 'function') return null;
@@ -5732,6 +5732,7 @@ return { TRIGRAM, YAO_POSITION, MOVING_NATURE, HEX64, HEX_BY_CORE, ZIWEI_STARS,
5732
5732
 
5733
5733
  __mods.almanac = (function () {
5734
5734
  const { solarToLunar } = __mods.lunar;
5735
+ const { nayinOf } = __mods.nayin;
5735
5736
  const { toJDN, fromJDN, dayGanzhiIndex, ganzhiName, termWindow, termInstantJd, partsToJd, STEMS, BRANCHES, TERM_NAMES } = __mods.calendar;
5736
5737
  const { HEX64 } = __mods['interpret-data'];
5737
5738
  const { assertCivil, daysInMonth } = __mods.validate;
@@ -5803,6 +5804,80 @@ function lunarMonthText(month, isLeap = false) {
5803
5804
  return `${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月`;
5804
5805
  }
5805
5806
 
5807
+ const TIAN_SHEN = ['青龙', '明堂', '天刑', '朱雀', '金匮', '天德', '白虎', '玉堂', '天牢', '玄武', '司命', '勾陈'];
5808
+ const TIAN_SHEN_TYPE = {
5809
+ 青龙: '黄道', 明堂: '黄道', 金匮: '黄道', 天德: '黄道', 玉堂: '黄道', 司命: '黄道',
5810
+ 天刑: '黑道', 朱雀: '黑道', 白虎: '黑道', 天牢: '黑道', 玄武: '黑道', 勾陈: '黑道',
5811
+ };
5812
+ const TIAN_SHEN_OFFSET = { 子: 4, 丑: 2, 寅: 0, 卯: 10, 辰: 8, 巳: 6, 午: 4, 未: 2, 申: 0, 酉: 10, 戌: 8, 亥: 6 };
5813
+
5814
+ function tianShenOf(branch, anchorBranch) {
5815
+ const b = BRANCHES.indexOf(branch);
5816
+ const off = TIAN_SHEN_OFFSET[anchorBranch];
5817
+ if (b < 0 || off == null) return null;
5818
+ const name = TIAN_SHEN[(b + off) % 12];
5819
+ const type = TIAN_SHEN_TYPE[name];
5820
+ return { name, type, luck: type === '黄道' ? '吉' : '凶' };
5821
+ }
5822
+
5823
+ const MANSIONS = {
5824
+ 角: { luck: '吉', zheng: '木', animal: '蛟', gong: '东' }, 亢: { luck: '凶', zheng: '金', animal: '龙', gong: '东' },
5825
+ 氐: { luck: '凶', zheng: '土', animal: '貉', gong: '东' }, 房: { luck: '吉', zheng: '日', animal: '兔', gong: '东' },
5826
+ 心: { luck: '凶', zheng: '月', animal: '狐', gong: '东' }, 尾: { luck: '吉', zheng: '火', animal: '虎', gong: '东' },
5827
+ 箕: { luck: '吉', zheng: '水', animal: '豹', gong: '东' }, 斗: { luck: '吉', zheng: '木', animal: '獬', gong: '北' },
5828
+ 牛: { luck: '凶', zheng: '金', animal: '牛', gong: '北' }, 女: { luck: '凶', zheng: '土', animal: '蝠', gong: '北' },
5829
+ 虚: { luck: '凶', zheng: '日', animal: '鼠', gong: '北' }, 危: { luck: '凶', zheng: '月', animal: '燕', gong: '北' },
5830
+ 室: { luck: '吉', zheng: '火', animal: '猪', gong: '北' }, 壁: { luck: '吉', zheng: '水', animal: '獝', gong: '北' },
5831
+ 奎: { luck: '凶', zheng: '木', animal: '狼', gong: '西' }, 娄: { luck: '吉', zheng: '金', animal: '狗', gong: '西' },
5832
+ 胃: { luck: '吉', zheng: '土', animal: '彘', gong: '西' }, 昴: { luck: '凶', zheng: '日', animal: '鸡', gong: '西' },
5833
+ 毕: { luck: '吉', zheng: '月', animal: '乌', gong: '西' }, 觜: { luck: '凶', zheng: '火', animal: '猴', gong: '西' },
5834
+ 参: { luck: '吉', zheng: '水', animal: '猿', gong: '西' }, 井: { luck: '吉', zheng: '木', animal: '犴', gong: '南' },
5835
+ 鬼: { luck: '凶', zheng: '金', animal: '羊', gong: '南' }, 柳: { luck: '凶', zheng: '土', animal: '獐', gong: '南' },
5836
+ 星: { luck: '凶', zheng: '日', animal: '马', gong: '南' }, 张: { luck: '吉', zheng: '月', animal: '鹿', gong: '南' },
5837
+ 翼: { luck: '凶', zheng: '火', animal: '蛇', gong: '南' }, 轸: { luck: '吉', zheng: '水', animal: '蚓', gong: '南' },
5838
+ };
5839
+ const XIU_GROUPS = {
5840
+ A: ['虚', '毕', '翼', '箕', '奎', '鬼', '氐'],
5841
+ B: ['房', '危', '觜', '轸', '斗', '娄', '柳'],
5842
+ C: ['星', '心', '室', '参', '角', '牛', '胃'],
5843
+ D: ['昴', '张', '尾', '壁', '井', '亢', '女'],
5844
+ };
5845
+ const XIU_GROUP_OF = { 子: 'A', 辰: 'A', 申: 'A', 丑: 'B', 巳: 'B', 酉: 'B', 寅: 'C', 午: 'C', 戌: 'C', 卯: 'D', 未: 'D', 亥: 'D' };
5846
+
5847
+ function xiuOf(dayBranch, weekdayIndex) {
5848
+ const g = XIU_GROUP_OF[dayBranch];
5849
+ if (!g) return null;
5850
+ const name = XIU_GROUPS[g][weekdayIndex];
5851
+ const info = MANSIONS[name];
5852
+ return info ? { name, ...info } : null;
5853
+ }
5854
+
5855
+ const DIR_OF = { 坎: '正北', 艮: '东北', 震: '正东', 巽: '东南', 离: '正南', 坤: '西南', 兑: '正西', 乾: '西北' };
5856
+ const POS_XI = { 甲: '艮', 乙: '乾', 丙: '坤', 丁: '离', 戊: '巽', 己: '艮', 庚: '乾', 辛: '坤', 壬: '离', 癸: '巽' };
5857
+ const POS_CAI = { 甲: '艮', 乙: '艮', 丙: '坤', 丁: '坤', 戊: '坎', 己: '坎', 庚: '震', 辛: '震', 壬: '离', 癸: '离' };
5858
+ const POS_FU = { 甲: '坎', 乙: '坤', 丙: '乾', 丁: '巽', 戊: '艮', 己: '坎', 庚: '坤', 辛: '乾', 壬: '巽', 癸: '艮' };
5859
+ const POS_YANG_GUI = { 甲: '坤', 乙: '坤', 丙: '兑', 丁: '乾', 戊: '艮', 己: '坎', 庚: '离', 辛: '艮', 壬: '震', 癸: '巽' };
5860
+ const POS_YIN_GUI = { 甲: '艮', 乙: '坎', 丙: '乾', 丁: '兑', 戊: '坤', 己: '坤', 庚: '艮', 辛: '离', 壬: '巽', 癸: '震' };
5861
+ const posOf = (map, stem) => (map[stem] ? { gua: map[stem], dir: DIR_OF[map[stem]] } : null);
5862
+
5863
+ const TAI_SHEN = {
5864
+ 甲子: '占门碓 外东南', 乙丑: '碓磨厕 外东南', 丙寅: '厨灶炉 外正南', 丁卯: '仓库门 外正南', 戊辰: '房床栖 外正南',
5865
+ 己巳: '占门床 外正南', 庚午: '占碓磨 外正南', 辛未: '厨灶厕 外西南', 壬申: '仓库炉 外西南', 癸酉: '房床门 外西南',
5866
+ 甲戌: '占门栖 外西南', 乙亥: '碓磨床 外西南', 丙子: '厨灶碓 外西南', 丁丑: '仓库厕 外正西', 戊寅: '房床炉 外正西',
5867
+ 己卯: '占大门 外正西', 庚辰: '碓磨栖 外正西', 辛巳: '厨灶床 外正西', 壬午: '仓库碓 外西北', 癸未: '房床厕 外西北',
5868
+ 甲申: '占门炉 外西北', 乙酉: '碓磨门 外西北', 丙戌: '厨灶栖 外西北', 丁亥: '仓库床 外西北', 戊子: '房床碓 外正北',
5869
+ 己丑: '占门厕 外正北', 庚寅: '碓磨炉 外正北', 辛卯: '厨灶门 外正北', 壬辰: '仓库栖 外正北', 癸巳: '占房床 房内北',
5870
+ 甲午: '占门碓 房内北', 乙未: '碓磨厕 房内北', 丙申: '厨灶炉 房内北', 丁酉: '仓库门 房内北', 戊戌: '房床栖 房内中',
5871
+ 己亥: '占门床 房内中', 庚子: '占碓磨 房内南', 辛丑: '厨灶厕 房内南', 壬寅: '仓库炉 房内南', 癸卯: '房床门 房内西',
5872
+ 甲辰: '占门栖 房内东', 乙巳: '碓磨床 房内东', 丙午: '厨灶碓 房内东', 丁未: '仓库厕 房内东', 戊申: '房床炉 房内中',
5873
+ 己酉: '占大门 外东北', 庚戌: '碓磨栖 外东北', 辛亥: '厨灶床 外东北', 壬子: '仓库碓 外东北', 癸丑: '房床厕 外东北',
5874
+ 甲寅: '占门炉 外东北', 乙卯: '碓磨门 外正东', 丙辰: '厨灶栖 外正东', 丁巳: '仓库床 外正东', 戊午: '房床碓 外正东',
5875
+ 己未: '占门厕 外正东', 庚申: '碓磨炉 外东南', 辛酉: '厨灶门 外东南', 壬戌: '仓库栖 外东南', 癸亥: '占房床 外东南',
5876
+ };
5877
+
5878
+ const LIUHE = { 子: '丑', 丑: '子', 寅: '亥', 亥: '寅', 卯: '戌', 戌: '卯', 辰: '酉', 酉: '辰', 巳: '申', 申: '巳', 午: '未', 未: '午' };
5879
+ const SANHE = { 申: '申子辰', 子: '申子辰', 辰: '申子辰', 亥: '亥卯未', 卯: '亥卯未', 未: '亥卯未', 寅: '寅午戌', 午: '寅午戌', 戌: '寅午戌', 巳: '巳酉丑', 酉: '巳酉丑', 丑: '巳酉丑' };
5880
+
5806
5881
  function dailyHexagram(year, month, day) {
5807
5882
  const names = Object.keys(HEX64);
5808
5883
  if (!names.length) return null;
@@ -5858,7 +5933,41 @@ function buildAlmanac(input) {
5858
5933
  return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
5859
5934
  };
5860
5935
 
5861
- const weekday = ['日', '一', '二', '三', '四', '五', '六'][(jdn + 1) % 7];
5936
+ const weekdayIdx = (jdn + 1) % 7;
5937
+ const weekday = ['日', '一', '二', '三', '四', '五', '六'][weekdayIdx];
5938
+ const dayGod = tianShenOf(dayBranch, BRANCHES[monthBranchIdx]);
5939
+ const xiu = xiuOf(dayBranch, weekdayIdx);
5940
+ const taiShen = TAI_SHEN[dayGz.name] || null;
5941
+ const hourBase = (STEMS.indexOf(dayGz.stem) % 5) * 2;
5942
+ const hours = BRANCHES.map((zhi, i) => {
5943
+ const gan = STEMS[(hourBase + i) % 10];
5944
+ const gz = ganzhiName(hourComposite((hourBase + i) % 10, i));
5945
+ const shen = tianShenOf(zhi, dayBranch);
5946
+ return {
5947
+ zhi,
5948
+ name: `${zhi}时`,
5949
+ ganZhi: gz.name,
5950
+ gan,
5951
+ shen: shen ? shen.name : null,
5952
+ type: shen ? shen.type : null,
5953
+ luck: shen ? shen.luck : null,
5954
+ };
5955
+ });
5956
+ const sanheGroup = SANHE[dayBranch] || '';
5957
+ const liuheBranch = LIUHE[dayBranch] || null;
5958
+ const lucky = {
5959
+ liuhe: liuheBranch ? SHENGXIAO[BRANCHES.indexOf(liuheBranch)] : null,
5960
+ sanhe: [...sanheGroup].filter((b) => b !== dayBranch).map((b) => SHENGXIAO[BRANCHES.indexOf(b)]),
5961
+ };
5962
+ const positions = {
5963
+ caiShen: CAI_SHEN[dayGz.stem] || null,
5964
+ xiShen: XI_SHEN[dayGz.stem] || null,
5965
+ fuShen: posOf(POS_FU, dayGz.stem),
5966
+ yangGui: posOf(POS_YANG_GUI, dayGz.stem),
5967
+ yinGui: posOf(POS_YIN_GUI, dayGz.stem),
5968
+ xiShenFull: posOf(POS_XI, dayGz.stem),
5969
+ caiShenFull: posOf(POS_CAI, dayGz.stem),
5970
+ };
5862
5971
  const lunarText = `农历${springGz.name}年${lunarMonthText(lunar.month, lunar.isLeap)}${dayText(lunar.day)}`;
5863
5972
  const boundaryNote = yearGz.name !== springGz.name
5864
5973
  ? `年界:本条按立春界作${yearGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(yearGz.branch)]}(命理排盘用);春节界为${springGz.name}年·属${SHENGXIAO[BRANCHES.indexOf(springGz.branch)]},两种都对,取用场合不同`
@@ -5870,7 +5979,7 @@ function buildAlmanac(input) {
5870
5979
  date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
5871
5980
  weekday: `星期${weekday}`,
5872
5981
  ...(boundaryNote ? { boundaryNote } : {}),
5873
- scope: '通行简表口径:宜忌只按建除十二神;逐日神煞/二十八宿/彭祖百忌/胎神等全量黄历数据未收录',
5982
+ scope: '通行口径:宜忌按建除十二神简表;已收录值日十二神(黄道/黑道)、时辰吉凶、二十八宿、胎神、吉神方位、纳音、六合三合吉配;逐日全量吉神凶煞与彭祖百忌未收录',
5874
5983
  },
5875
5984
  lunar: {
5876
5985
  year: springYear,
@@ -5891,7 +6000,13 @@ function buildAlmanac(input) {
5891
6000
  prev: prevTerm ? { name: termName(prevTerm), date: fmtJdDate(prevTerm.jd) } : null,
5892
6001
  next: nextTerm ? { name: termName(nextTerm), date: fmtJdDate(nextTerm.jd) } : null,
5893
6002
  },
5894
- positions: { caiShen: CAI_SHEN[dayGz.stem] || null, xiShen: XI_SHEN[dayGz.stem] || null },
6003
+ positions,
6004
+ dayGod,
6005
+ xiu,
6006
+ taiShen,
6007
+ nayin: { day: nayinOf(dayGanzhiIndex(jdn)) },
6008
+ lucky,
6009
+ hours,
5895
6010
  zhiXing,
5896
6011
  yi: ZHIXING_YI[zhiXing].slice(),
5897
6012
  ji: ZHIXING_JI[zhiXing].slice(),
@@ -5904,6 +6019,7 @@ function buildAlmanac(input) {
5904
6019
  sources: [
5905
6020
  '农历与节气:本地天文历法(定朔 + 节气黄经)',
5906
6021
  '建除十二神与冲煞:传统通行口径',
6022
+ '值日/时家十二神(青龙…勾陈)、二十八宿、胎神、吉神方位:通行表',
5907
6023
  '宜忌:建除十二神通行简表(非逐日全量黄历)',
5908
6024
  '每日一卦:按公历日期确定性取卦(同日同卦)',
5909
6025
  ],
@@ -5918,7 +6034,14 @@ function monthComposite(stemIdx, branchIdx) {
5918
6034
  return 0;
5919
6035
  }
5920
6036
 
5921
- return { ZHIXING, ZHIXING_YI, ZHIXING_JI, SHENGXIAO, hourToBranch, zhixingOf, lunarMonthText, dailyHexagram, buildAlmanac };
6037
+ function hourComposite(stemIdx, branchIdx) {
6038
+ for (let i = 0; i < 60; i++) {
6039
+ if (i % 10 === stemIdx && i % 12 === branchIdx) return i;
6040
+ }
6041
+ return 0;
6042
+ }
6043
+
6044
+ return { ZHIXING, ZHIXING_YI, ZHIXING_JI, SHENGXIAO, hourToBranch, zhixingOf, lunarMonthText, TIAN_SHEN, TIAN_SHEN_TYPE, tianShenOf, MANSIONS, xiuOf, dailyHexagram, buildAlmanac };
5922
6045
  })();
5923
6046
 
5924
6047
  __mods.star = (function () {
@@ -6304,26 +6427,52 @@ const { buildAlmanac, SHENGXIAO } = __mods.almanac;
6304
6427
  const TASKS = {
6305
6428
  嫁娶: { yi: ['嫁娶', '结亲', '订盟', '纳采'], related: ['会亲友', '裁衣'], category: 'marriage' },
6306
6429
  订婚: { yi: ['订盟', '纳采'], related: ['嫁娶', '会亲友'], category: 'marriage' },
6430
+ 领证: { yi: ['嫁娶', '纳采'], related: ['订盟', '会亲友'], category: 'marriage' },
6431
+ 提亲: { yi: ['纳采', '订盟'], related: ['会亲友'], category: 'marriage' },
6432
+ 收送聘金: { yi: ['纳采', '订盟'], related: ['嫁娶'], category: 'marriage' },
6433
+ 满月酒: { yi: ['祭祀', '祈福'], related: ['会亲友'], category: 'celebration' },
6307
6434
  开业: { yi: ['开市', '立券', '交易'], related: ['纳财', '挂匾'], category: 'business' },
6435
+ 交易: { yi: ['交易', '立券'], related: ['开市', '纳财'], category: 'business' },
6436
+ 投资: { yi: ['纳财', '交易'], related: ['立券'], category: 'business' },
6437
+ 签订合同: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6438
+ 签约: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6439
+ 纳财: { yi: ['纳财', '收账'], related: ['开市', '立券'], category: 'contract' },
6308
6440
  入宅: { yi: ['入宅', '移徙'], related: ['安床', '修造', '扫舍'], category: 'move' },
6309
6441
  搬家: { yi: ['移徙', '入宅'], related: ['安床', '修造'], category: 'move' },
6442
+ 搬家入宅: { yi: ['入宅', '移徙'], related: ['安床', '扫舍'], category: 'move' },
6310
6443
  动土: { yi: ['动土', '修造'], related: ['竖柱', '上梁'], category: 'construction' },
6311
6444
  装修: { yi: ['修造', '修饰垣墙'], related: ['动土'], category: 'construction' },
6445
+ 装修建造: { yi: ['修造', '动土'], related: ['竖柱', '上梁'], category: 'construction' },
6446
+ 试车买车: { yi: ['交易', '纳财'], related: ['出行'], category: 'purchase' },
6312
6447
  安葬: { yi: ['安葬', '破土'], related: ['立碑', '埋穴'], category: 'burial' },
6313
6448
  出行: { yi: ['出行'], related: ['上任', '赴任'], category: 'travel' },
6449
+ 旅行: { yi: ['出行'], related: ['上任', '赴任'], category: 'travel' },
6450
+ 赴任: { yi: ['上任', '赴任'], related: ['出行'], category: 'travel' },
6451
+ 访亲友: { yi: ['会亲友'], related: ['出行'], category: 'visit' },
6452
+ 入学: { yi: ['入学'], related: ['开市', '修造'], category: 'study' },
6314
6453
  祭祀: { yi: ['祭祀'], related: ['祈福', '求嗣'], category: 'worship' },
6315
- 签约: { yi: ['立券', '交易'], related: ['纳财', '开市'], category: 'contract' },
6316
6454
  求医: { yi: ['求医', '疗病'], related: ['针灸'], category: 'medical' },
6317
- 纳财: { yi: ['纳财', '收账'], related: ['开市', '立券'], category: 'contract' },
6455
+ };
6456
+
6457
+ const TASK_GROUPS = {
6458
+ 常用: ['嫁娶', '搬家入宅', '出行', '开业', '签订合同', '祭祀'],
6459
+ 婚嫁: ['嫁娶', '订婚', '领证', '提亲', '收送聘金', '满月酒'],
6460
+ 生活: ['搬家', '入宅', '旅行', '访亲友', '入学', '求医', '祭祀'],
6461
+ 建造: ['动土', '装修', '装修建造', '安葬', '试车买车'],
6462
+ 工商: ['开业', '交易', '投资', '签订合同', '签约', '纳财', '赴任'],
6318
6463
  };
6319
6464
 
6320
6465
  const ZHIXING_FAVOR = {
6321
6466
  marriage: ['满', '定', '成', '开'],
6467
+ celebration: ['满', '成', '开', '平'],
6322
6468
  business: ['开', '成', '满', '收'],
6323
6469
  move: ['定', '成', '开', '除'],
6324
6470
  construction: ['建', '除', '成', '开'],
6471
+ purchase: ['成', '开', '满'],
6325
6472
  burial: ['除', '定', '闭', '破'],
6326
6473
  travel: ['建', '成', '开', '除'],
6474
+ visit: ['满', '平', '定', '成'],
6475
+ study: ['成', '开', '建'],
6327
6476
  worship: ['除', '满', '平', '定', '执', '成', '开'],
6328
6477
  contract: ['定', '成', '收', '开'],
6329
6478
  medical: ['除', '开'],
@@ -6417,7 +6566,133 @@ function buildZeri(input) {
6417
6566
  };
6418
6567
  }
6419
6568
 
6420
- return { TASKS, buildZeri };
6569
+ return { TASKS, TASK_GROUPS, buildZeri };
6570
+ })();
6571
+
6572
+ __mods.convert = (function () {
6573
+ const { solarToLunar } = __mods.lunar;
6574
+ const { fromJDN, toJDN } = __mods.calendar;
6575
+ const { buildAlmanac } = __mods.almanac;
6576
+ const { assertCivil, daysInMonth } = __mods.validate;
6577
+
6578
+ const WEEK = ['日', '一', '二', '三', '四', '五', '六'];
6579
+ const MONTH_TEXT = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
6580
+ const DAY_TENS = ['初', '十', '廿'];
6581
+ const DAY_UNITS = ['十', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
6582
+
6583
+ function dayText(day) {
6584
+ if (day === 10) return '初十';
6585
+ if (day === 20) return '二十';
6586
+ if (day === 30) return '三十';
6587
+ return `${DAY_TENS[Math.floor(day / 10)]}${DAY_UNITS[day % 10]}`;
6588
+ }
6589
+
6590
+ function lunarText(year, month, day, isLeap) {
6591
+ return `农历${year}年${isLeap ? '闰' : ''}${MONTH_TEXT[month - 1] || month}月${dayText(day)}`;
6592
+ }
6593
+
6594
+ function lunarNewYearJdn(lunarYear) {
6595
+ const from = toJDN(lunarYear, 1, 20);
6596
+ const to = toJDN(lunarYear, 2, 25);
6597
+ for (let jdn = from; jdn <= to; jdn++) {
6598
+ const p = fromJDN(jdn);
6599
+ const l = solarToLunar(p.year, p.month, p.day);
6600
+ if (l.lunarYear === lunarYear && l.month === 1 && l.day === 1) return jdn;
6601
+ }
6602
+ return null;
6603
+ }
6604
+
6605
+ function lunarMonths(lunarYear) {
6606
+ const first = lunarNewYearJdn(lunarYear);
6607
+ if (first == null) return [];
6608
+ const out = [];
6609
+ let cur = first;
6610
+ for (let i = 0; i < 14; i++) {
6611
+ const p = fromJDN(cur);
6612
+ const l = solarToLunar(p.year, p.month, p.day);
6613
+ if (i > 0 && l.month === 1 && !l.isLeap) break;
6614
+ let next = null;
6615
+ for (let k = 29; k <= 31; k++) {
6616
+ const d = fromJDN(cur + k);
6617
+ const l2 = solarToLunar(d.year, d.month, d.day);
6618
+ if (l2.day === 1) { next = cur + k; break; }
6619
+ }
6620
+ if (next == null) break;
6621
+ out.push({ month: l.month, isLeap: l.isLeap, startJdn: cur, length: next - cur });
6622
+ cur = next;
6623
+ }
6624
+ return out;
6625
+ }
6626
+
6627
+ function buildConvert(input) {
6628
+ const mode = input.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
6629
+ let solar;
6630
+ let lunar;
6631
+ let note = null;
6632
+ if (mode === 'solar2lunar') {
6633
+ solar = { year: input.year, month: input.month, day: input.day };
6634
+ assertCivil(solar, '公农历转换');
6635
+ if (solar.day > daysInMonth(solar.year, solar.month)) {
6636
+ throw new Error(`非法日期:${solar.year} 年 ${solar.month} 月没有 ${solar.day} 日`);
6637
+ }
6638
+ lunar = solarToLunar(solar.year, solar.month, solar.day);
6639
+ } else {
6640
+ const y = Number(input.lunarYear);
6641
+ const m = Number(input.lunarMonth);
6642
+ const d = Number(input.lunarDay);
6643
+ if (![y, m, d].every(Number.isInteger)) throw new Error('农历转换需要 lunarYear / lunarMonth / lunarDay');
6644
+ if (m < 1 || m > 12) throw new Error('农历月份须在 1–12');
6645
+ const months = lunarMonths(y);
6646
+ if (!months.length) throw new Error(`农历 ${y} 年超出本地历法覆盖范围(1900–2100)`);
6647
+ const isLeap = input.isLeapMonth === true;
6648
+ const hit = months.find((x) => x.month === m && x.isLeap === isLeap);
6649
+ if (!hit) {
6650
+ const leapMonths = months.filter((x) => x.isLeap).map((x) => `闰${MONTH_TEXT[x.month - 1]}月`);
6651
+ throw new Error(`农历 ${y} 年没有${leapMonths.length ? `该月(该年闰月为 ${leapMonths.join('、')})` : '闰月'}:${isLeap ? '请确认闰月标记' : '请确认月份'}`);
6652
+ }
6653
+ if (d < 1 || d > hit.length) throw new Error(`农历 ${y} 年${isLeap ? '闰' : ''}${MONTH_TEXT[m - 1]}月只有 ${hit.length} 天,没有 ${d} 日`);
6654
+ const p = fromJDN(hit.startJdn + d - 1);
6655
+ solar = { year: p.year, month: p.month, day: p.day };
6656
+ const check = solarToLunar(p.year, p.month, p.day);
6657
+ if (check.month !== m || check.day !== d || check.isLeap !== isLeap) {
6658
+ note = '反查结果与目标农历日不完全一致,已按本地历法实际月首回推';
6659
+ }
6660
+ lunar = check;
6661
+ }
6662
+
6663
+ const a = buildAlmanac(solar);
6664
+ const jdn = toJDN(solar.year, solar.month, solar.day);
6665
+ const solarText = `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`;
6666
+ return {
6667
+ system: 'convert',
6668
+ meta: {
6669
+ mode,
6670
+ solarDate: solarText,
6671
+ weekday: `星期${WEEK[(jdn + 1) % 7]}`,
6672
+ ...(note ? { note } : {}),
6673
+ scope: '公农历互转按本地定朔历(1900–2100);农历闰月须显式指定,不自动猜同名月',
6674
+ },
6675
+ solar: { year: solar.year, month: solar.month, day: solar.day, text: solarText },
6676
+ lunar: {
6677
+ year: lunar.lunarYear,
6678
+ month: lunar.month,
6679
+ day: lunar.day,
6680
+ isLeap: lunar.isLeap,
6681
+ text: lunarText(lunar.lunarYear, lunar.month, lunar.day, lunar.isLeap),
6682
+ },
6683
+ ganzhi: {
6684
+ year: a.ganzhi.year,
6685
+ month: a.ganzhi.month,
6686
+ day: a.ganzhi.day,
6687
+ shengXiao: a.ganzhi.shengXiao,
6688
+ springYear: a.ganzhi.springYear,
6689
+ },
6690
+ sources: ['农历:本地定朔 + 节气黄经历法(engine/lunar.js)', '干支与生肖:与黄历同口径(年按立春界,另列春节界)'],
6691
+ disclaimer: '历法换算为确定性计算;民俗历法各版本在极少数日期(交节当日、闰月边界)存在日界差异,已注明口径。',
6692
+ };
6693
+ }
6694
+
6695
+ return { buildConvert };
6421
6696
  })();
6422
6697
 
6423
6698
  __mods['interpret-plain'] = (function () {
@@ -8590,6 +8865,7 @@ const { buildChenggu } = __mods.chenggu;
8590
8865
  const { buildXiaoliuren } = __mods.xiaoliuren;
8591
8866
  const { buildStar } = __mods.star;
8592
8867
  const { buildNumerology } = __mods.numerology;
8868
+ const { buildConvert } = __mods.convert;
8593
8869
  const { buildTrend } = __mods.trend;
8594
8870
  const { buildFengshui } = __mods.fengshui;
8595
8871
  const { buildName } = __mods.name;
@@ -9144,6 +9420,26 @@ function computeRaw(args = {}) {
9144
9420
  const result = buildChenggu({ year: dt.year, month: dt.month, day: dt.day, hour, minute: hasTime ? dt.minute : 0 });
9145
9421
  return { ...result, summary: chengguText(result) };
9146
9422
  }
9423
+ if (system === 'convert') {
9424
+ const mode = args.mode === 'lunar2solar' ? 'lunar2solar' : 'solar2lunar';
9425
+ let result;
9426
+ if (mode === 'lunar2solar') {
9427
+ if (args.lunarYear == null || args.lunarMonth == null || args.lunarDay == null) {
9428
+ throw fail('E_INPUT', '农历转公历需要 lunarYear / lunarMonth / lunarDay(闰月另加 isLeapMonth: true)');
9429
+ }
9430
+ result = buildConvert({
9431
+ mode,
9432
+ lunarYear: Number(args.lunarYear),
9433
+ lunarMonth: Number(args.lunarMonth),
9434
+ lunarDay: Number(args.lunarDay),
9435
+ isLeapMonth: args.isLeapMonth === true,
9436
+ });
9437
+ } else {
9438
+ const dt = parseDateTime(args.datetime);
9439
+ result = buildConvert({ mode, year: dt.year, month: dt.month, day: dt.day });
9440
+ }
9441
+ return { ...result, summary: convertText(result) };
9442
+ }
9147
9443
  if (system === 'xiaoliuren') {
9148
9444
  const dt = args.datetime ? parseDateTime(args.datetime) : null;
9149
9445
  const result = buildXiaoliuren({
@@ -9227,21 +9523,34 @@ function computeRaw(args = {}) {
9227
9523
  return { ...result, summary: nameText(result) };
9228
9524
  }
9229
9525
  if (!system) {
9230
- throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。');
9526
+ throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
9231
9527
  }
9232
- throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)。`);
9528
+ throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
9233
9529
  }
9234
9530
 
9235
9531
  function almanacText(r) {
9236
9532
  const g = r.ganzhi;
9237
9533
  const jq = r.jieQi && r.jieQi.next ? ` 下一节:${r.jieQi.next.name} ${r.jieQi.next.date}` : '';
9534
+ const god = r.dayGod ? `值日:${r.dayGod.name}(${r.dayGod.type}·${r.dayGod.luck})` : '';
9535
+ const xiu = r.xiu ? `二十八宿:${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
9536
+ const tai = r.taiShen ? `胎神:${r.taiShen}` : '';
9537
+ const pos = r.positions
9538
+ ? `吉神方位:喜神${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 : '—'}`
9539
+ : '';
9540
+ const lucky = r.lucky ? `吉配生肖:六合${r.lucky.liuhe || '—'} 三合${(r.lucky.sanhe || []).join('、') || '—'}` : '';
9541
+ const nayin = r.nayin && r.nayin.day ? `日柱纳音:${r.nayin.day.name}` : '';
9542
+ const hours = (r.hours || []).map((h) => `${h.zhi}${h.ganZhi.slice(0, 1)}·${h.shen}${h.luck === '吉' ? '吉' : '凶'}`).join(' ');
9238
9543
  return [
9239
9544
  `【黄历】${r.meta.date} ${r.meta.weekday} ${r.lunar.text}`,
9240
9545
  `干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}`,
9241
9546
  ...(r.meta.boundaryNote ? [` ${r.meta.boundaryNote}`] : []),
9242
- `建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} 财神:${r.positions.caiShen || '—'} 喜神:${r.positions.xiShen || '—'}`,
9547
+ `建除:${r.zhiXing}日 冲:${r.chong.text}${r.chong.sha ? `(${r.chong.sha})` : ''} ${god}`,
9548
+ [xiu, nayin, tai].filter(Boolean).join(' '),
9243
9549
  `宜:${r.yi.join('、')}`,
9244
9550
  `忌:${r.ji.join('、')}`,
9551
+ pos,
9552
+ lucky,
9553
+ hours ? `时辰吉凶:${hours}` : '',
9245
9554
  `节气:上一节 ${r.jieQi.prev ? `${r.jieQi.prev.name} ${r.jieQi.prev.date}` : '—'}${jq}`,
9246
9555
  r.dailyHexagram ? `每日一卦:${r.dailyHexagram.name} ${r.dailyHexagram.meaning || ''}` : '',
9247
9556
  `口径:${r.meta.scope}`,
@@ -9297,6 +9606,17 @@ function starText(r) {
9297
9606
  ].join('\n');
9298
9607
  }
9299
9608
 
9609
+ function convertText(r) {
9610
+ const g = r.ganzhi;
9611
+ const dir = r.meta.mode === 'lunar2solar' ? '农历 → 公历' : '公历 → 农历';
9612
+ return [
9613
+ `【公农历转换·${dir}】${r.lunar.text} ⇄ ${r.solar.text} ${r.meta.weekday}`,
9614
+ `干支:${g.year}年 ${g.month}月 ${g.day}日 属${g.shengXiao}${g.springYear && g.springYear !== g.year ? `(春节界为${g.springYear}年)` : ''}`,
9615
+ ...(r.meta.note ? [`注:${r.meta.note}`] : []),
9616
+ `口径:${r.meta.scope}`,
9617
+ ].join('\n');
9618
+ }
9619
+
9300
9620
  function numerologyText(r) {
9301
9621
  return [
9302
9622
  `【生命数字】${r.meta.date}(西方数字学,与八字/紫微不可互证)`,
@@ -9391,7 +9711,7 @@ function renderText(result) {
9391
9711
  return parts.filter(Boolean).join('\n\n');
9392
9712
  }
9393
9713
 
9394
- return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
9714
+ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
9395
9715
  })();
9396
9716
 
9397
9717
  const Engine = __mods.index;
@@ -9922,6 +10242,38 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
9922
10242
  @media (max-width:640px){.mp-cg-w{grid-template-columns:repeat(2,minmax(0,1fr))}}
9923
10243
  .mp-quick-h{font-size:13px;color:var(--lu);margin-bottom:6px;font-family:system-ui,sans-serif}
9924
10244
  .mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
10245
+ .mp-al{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
10246
+ .mp-al-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--hair);padding-bottom:8px;margin-bottom:9px}
10247
+ .mp-al-h b{font-size:18px;color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums}
10248
+ .mp-al-h i{font-style:normal;font-size:12px;color:var(--muted)}
10249
+ .mp-al-gz{font-size:12.5px;color:var(--ink2);margin-bottom:9px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
10250
+ .mp-al-badge{font-size:11px;border:1px solid var(--line);border-radius:999px;padding:1px 8px}
10251
+ .mp-al-badge.good{color:var(--lu);border-color:var(--lu)}
10252
+ .mp-al-badge.bad{color:var(--ji);border-color:var(--ji)}
10253
+ .mp-al-yiji{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:6px 0 9px}
10254
+ .mp-al-col em,.mp-al-row>em{font-style:normal;font-size:10.5px;color:var(--muted);display:block;margin-bottom:4px}
10255
+ .mp-al-chips{display:flex;flex-wrap:wrap;gap:4px}
10256
+ .mp-al-chip{font-size:11.5px;padding:1px 7px;border:1px solid var(--line);border-radius:999px;color:var(--ink2);white-space:nowrap}
10257
+ .mp-al-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line);border-radius:6px;overflow:hidden;margin:8px 0}
10258
+ .mp-al-cell{background:var(--card);padding:6px 8px;min-width:0}
10259
+ .mp-al-cell em{display:block;font-style:normal;font-size:10.5px;color:var(--muted);margin-bottom:2px}
10260
+ .mp-al-cell b{font-size:12.5px;color:var(--ink);font-weight:600;line-height:1.45;display:block}
10261
+ .mp-al-row{margin:9px 0}
10262
+ .mp-al-hours{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:5px}
10263
+ .mp-al-hour{display:flex;align-items:baseline;gap:3px;border:1px solid var(--hair);border-radius:5px;padding:3px 6px;font-size:11px;color:var(--ink2);background:var(--band)}
10264
+ .mp-al-hour i{font-style:normal;font-weight:600;color:var(--ink)}
10265
+ .mp-al-hour em{font-style:normal;color:var(--muted)}
10266
+ .mp-al-hour b{font-weight:500}
10267
+ .mp-al-hour u{text-decoration:none;margin-left:auto;font-size:10.5px}
10268
+ .mp-al-hour.good{border-color:var(--lu);color:var(--lu)}
10269
+ .mp-al-hour.good i{color:var(--lu)}
10270
+ .mp-al-hour.bad{border-color:var(--hair)}
10271
+ .mp-al-hour.bad u{color:var(--ji)}
10272
+ .mp-al-hex{margin-top:8px;font-size:12px;color:var(--ink2)}
10273
+ .mp-al-hex b{color:var(--quan)}
10274
+ .mp-al-note{margin-top:9px;padding-top:8px;border-top:1px dashed var(--line);font-size:11px;line-height:1.7;color:var(--muted)}
10275
+ @media print{.mp-al{break-inside:avoid}}
10276
+ @media (max-width:640px){.mp-al{margin:10px;padding:10px}.mp-al-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.mp-al-yiji{grid-template-columns:1fr}}.mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
9925
10277
  .mp-it-dis{padding:11px 16px;border-top:1px solid var(--line);background:var(--band);font-size:12px;line-height:1.75;color:var(--muted);font-family:system-ui,sans-serif}
9926
10278
 
9927
10279
  @media (max-width:640px){
@@ -11183,11 +11535,43 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11183
11535
  return (html || '') + pp.interpretHTML(it, opt);
11184
11536
  };
11185
11537
 
11538
+ pp.almanacHTML = function (r) {
11539
+ if (!r || !r.meta) return pp.quickHTML(r);
11540
+ const g = r.ganzhi || {};
11541
+ const god = r.dayGod || null;
11542
+ const chips = (list, cls) => (list || []).map((x) => `<span class="mp-al-chip${cls ? ` ${cls}` : ''}">${esc(x)}</span>`).join('');
11543
+ const cell = (k, v) => (v ? `<div class="mp-al-cell"><em>${esc(k)}</em><b>${esc(v)}</b></div>` : '');
11544
+ const xiu = r.xiu ? `${r.xiu.name}宿(${r.xiu.luck}·${r.xiu.zheng}·${r.xiu.animal})` : '';
11545
+ const nayin = r.nayin && r.nayin.day ? r.nayin.day.name : '';
11546
+ const chong = r.chong ? `${r.chong.text}${r.chong.sha ? ` ${r.chong.sha}` : ''}` : '';
11547
+ const hours = (r.hours || []).map((h) => `<span class="mp-al-hour ${h.luck === '吉' ? 'good' : 'bad'}">`
11548
+ + `<i>${esc(h.zhi)}</i><em>${esc((h.ganZhi || '').slice(0, 1))}</em><b>${esc(h.shen || '')}</b>`
11549
+ + `<u>${esc(h.luck || '')}</u></span>`).join('');
11550
+ const pos = r.positions || {};
11551
+ const posRow = (k, v) => (v ? `<span class="mp-al-chip">${esc(k)}·${esc(typeof v === 'string' ? v : v.dir || '')}</span>` : '');
11552
+ const lucky = r.lucky || {};
11553
+ const godBadge = god ? `<span class="mp-al-badge ${god.luck === '吉' ? 'good' : 'bad'}">${esc(god.name)}·${esc(god.type)}·${esc(god.luck)}</span>` : '';
11554
+ return `<section class="mp-al" aria-label="黄历">
11555
+ <div class="mp-al-h"><b>${esc(r.meta.date || '')}</b><i>${esc(r.meta.weekday || '')} ${esc((r.lunar || {}).text || '')}</i></div>
11556
+ <div class="mp-al-gz">${esc(g.year || '')}年 ${esc(g.month || '')}月 ${esc(g.day || '')}日 属${esc(g.shengXiao || '')} 建除 ${esc(r.zhiXing || '')}日 ${godBadge}</div>
11557
+ <div class="mp-al-yiji">
11558
+ <div class="mp-al-col"><em>宜</em><div class="mp-al-chips">${chips(r.yi)}</div></div>
11559
+ <div class="mp-al-col"><em>忌</em><div class="mp-al-chips">${chips(r.ji)}</div></div>
11560
+ </div>
11561
+ <div class="mp-al-grid">${cell('二十八宿', xiu)}${cell('日柱纳音', nayin)}${cell('胎神', r.taiShen)}${cell('冲煞', chong)}</div>
11562
+ <div class="mp-al-row"><em>吉神方位</em><div class="mp-al-chips">${posRow('喜神', pos.xiShen)}${posRow('财神', pos.caiShen)}${posRow('福神', pos.fuShen)}${posRow('阳贵', pos.yangGui)}${posRow('阴贵', pos.yinGui)}</div></div>
11563
+ <div class="mp-al-row"><em>时刻吉凶</em><div class="mp-al-hours">${hours}</div></div>
11564
+ <div class="mp-al-row"><em>吉配生肖</em><div class="mp-al-chips">${lucky.liuhe ? `<span class="mp-al-chip">六合·${esc(lucky.liuhe)}</span>` : ''}${chips(lucky.sanhe || [])}</div></div>
11565
+ ${r.dailyHexagram ? `<div class="mp-al-hex">每日一卦:<b>${esc(r.dailyHexagram.name)}</b> ${esc(r.dailyHexagram.meaning || '')}</div>` : ''}
11566
+ <div class="mp-al-note">${esc((r.meta && r.meta.scope) || '')}</div>
11567
+ </section>`;
11568
+ };
11569
+
11186
11570
  pp.quickHTML = function (r) {
11187
11571
  if (!r) return '';
11188
11572
  const LABEL = {
11189
11573
  almanac: '黄历', zeri: '择日', chenggu: '称骨', xiaoliuren: '小六壬',
11190
- star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名',
11574
+ star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名', convert: '公农历转换',
11191
11575
  };
11192
11576
  const title = LABEL[r.system] || '速查';
11193
11577
  const text = r.summary || '';
@@ -11457,7 +11841,8 @@ const TABS = [
11457
11841
  { k: 'qimen', label: '奇门', timeLabel: '起局时刻', hint: '奇门时家转盘:用起局时刻定节气、三元与局数;换局规则决定符头与节气如何对齐。' },
11458
11842
  { k: 'hepan', label: '合盘', timeLabel: '你的出生时刻', hint: '合盘按「日主 + 夫妻宫 + 五行互补」看两个人的相处与互补,需要双方出生信息。' },
11459
11843
  { k: 'tarot', label: '塔罗', timeLabel: '', hint: '塔罗不看时间,只记录抽牌时刻与你的提问。' },
11460
- { k: 'almanac', label: '黄历', timeLabel: '', hint: '黄历按公历日期查农历、干支、建除、宜忌与冲煞;只做本地计算。' },
11844
+ { k: 'almanac', label: '黄历', timeLabel: '', hint: '黄历按公历日期查农历、干支、宜忌、时辰吉凶、二十八宿与吉神方位;只做本地计算。' },
11845
+ { k: 'convert', label: '农历', timeLabel: '', hint: '公农历互转:公历生日可反查农历;只知道农历生日时,可转成公历再排盘。' },
11461
11846
  { k: 'zeri', label: '择日', timeLabel: '', hint: '择日把区间内每天的建除宜忌与事项适配打分排序,并按属相排除冲煞日。' },
11462
11847
  { k: 'chenggu', label: '称骨', timeLabel: '出生时刻', hint: '称骨按农历年、月、日、时的骨重求和;时辰必填。' },
11463
11848
  { k: 'xiaoliuren', label: '小六壬', timeLabel: '起课时刻', hint: '小六壬用农历月/日/时掐指,也可用三个随机数起课。' },
@@ -11479,7 +11864,7 @@ const TIME_KIND = {
11479
11864
  ziwei: 'birth', bazi: 'birth', hepan: 'birth',
11480
11865
  liuyao: 'cast', meihua: 'cast', daliuren: 'cast', qimen: 'cast',
11481
11866
  tarot: 'none',
11482
- almanac: 'date', zeri: 'date', star: 'date', numerology: 'date',
11867
+ almanac: 'date', zeri: 'date', star: 'date', numerology: 'date', convert: 'date',
11483
11868
  chenggu: 'birth', trend: 'birth', fengshui: 'birth', name: 'birth',
11484
11869
  xiaoliuren: 'cast',
11485
11870
  };
@@ -11514,6 +11899,9 @@ function App() {
11514
11899
  const [task, setTask] = useState('开业');
11515
11900
  const [days, setDays] = useState('30');
11516
11901
  const [shengXiao, setShengXiao] = useState('');
11902
+ const [convMode, setConvMode] = useState('solar2lunar');
11903
+ const [lunarDate, setLunarDate] = useState('');
11904
+ const [leapMonth, setLeapMonth] = useState(false);
11517
11905
  const [xlrMode, setXlrMode] = useState('time');
11518
11906
  const [num1, setNum1] = useState(''); const [num2, setNum2] = useState(''); const [num3, setNum3] = useState('');
11519
11907
  const [toAge, setToAge] = useState('60');
@@ -11619,6 +12007,24 @@ function App() {
11619
12007
  delete args.datetime;
11620
12008
  }
11621
12009
  }
12010
+ if (k === 'convert') {
12011
+ args.system = 'convert';
12012
+ if (convMode === 'lunar2solar') {
12013
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(String(lunarDate).trim());
12014
+ if (!m) {
12015
+ setError('农历转公历请按「农历年-月-日」填写,例如 2026-9-17;闰月单独勾选。');
12016
+ return;
12017
+ }
12018
+ args.mode = 'lunar2solar';
12019
+ args.lunarYear = Number(m[1]);
12020
+ args.lunarMonth = Number(m[2]);
12021
+ args.lunarDay = Number(m[3]);
12022
+ args.isLeapMonth = leapMonth;
12023
+ delete args.datetime;
12024
+ } else {
12025
+ args.mode = 'solar2lunar';
12026
+ }
12027
+ }
11622
12028
  if (k === 'tarot') args.count = Number(count);
11623
12029
  if (k === 'hepan') {
11624
12030
  args.chart = hpChart;
@@ -11987,8 +12393,24 @@ function App() {
11987
12393
  })) : null,
11988
12394
  tab === 'zeri' ? field('所办事项', h('select', {
11989
12395
  className: 'mp-input', value: task, onChange: (e) => setTask(e.target.value),
11990
- }, ['嫁娶', '订婚', '开业', '入宅', '搬家', '动土', '装修', '安葬', '出行', '祭祀', '签约', '求医', '纳财']
11991
- .map((t) => h('option', { key: t, value: t }, t)))) : null,
12396
+ }, [
12397
+ ['常用', ['嫁娶', '搬家入宅', '出行', '开业', '签订合同', '祭祀']],
12398
+ ['婚嫁', ['嫁娶', '订婚', '领证', '提亲', '收送聘金', '满月酒']],
12399
+ ['生活', ['搬家', '入宅', '旅行', '访亲友', '入学', '求医', '祭祀']],
12400
+ ['建造', ['动土', '装修', '装修建造', '安葬', '试车买车']],
12401
+ ['工商', ['开业', '交易', '投资', '签订合同', '签约', '纳财', '赴任']],
12402
+ ].map(([g, items]) => h('optgroup', { key: g, label: g },
12403
+ items.map((t) => h('option', { key: `${g}-${t}`, value: t }, t)))))) : null,
12404
+ tab === 'convert' ? field('转换方向', h('div', { className: 'mp-seg' },
12405
+ h('button', { className: convMode === 'solar2lunar' ? 'on' : '', onClick: () => setConvMode('solar2lunar') }, '公历 → 农历'),
12406
+ h('button', { className: convMode === 'lunar2solar' ? 'on' : '', onClick: () => setConvMode('lunar2solar') }, '农历 → 公历'))) : null,
12407
+ (tab === 'convert' && convMode === 'lunar2solar') ? field('农历日期', h('input', {
12408
+ className: 'mp-input', value: lunarDate, placeholder: '如 2026-9-17',
12409
+ onChange: (e) => setLunarDate(e.target.value),
12410
+ })) : null,
12411
+ (tab === 'convert' && convMode === 'lunar2solar') ? h('label', {
12412
+ className: 'mp-check',
12413
+ }, h('input', { type: 'checkbox', checked: leapMonth, onChange: (e) => setLeapMonth(e.target.checked) }), ' 闰月') : null,
11992
12414
  tab === 'zeri' ? field('看多少天', h('input', {
11993
12415
  className: 'mp-input', type: 'number', min: 1, max: 60, value: days,
11994
12416
  onChange: (e) => setDays(e.target.value),
@@ -12114,6 +12536,7 @@ function App() {
12114
12536
  : result.kind === 'tarot' ? PP.tarotHTML(result.r)
12115
12537
  : result.kind === 'liuyao' ? PP.liuyaoHTML(result.r)
12116
12538
  : result.kind === 'chenggu' ? PP.chengguHTML(result.r)
12539
+ : result.kind === 'almanac' ? PP.almanacHTML(result.r)
12117
12540
  : result.kind === 'trend' ? PP.trendHTML(result.r)
12118
12541
  : PP.quickHTML(result.r),
12119
12542
  result.it,