模組:SunXiu
外观
|
请对照以下删除理由判断本页是否具备执行快速删除的理据:
本页可能符合快速删除的標準而需删除,理由:
請勿移除本模板。如有異議,請在本模板下方加入 其他編者若認為本頁明顯不符合快速删除的标准,或者您已修正存在的問題,可去除此模板。 |
本模块用于计算任意时刻太阳的精确位置,并将其映射到中国传统天文体系(二十八宿、十二次)与中西黄道体系(西洋十二宫、IAU真实星座)。
概述
[编辑]时区默认 UTC+8:
- 太阳几何位置:黄经、赤纬、赤经、日地距、均时差、视半径等。
- 二十四节气:以定气法迭代求解交节时刻,输出当前所处节气区间、已过天数、距下一节气天数。
- 黄道宫多体系对照:西洋回归十二宫 / 西洋恒星十二宫 / IAU 真实星座(含蛇夫座)/ 中国十二次+十二辰 / 二十八宿日躔。
完整参数表
[编辑]太阳几何位置
[编辑]| 字段 | 含义 | 单位 |
|---|---|---|
jd |
儒略日(UT) | 日 |
jd_tt |
儒略日(力学时 TT) | 日 |
T |
儒略世纪数(自 J2000.0 起) | 世纪 |
lon |
太阳真黄经(几何,未含光行差与章动) | ° |
lon_app |
视黄经(含光行差 + 章动修正) | ° |
lat |
黄纬(恒为 0,保留字段) | ° |
ra |
赤经 | 小时 |
ra_deg |
赤经 | ° |
dec |
赤纬 | ° |
dist_au |
日地距离 | AU |
eq_time |
均时差 | 分 |
eps_true |
真黄赤交角 | ° |
radius_arcsec |
太阳视半径 | ″ |
二十四节气(定气法)
[编辑]以春分黄经 0° 起,每 15° 一节气,二分法迭代求解交节时刻。
| 字段 | 含义 |
|---|---|
prev_term |
上一节气名 |
prev_term_jd |
上一节气交节 JD |
prev_term_time |
上一节气北京时间 |
cur_term |
当前所处节气段名(如"寒露后") |
days_after_term |
当前节气后天数(浮点) |
days_after_term_hm |
格式化:"第3天 04时12分" |
next_term |
下一节气名 |
next_term_jd |
下一节气 JD |
next_term_time |
下一节气北京时间 |
days_to_next_term |
距下一节气剩余天数(浮点) |
term_lon |
当前段对应黄经基准(如寒露 = 195°) |
黄道宫体系
[编辑]西洋回归黄道十二宫(Tropical,锚定春分点)
[编辑]| 字段 | 含义 |
|---|---|
trop_sign |
宫名(白羊宫/金牛宫/…/双鱼宫) |
trop_sign_cn |
中文宫名(同上,保留字段) |
trop_in_deg |
入本宫距度(0–30°) |
trop_days_to_next |
距下一宫天数 |
西洋恒星黄道(Sidereal,含岁差偏移)
[编辑]| 字段 | 含义 |
|---|---|
sidereal_sign |
恒星制宫名 |
sidereal_ayanamsa |
岁差修正量(°) |
sidereal_in_deg |
入宫距度(0–30°) |
IAU 实际黄道星座(含蛇夫座)
[编辑]| 字段 | 含义 |
|---|---|
iau_constellation |
真实星座(白羊座/…/蛇夫座/…) |
iau_in_deg |
入星座边界距度 |
iau_note |
"宫≠星座"说明 |
中国十二次(自西向东等分,配十二辰)
[编辑]| 字段 | 含义 |
|---|---|
xing_ci |
星次名(星纪/玄枵/娵訾/降娄/大梁/实沈/鹑首/鹑火/鹑尾/寿星/大火/析木) |
chen |
十二辰(子丑寅卯辰巳午未申酉戌亥) |
xingci_in_deg |
入本次距度 |
十二次↔十二辰↔宿大致对应:
- 星纪(丑)·斗牛|玄枵(子)·女虚危|娵訾(亥)·室壁|降娄(戌)·奎娄|大梁(酉)·胃昴毕|实沈(申)·觜参|鹑首(未)·井鬼|鹑火(午)·柳星张|鹑尾(巳)·翼轸|寿星(辰)·角亢|大火(卯)·氐房心|析木(寅)·尾箕
中国二十八宿日躔
[编辑]| 字段 | 含义 |
|---|---|
sun_xiu |
太阳所在宿名 |
sun_xiu_in_deg |
入宿距度 |
sun_xiu_quji |
去极度(90° − 赤纬) |
sun_xiu_system |
所用宿度体系(huainan/dayan_ecl/dayan_equ/modern) |
sun_xiu_sysname |
宿度体系中文名 |
接口(公开函数)
[编辑]| 接口 | 说明 | 示例 |
|---|---|---|
p.get |
取任意单个字段值 | {{#invoke:SunXiu|get|param=sun_xiu}}
|
p.getJD |
取当前儒略日 | {{#invoke:SunXiu|getJD}}
|
p.infoBox |
完整信息框(6 区块) | {{#invoke:SunXiu|infoBox|system=modern}}
|
p.short |
简洁摘要(一行) | {{#invoke:SunXiu|short}}
|
p.solarTermTable |
指定年份 24 节气时刻表 | {{#invoke:SunXiu|solarTermTable|year=2026}}
|
p.zodiac |
多体系黄道宫对照表 | {{#invoke:SunXiu|zodiac}}
|
调用参数
[编辑]所有接口均接受以下 frame.args 参数:
| 参数 | 含义 | 默认 |
|---|---|---|
year |
年份 | 当前年 |
month |
月份(1–12) | 当前月 |
day |
日期(1–31) | 当日 |
hour |
小时(UTC+8) | 12 |
system |
二十八宿宿度体系,可选值:huainan(淮南子距度)dayan_ecl(大衍历黄道)dayan_equ(大衍历赤道)modern(现代真黄道) |
huainan
|
示例
[编辑]- 太阳所在宿名:
{{#invoke:SunXiu|get|param=sun_xiu}}→ 毕 - 太阳视黄经:
{{#invoke:SunXiu|get|param=lon_app}}→ 231.88614 - 太阳赤纬:
{{#invoke:SunXiu|get|param=dec}}→ -18.23537 - 儒略日:
{{#invoke:SunXiu|getJD}}→ 2461358.75 - 距下一节气天数(格式化):
{{#invoke:SunXiu|get|param=days_after_term_hm}}→ 6天 20时02分 - 多体系对照:返回西洋宫 / 真实星座 / 十二次 / 宿的对照表。
{{#invoke:SunXiu|zodiac}} →
| 体系 | 名称 | 入度 | 备注 |
|---|---|---|---|
| 回归黄道宫 | 天蝎宫 | 21.89° | 春分点为0° |
| 恒星黄道宫 | 天秤座 | 28.89° | 岁差偏移 23.00° |
| IAU真实星座 | 天蝎座 | 21.89° | 注:IAU星座边界非均匀,此为近似 |
| 中国十二次 | 大火(辰在卯) | 21.89° | 起于星纪 |
| 二十八宿日躔 | 毕 | 5.89° | 体系:《淮南子·天文训》距度 |
- 指定参数:
{{#invoke:SunXiu|infoBox|year=2026|month=10|day=23|hour=20|system=modern}}→
模板
[编辑]基于 Module:SunXiu 计算当前或指定日期的太阳位置、二十八宿、节气、黄道宫等信息。
参数
[编辑]year– 年份(数字),默认当前年month– 月份(1–12),默认当前月day– 日期(1–31),默认当日hour– 小时(0–23,UTC+8),默认12时system– 二十八宿体系,可选huainan(默认)、dayan_ecl、dayan_equ、modern
输出内容
[编辑]- 浮动信息框:太阳所在的二十八宿、入宿距度、去极度、体系
- 几何位置:黄经、赤纬、赤经、日地距、均时差、视半径
- 节气状态:当前节气区间、已过时长、下一节气及时刻
- 回归黄道宫、恒星黄道宫、IAU真实星座、中国十二次
- 可折叠原始数据:JD、JD_TT、T、视黄经、赤纬、日地距、均时差、视半径
相关模板
[编辑]完整信息框
{{SunXiuall}}
{{SunXiuall|year=2026|month=10|day=23|hour=12|system=huainan}}
当前节气
{{SunJQ}}
太阳位置
{{SunXiuJJ}}
{{SunXiuJJ|year=2026|month=10|day=23|hour=12|system=modern}}
技术说明
[编辑]- 时区默认 UTC+8,若需其他时区请在调用前换算。
- "宫"≠"星座":西洋十二宫为等分 30°,IAU 真实星座边界不均等(且含蛇夫座),两者不可混用。
- 十二次为等分体系,二十八宿不等分;跨宿/跨次边界以太阳真黄经精确判定。
- 节气时刻采用定气法,与现行农历节气一致;与平气法(固定 15.x 天)略有出入。
- 本模块为自包含设计,不依赖外部数据模块。
- 算法基于 Meeus 低阶 VSOP 简化公式,节气采用定气法(黄经每 15° 一节点)二分迭代求解。
require('Module:Module wikitext')._addText('{{D|G3|G21|见[[U:12З4567/Wenlongtian调查]]}}')
-- Module:SunXiu 太阳位置与二十八宿计算(自包含,无外部数据依赖)
local p = {}
local TERM_LONS = {0,15,30,45,60,75,90,105,120,135,150,165,180,195,210,225,240,255,270,285,300,315,330,345}
local TERM_NAMES = {
[0]="春分",[15]="清明",[30]="谷雨",[45]="立夏",[60]="小满",[75]="芒种",
[90]="夏至",[105]="小暑",[120]="大暑",[135]="立秋",[150]="处暑",[165]="白露",
[180]="秋分",[195]="寒露",[210]="霜降",[225]="立冬",[240]="小雪",[255]="大雪",
[270]="冬至",[285]="小寒",[300]="大寒",[315]="立春",[330]="雨水",[345]="惊蛰"
}
local TROP_SIGNS = {"白羊宫","金牛宫","双子宫","巨蟹宫","狮子宫","室女宫","天秤宫","天蝎宫","射手宫","摩羯宫","宝瓶宫","双鱼宫"}
local SID_SIGNS = {"白羊座","金牛座","双子座","巨蟹座","狮子座","室女座","天秤座","天蝎座","射手座","摩羯座","宝瓶座","双鱼座"}
local IAU_CONST = {
{name="白羊座", s=0, e=30},{name="金牛座", s=30, e=60},{name="双子座", s=60, e=90},
{name="巨蟹座", s=90, e=120},{name="狮子座", s=120, e=150},{name="室女座", s=150, e=180},
{name="天秤座", s=180, e=210},{name="天蝎座", s=210, e=240},{name="蛇夫座", s=240, e=270},
{name="射手座", s=270, e=300},{name="摩羯座", s=300, e=330},{name="宝瓶座", s=330, e=360}
}
local XINGCI_NAMES = {"星纪","玄枵","娵訾","降娄","大梁","实沈","鹑首","鹑火","鹑尾","寿星","大火","析木"}
local CHEN_NAMES = {"丑","子","亥","戌","酉","申","未","午","巳","辰","卯","寅"}
local XINGCI_START = 270
local XIU_SYSTEMS = {
huainan = {
name = "《淮南子·天文训》距度", origin = 0,
stars = {{"角",12},{"亢",9},{"氐",15},{"房",5},{"心",5},{"尾",18},{"箕",11},
{"斗",26},{"牛",8},{"女",12},{"虚",10},{"危",17},{"室",16},{"壁",9},
{"奎",16},{"娄",12},{"胃",14},{"昴",11},{"毕",16},{"觜",2},{"参",9},
{"井",33},{"鬼",4},{"柳",15},{"星",7},{"张",18},{"翼",18},{"轸",17}}
},
dayan_ecl = {
name = "大衍历·黄道宿度", origin = 0,
stars = {{"角",12.5},{"亢",9.5},{"氐",16},{"房",5.5},{"心",5.5},{"尾",18.5},{"箕",10.5},
{"斗",24},{"牛",7.5},{"女",11.5},{"虚",9.5},{"危",16.5},{"室",17},{"壁",9.5},
{"奎",17},{"娄",12.5},{"胃",14.5},{"昴",10.5},{"毕",16.5},{"觜",1.5},{"参",9.5},
{"井",32},{"鬼",3.5},{"柳",14.5},{"星",7.5},{"张",17.5},{"翼",18.5},{"轸",16.5}}
},
dayan_equ = {
name = "大衍历·赤道宿度", origin = 0,
stars = {{"角",12},{"亢",9},{"氐",15},{"房",5},{"心",5},{"尾",18},{"箕",11},
{"斗",25},{"牛",7},{"女",11},{"虚",10},{"危",16},{"室",17},{"壁",9},
{"奎",16},{"娄",12},{"胃",14},{"昴",11},{"毕",16},{"觜",2},{"参",9},
{"井",33},{"鬼",4},{"柳",15},{"星",7},{"张",18},{"翼",18},{"轸",17}}
},
modern = {
name = "现代真黄道宿界(含岁差)", origin = -28.0,
stars = {{"角",11.3},{"亢",9.0},{"氐",14.4},{"房",5.2},{"心",6.1},{"尾",19.0},{"箕",10.2},
{"斗",24.0},{"牛",7.8},{"女",11.0},{"虚",9.5},{"危",16.2},{"室",17.0},{"壁",8.8},
{"奎",15.5},{"娄",11.8},{"胃",13.5},{"昴",10.2},{"毕",15.8},{"觜",1.8},{"参",9.2},
{"井",31.5},{"鬼",3.8},{"柳",14.2},{"星",7.2},{"张",17.2},{"翼",17.8},{"轸",16.8}}
}
}
local function round(v, n)
local pow = 10^(n or 2)
return math.floor(v*pow + 0.5) / pow
end
local function mod360(x) return x - math.floor(x/360)*360 end
local function mod365(x) return x - math.floor(x/365.25)*365.25 end
local function fmtDays(days)
local sec = math.abs(days) * 86400
local d = math.floor(sec / 86400)
local r = sec % 86400
local h = math.floor(r / 3600)
local m = math.floor((r % 3600) / 60)
return string.format("%d天 %d时%02d分", d, h, m)
end
local function jdFromDate(y,m,d,h)
h = h or 12
if m <= 2 then y=y-1; m=m+12 end
local a = math.floor(y/100)
local b = 2 - a + math.floor(a/4)
return math.floor(365.25*(y+4716)) + math.floor(30.6001*(m+1)) + d + b - 1524.5 + h/24
end
local function dateFromJd(jd)
local z = math.floor(jd+0.5)
local f = (jd+0.5)-z
local a = z
if z >= 2299161 then
local alpha = math.floor((z-1867216.25)/36524.25)
a = z+1+alpha-math.floor(alpha/4)
end
local b = a+1524
local c = math.floor((b-122.1)/365.25)
local dd = math.floor(365.25*c)
local e = math.floor((b-dd)/30.6001)
local day = b-dd-math.floor(30.6001*e)+f
local month = e<14 and (e-1) or (e-13)
local year = month>2 and (c-4716) or (c-4715)
local hh = math.floor((day-math.floor(day))*24)
local mi = math.floor(((day-math.floor(day))*24-hh)*60)
return year, month, math.floor(day), hh, mi
end
local function calcNutation(jd)
local T = (jd-2451545.0)/36525.0
local D = 297.85036+445267.11148*T-0.0019142*T*T+T^3/189474
local M = 357.52772+35999.05034*T-0.0001603*T*T-T^3/300000
local Mp= 134.96298+477198.867398*T+0.0086972*T*T+T^3/56250
local F = 93.27191+483202.017538*T-0.0036825*T*T+T^3/327270
local Om= 125.04452-1934.136261*T+0.0020708*T*T+T^3/450000
local rd=math.rad; local s=math.sin; local c=math.cos
local dD=rd(D); local dM=rd(M); local dMp=rd(Mp); local dF=rd(F); local dOm=rd(Om)
local dp = -17.1996*s(dOm) -1.3187*s(2*(dF-dOm+dMp)) -0.2274*s(2*dF) +0.2062*s(2*dOm)
+0.1426*s(2*(dF+dMp)) +0.0712*s(2*(dF-dOm)) -0.0517*s(2*dMp) -0.038*s(2*(dF+dMp-dOm))
-0.0304*s(2*(dF+dMp+dOm)) +0.0153*s(2*(dF-dOm-dMp)) -0.0127*s(2*(dF+dM)) -0.0103*s(2*(dF-dM))
local de = 9.2025*c(dOm) +0.5736*c(2*(dF-dOm+dMp)) +0.0977*c(2*dF) -0.0895*c(2*dOm)
+0.0224*c(2*(dF+dMp)) -0.0209*c(2*(dF-dOm)) -0.0095*c(2*dMp) +0.0078*c(2*(dF+dMp-dOm))
+0.0068*c(2*(dF+dMp+dOm))
return dp, de
end
local function meanObliquity(jd)
local T=(jd-2451545.0)/36525.0
return 23.439291-0.0130042*T-1.64e-7*T*T+5.04e-7*T*T*T
end
local function sunPos(jd)
local T=(jd-2451545.0)/36525.0; local T2=T*T
local L0=mod360(280.46646+36000.76983*T+0.0003032*T2)
local M=mod360(357.52911+35999.05029*T-0.0001537*T2)
local Mr=math.rad(M)
local C=(1.914602-0.004817*T-0.000014*T2)*math.sin(Mr)
+(0.019993-0.000101*T)*math.sin(2*Mr)+0.000289*math.sin(3*Mr)
local lon=mod360(L0+C)
local R=1.000001018*(1-0.016708634*math.cos(Mr)-0.000042037*math.cos(2*Mr)-0.000000126*math.cos(3*Mr))
local kappa=20.49552
local lonApp=lon+(kappa/3600)/R
local dp,_=calcNutation(jd)
lonApp=mod360(lonApp+dp/3600)
local eps=meanObliquity(jd); local epsRad=math.rad(eps)
local lonRad=math.rad(lonApp)
local atan2 = math.atan2 or function(y,x) return math.atan(y, x) end
local ra=atan2(math.sin(lonRad)*math.cos(epsRad), math.cos(lonRad))
local dec=math.asin(math.sin(lonRad)*math.sin(epsRad))
local raDeg=mod360(math.deg(ra)); local raHour=raDeg/15
local y=math.tan(epsRad/2)^2
local sin2L0=math.sin(2*math.rad(L0)); local sinM=math.sin(Mr)
local cos2L0=math.cos(2*math.rad(L0)); local cosM=math.cos(Mr)
local eqTime=4*(y*sin2L0-2 * 0.0167*sinM+0.0433*sin2L0*cosM-0.0325*cos2L0*sinM-0.125*cos2L0)
if eqTime>20 then eqTime=eqTime-720 end
if eqTime<-20 then eqTime=eqTime+720 end
local radius=959.633/R
return {lonApp=lonApp, dec=math.deg(dec), ra=raHour, raDeg=raDeg, distAu=R,
eqTime=eqTime, epsTrue=eps, radiusArcsec=radius, lat=0}
end
local function sunLonQuick(jd)
local n=jd-2451545.0
local L=mod360(280.460+0.9856474*n)
local g=mod360(357.528+0.9856003*n); local gr=math.rad(g)
return mod360(L+1.915*math.sin(gr)+0.020*math.sin(2*gr))
end
local function refineTerm(jdGuess, targetLon)
local lo=jdGuess-5; local hi=jdGuess+5
for _=1,40 do
local mid=(lo+hi)/2
local diff=sunLonQuick(mid)-targetLon
diff=mod360(diff+180)-180
if diff>0 then hi=mid else lo=mid end
end
return (lo+hi)/2
end
local function findTermNear(targetLon, jdAround)
local lo=jdAround-10; local hi=jdAround+10
for _=1,50 do
local mid=(lo+hi)/2
local diff=sunLonQuick(mid)-targetLon
diff=mod360(diff+180)-180
if diff>0 then hi=mid else lo=mid end
end
return (lo+hi)/2
end
local function findTermBracket(targetLon, jdStart)
local lo=jdStart; local hi=jdStart+400
for _=1,50 do
local mid=(lo+hi)/2
local diff=sunLonQuick(mid)-targetLon
diff=mod360(diff+180)-180
if diff>0 then hi=mid else lo=mid end
end
return (lo+hi)/2
end
local function getTermBracket(jd)
local candidates={}
for _,lon in ipairs(TERM_LONS) do
local guess=jd+(lon/15-12)*15.218
local lo=guess-200; local hi=guess+200
for _=1,60 do
local mid=(lo+hi)/2
local diff=sunLonQuick(mid)-lon
diff=mod360(diff+180)-180
if diff>0 then hi=mid else lo=mid end
end
local tjd=(lo+hi)/2
table.insert(candidates,{lon=lon, name=TERM_NAMES[lon], jd=tjd})
end
local prevTerm, nextTerm
for _,c in ipairs(candidates) do
if c.jd <= jd then
if not prevTerm or c.jd > prevTerm.jd then prevTerm=c end
end
if c.jd > jd then
if not nextTerm or c.jd < nextTerm.jd then nextTerm=c end
end
end
if not prevTerm then
prevTerm=candidates[1]; for _,c in ipairs(candidates) do
if c.jd > prevTerm.jd then prevTerm=c end end
end
if not nextTerm then
nextTerm=candidates[1]; for _,c in ipairs(candidates) do
if c.jd < nextTerm.jd then nextTerm=c end end
end
return prevTerm, nextTerm, candidates
end
local function findStar(pos, sys)
local acc=0
for i,s in ipairs(sys.stars) do
local w=s[2]
if pos>=acc and pos<acc+w then return s[1],acc,w,i end
acc=acc+w
end
local last=sys.stars[#sys.stars]
return last[1], 365.25-last[2], last[2], #sys.stars
end
local function sunInXiu(lon, sysKey)
local sys=XIU_SYSTEMS[sysKey] or XIU_SYSTEMS.huainan
local pos=mod365(lon-sys.origin)
if pos<0 then pos=pos+365.25 end
local name,startDeg,_,_=findStar(pos,sys)
local inDeg=pos-startDeg
return name, inDeg, sys.name
end
local function getZodiac(lon)
local lonNorm=mod360(lon)
local tropIdx=math.floor(lonNorm/30)+1
local tropIn=lonNorm-(tropIdx-1)*30
local tropDays=(30-tropIn)/0.985647
local ayanamsa=23.0
local sidLon=mod360(lonNorm-ayanamsa)
local sidIdx=math.floor(sidLon/30)+1
local sidIn=sidLon-(sidIdx-1)*30
local iauName="双鱼座"; local iauIn=lonNorm-330
for _,c in ipairs(IAU_CONST) do
if lonNorm>=c.s and lonNorm<c.e then iauName=c.name; iauIn=lonNorm-c.s; break end
end
local xcLon=mod360(lonNorm-XINGCI_START)
local xcIdx=math.floor(xcLon/30)+1
local xcName=XINGCI_NAMES[xcIdx]; local chen=CHEN_NAMES[xcIdx]
local xcIn=xcLon-(xcIdx-1)*30
return {
trop_sign=TROP_SIGNS[tropIdx], trop_sign_cn=TROP_SIGNS[tropIdx], trop_in_deg=tropIn, trop_days_to_next=tropDays,
sidereal_sign=SID_SIGNS[sidIdx], sidereal_ayanamsa=ayanamsa, sidereal_in_deg=sidIn,
iau_constellation=iauName, iau_in_deg=iauIn, iau_note="注:IAU星座边界非均匀,此为近似",
xing_ci=xcName, chen=chen, xingci_in_deg=xcIn
}
end
local function compute(args)
-- === 获取当前北京时间(UTC+8)并转换为UT用于天文计算 ===
local utc = os.date("!*t") -- 当前UTC时间表
local utc_sec = utc.hour * 3600 + utc.min * 60 + utc.sec -- UTC当天秒数
local bj_sec = utc_sec + 8 * 3600 -- 北京时间当天秒数
local bj_day_offset = math.floor(bj_sec / 86400)
local bj_h = (bj_sec % 86400) / 3600 -- 北京时间小时(含小数,用于显示)
local bj_day = utc.day + bj_day_offset
local bj_month = utc.month
local bj_year = utc.year
-- 处理跨日/月/年
while bj_day > 28 do
local days_in_month = os.date("*t", os.time({year=bj_year, month=bj_month, day=1}) - 86400).day
if bj_day <= days_in_month then break end
bj_day = bj_day - days_in_month
bj_month = bj_month + 1
if bj_month > 12 then bj_month = 1; bj_year = bj_year + 1 end
end
while bj_day < 1 do
bj_month = bj_month - 1
if bj_month < 1 then bj_month = 12; bj_year = bj_year - 1 end
local days_in_prev = os.date("*t", os.time({year=bj_year, month=bj_month, day=1}) - 86400).day
bj_day = bj_day + days_in_prev
end
-- 应用用户传入的参数(覆盖默认的北京时间)
local y = tonumber(args.year) or bj_year
local m = tonumber(args.month) or bj_month
local d = tonumber(args.day) or bj_day
local bj_h_display = tonumber(args.hour) or math.floor(bj_h) -- 显示用整点小时
local sysKey = args.system or "huainan"
-- 将北京时间转换为世界时(UT)
local ut_h = bj_h_display - 8
local ut_day = d
if ut_h < 0 then ut_h = ut_h + 24; ut_day = d - 1 end
if ut_h >= 24 then ut_h = ut_h - 24; ut_day = d + 1 end
-- 处理跨月(简化:若ut_day超出范围则借用os.date重新计算,但此处只需保证jdFromDate能正确处理)
-- 实际上jdFromDate可以处理负数和大于月份天数的日期,但为了严谨,我们简单处理:
if ut_day < 1 then
local prev_month = m - 1
local prev_year = y
if prev_month < 1 then prev_month = 12; prev_year = y - 1 end
local days_in_prev = os.date("*t", os.time({year=prev_year, month=prev_month, day=1}) - 86400).day
ut_day = ut_day + days_in_prev
m = prev_month
y = prev_year
elseif ut_day > 28 then
local days_in_this = os.date("*t", os.time({year=y, month=m, day=1}) - 86400).day
if ut_day > days_in_this then
ut_day = ut_day - days_in_this
m = m + 1
if m > 12 then m = 1; y = y + 1 end
end
end
local jd = jdFromDate(y, m, ut_day, ut_h)
local jdTT = jd + 69.184/86400
-- === 时间处理结束 ===
local sun = sunPos(jdTT)
local lonApp = sun.lonApp; local dec = sun.dec
local prev, nxt = getTermBracket(jd)
local prevTerm, prevTermJd = prev.name, prev.jd
local termLon = prev.lon
local segNames={
["春分"]="春分后",["清明"]="清明后",["谷雨"]="谷雨后",["立夏"]="立夏后",
["小满"]="小满后",["芒种"]="芒种后",["夏至"]="夏至后",["小暑"]="小暑后",
["大暑"]="大暑后",["立秋"]="立秋后",["处暑"]="处暑后",["白露"]="白露后",
["秋分"]="秋分后",["寒露"]="寒露后",["霜降"]="霜降后",["立冬"]="立冬后",
["小雪"]="小雪后",["大雪"]="大雪后",["冬至"]="冬至后",["小寒"]="小寒后",
["大寒"]="大寒后",["立春"]="立春后",["雨水"]="雨水后",["惊蛰"]="惊蛰后"
}
local curTerm = segNames[prev.name] or (prev.name.."后")
local nextTerm, nextTermJd = nxt.name, nxt.jd
local daysAfter = jd - prev.jd
local daysToNext = nxt.jd - jd
if daysAfter < 0 then daysAfter = daysAfter + 365.25 end
if daysToNext < 0 then daysToNext = daysToNext + 365.25 end
local daysAfterHM = fmtDays(daysAfter)
local function formatJd(jdVal)
local jdLocal=jdVal+8/24
local yy,mm,dd,hh,mi=dateFromJd(jdLocal)
return string.format("%04d-%02d-%02d %02d:%02d",yy,mm,dd,hh,mi)
end
local prevTermTime = formatJd(prevTermJd)
local nextTermTime = formatJd(nextTermJd)
local xiuname, xiuindeg, xiusysname = sunInXiu(lonApp, sysKey)
local quji=90-dec
local zod=getZodiac(lonApp)
return {
jd=jd, jd_tt=jdTT, T=(jdTT-2451545.0)/36525.0,
lon=sun.lonApp, lon_app=sun.lonApp, lat=sun.lat,
ra=sun.ra, ra_deg=sun.raDeg, dec=sun.dec,
dist_au=sun.distAu, eq_time=sun.eqTime,
eps_true=sun.epsTrue, radius_arcsec=sun.radiusArcsec,
prev_term=prevTerm, prev_term_jd=prevTermJd, prev_term_time=prevTermTime,
cur_term=curTerm, days_after_term=daysAfter, days_after_term_hm=daysAfterHM,
next_term=nextTerm, next_term_jd=nextTermJd, next_term_time=nextTermTime,
days_to_next_term=daysToNext, term_lon=termLon,
trop_sign=zod.trop_sign, trop_sign_cn=zod.trop_sign_cn,
trop_in_deg=zod.trop_in_deg, trop_days_to_next=zod.trop_days_to_next,
sidereal_sign=zod.sidereal_sign, sidereal_ayanamsa=zod.sidereal_ayanamsa,
sidereal_in_deg=zod.sidereal_in_deg,
iau_constellation=zod.iau_constellation, iau_in_deg=zod.iau_in_deg, iau_note=zod.iau_note,
xing_ci=zod.xing_ci, chen=zod.chen, xingci_in_deg=zod.xingci_in_deg,
sun_xiu=xiuname, sun_xiu_in_deg=xiuindeg, sun_xiu_quji=quji,
sun_xiu_system=sysKey, sun_xiu_sysname=xiusysname,
-- 存储北京时间(显示用)
year = y, month = m, day = d, hour = bj_h
}
end
local function renderBox(d)
local out = {}
table.insert(out, '<div style="width:300px;float:right;clear:right;margin:0 0 1em 1em;'..
'border:1px solid #a2a9b1;border-radius:10px;overflow:hidden;font-family:sans-serif;'..
'box-shadow:0 2px 6px rgba(0,0,0,.12);background:#fff;font-size:13px;line-height:1.5">')
-- d.hour 已经是北京时间,无需再转换
local timeStr = string.format("%04d-%02d-%02d %02d:00 UTC+8", d.year, d.month, d.day, d.hour)
table.insert(out, string.format(
'<div style="background:linear-gradient(90deg,#e68a2e,#f0b400);color:#fff;padding:10px 12px;'..
'font-weight:bold;text-align:center;font-size:15px">太阳推算 · %s</div>', timeStr))
table.insert(out, string.format(
'<div style="padding:12px;text-align:center;background:#fff8e6;border-bottom:1px solid #eaecf0">'..
'<div style="font-size:18px;font-weight:bold;color:#8a5500">太阳在【%s】宿</div>'..
'<div style="margin-top:4px">入宿距度 <b>%.2f°</b> · 去极度 <b>%.2f°</b></div>'..
'<div style="font-size:11px;color:#887">体系:%s</div></div>',
d.sun_xiu, d.sun_xiu_in_deg, d.sun_xiu_quji, d.sun_xiu_sysname))
local function row(label, val, bg)
bg = bg or "#fafcff"
return '<tr style="background:' .. bg .. ';"><td style="width:96px;padding:6px 8px;font-weight:bold;background:#f1f4f6;border-right:1px solid #eaecf0;border-bottom:1px solid #eaecf0;">' .. label .. '</td><td style="padding:6px 8px;border-bottom:1px solid #eaecf0;">' .. val .. '</td></tr>'
end
table.insert(out, '<table style="width:100%;border-collapse:collapse;">')
table.insert(out, row("计算参数",
string.format("JD(UT) %.4f · JD(TT) %.4f<br/>T=%.6f",
d.jd, d.jd_tt, d.T)))
table.insert(out, row("岁差/章动",
string.format("真黄赤交角 %.4f°<br/>章动 Δψ=%.3f″ Δε=%.3f″",
d.eps_true, 0, 0), "#f6f8fa"))
table.insert(out, row("太阳坐标",
string.format("视黄经 %.4f° · 赤纬 %.4f°<br/>赤经 %.4fʰ",
d.lon_app, d.dec, d.ra)))
table.insert(out, row("几何位置",
string.format("日地距 %.6f AU<br/>均时差 %.2f min · 视半径 %.1f″",
d.dist_au, d.eq_time, d.radius_arcsec), "#f6f8fa"))
table.insert(out, row("节气状态",
string.format("当前:%s(%s)<br/>下一节气:%s(%s)",
d.cur_term, d.days_after_term_hm, d.next_term, d.next_term_time)))
table.insert(out, row("回归黄道宫",
string.format("%s · 入度 %.2f°<br/>距下一宫约 %.1f 天",
d.trop_sign, d.trop_in_deg, d.trop_days_to_next), "#f6f8fa"))
table.insert(out, row("恒星黄道宫",
string.format("%s · 入度 %.2f°<br/>岁差偏移 %.2f°",
d.sidereal_sign, d.sidereal_in_deg, d.sidereal_ayanamsa)))
table.insert(out, row("真实星座(IAU)",
string.format("%s · 入度 %.2f°<br/><small>%s</small>",
d.iau_constellation, d.iau_in_deg, d.iau_note), "#f6f8fa"))
table.insert(out, row("中国十二次",
string.format("%s · 辰在%s · 入度 %.2f°",
d.xing_ci, d.chen, d.xingci_in_deg)))
table.insert(out, '</table>')
table.insert(out, '</div>')
return table.concat(out)
end
-- 公开接口
function p.get(frame)
local d = compute(frame.args)
local key = frame.args.param or frame.args[1]
if key and d[key] ~= nil then
if type(d[key]) == "number" then return round(d[key], 5) end
return tostring(d[key])
end
return "未知参数:" .. tostring(key)
end
function p.getJD(frame) return tostring(compute(frame.args).jd) end
function p.infoBox(frame) return renderBox(compute(frame.args)) end
-- 带节气信息的简洁文本
function p.short(frame)
local d = compute(frame.args)
return string.format("太阳在【%s】宿,距度 %.2f°,去极度 %.2f°;当前节气:%s(%s);下一节气:%s(%.2f天);黄道宫:%s;十二次:%s",
d.sun_xiu, d.sun_xiu_in_deg, d.sun_xiu_quji, d.cur_term, d.days_after_term_hm,
d.next_term, d.days_to_next_term, d.trop_sign, d.xing_ci)
end
-- 不带节气信息的极简文本(供 SunJQ 等模板使用)
function p.brief(frame)
local d = compute(frame.args)
return string.format("太阳在【%s】宿,距度 %.2f°,去极度 %.2f°;黄道宫:%s;十二次:%s",
d.sun_xiu, d.sun_xiu_in_deg, d.sun_xiu_quji, d.trop_sign, d.xing_ci)
end
function p.solarTermTable(frame)
local year=tonumber(frame.args.year) or tonumber(os.date("%Y"))
local out={'{| class="wikitable"\n! 序号 !! 节气 !! 黄经 !! 日期时间 (UTC+8)'}
for _,lon in ipairs(TERM_LONS) do
local anchor=jdFromDate(year,12,21,0)
local offset=(lon-270)/15
local guess=anchor+offset*15.218
local tjd=refineTerm(guess,lon)
local jdLocal=tjd+8/24
local yy,mm,dd,hh,mi=dateFromJd(jdLocal)
local ds=string.format("%04d-%02d-%02d %02d:%02d",yy,mm,dd,hh,mi)
table.insert(out,string.format("\n|-\n| %d || %s || %.0f° || %s",(lon/15)+1,TERM_NAMES[lon],lon,ds))
end
table.insert(out,"\n|}")
return table.concat(out)
end
function p.zodiac(frame)
local d=compute(frame.args)
local out={'{| class="wikitable"\n! 体系 !! 名称 !! 入度 !! 备注'}
table.insert(out,string.format("\n|-\n| 回归黄道宫 || %s || %.2f° || 春分点为0°",d.trop_sign,d.trop_in_deg))
table.insert(out,string.format("\n|-\n| 恒星黄道宫 || %s || %.2f° || 岁差偏移 %.2f°",d.sidereal_sign,d.sidereal_in_deg,d.sidereal_ayanamsa))
table.insert(out,string.format("\n|-\n| IAU真实星座 || %s || %.2f° || %s",d.iau_constellation,d.iau_in_deg,d.iau_note))
table.insert(out,string.format("\n|-\n| 中国十二次 || %s(辰在%s) || %.2f° || 起于星纪",d.xing_ci,d.chen,d.xingci_in_deg))
table.insert(out,string.format("\n|-\n| 二十八宿日躔 || %s || %.2f° || 体系:%s",d.sun_xiu,d.sun_xiu_in_deg,d.sun_xiu_sysname))
table.insert(out,"\n|}")
return table.concat(out)
end
-- 供 {{节气状态}} 模板一次性取完整文案,避免多次 #invoke 时间快照不一致
function p.termStatus(frame)
local d = compute(frame.args)
local prevHM = d.days_after_term_hm -- 已经是 "N天 H时MM分"
local toNext = math.floor(d.days_to_next_term + 0.5) -- 四舍五入,与显示对齐
return string.format("%s%s,距离%s还有%d天",
d.cur_term, prevHM, d.next_term, toNext)
end
return p