モジュール:GengoConvert
ナビゲーションに移動
検索に移動
-- Module:DateConvHighPrecision
-- 新暦 -> 旧暦(太陰太陽暦) 高精度変換 + 表示ルール統合
-- 主要出典: Jean Meeus (新月・太陽位置近似), Espenak & Meeus (ΔT 多項式), 中国暦ルール(閏月検出)
-- 注意: 非常に多くの計算(新月計算・節気判定)を含むため、年ごとなどでキャッシュ推奨。
local p = {}
local pi = math.pi
local sin, cos = math.sin, math.cos
local floor = math.floor
local abs = math.abs
local function to_rad(d) return d * pi / 180 end
local function to_deg(r) return r * 180 / pi end
local function fix_angle(a) a = a % 360 if a < 0 then a = a + 360 end return a end
local function norm180(a) a = (a+180) % 360 - 180; return a end
-- ====== 日付 <-> JD (0h UT) ======
local function jd_from_date(y,m,d)
if m <= 2 then y = y - 1; m = m + 12 end
local A = floor(y/100)
local B = 2 - A + floor(A/4)
local jd = floor(365.25*(y + 4716)) + floor(30.6001*(m+1)) + d + B - 1524.5
return jd
end
local function date_from_jd(jd)
local Z = floor(jd + 0.5)
local F = (jd + 0.5) - Z
local A = Z
if Z >= 2299161 then
local alpha = floor((Z - 1867216.25)/36524.25)
A = Z + 1 + alpha - floor(alpha/4)
end
local B = A + 1524
local C = floor((B - 122.1)/365.25)
local D = floor(365.25 * C)
local E = floor((B - D)/30.6001)
local day = B - D - 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)
return year, month, math.floor(day + 1e-9)
end
-- ====== ΔT(TT - UT): Espenak & Meeus / NASA 多項式(-1999..+3000) ======
-- 参考: NASA "Polynomial Expressions for Delta T". Espenak & Meeus. :contentReference[oaicite:4]{index=4}
local function delta_t_seconds(year, month)
-- decimal year y as Espenak recommends: y = year + (month - 0.5)/12
local y = year + ( (month or 6) - 0.5 )/12
if y < -500 then
local u = (y - 1820)/100
return -20 + 32 * (u * u)
end
if y < 500 then
local u = y/100
return 10583.6 - 1014.41*u + 33.78311*u^2 - 5.952053*u^3 - 0.1798452*u^4 + 0.022174192*u^5 + 0.0090316521*u^6
end
if y < 1600 then
local u = (y - 1000)/100
return 1574.2 - 556.01*u + 71.23472*u^2 + 0.319781*u^3 - 0.8503463*u^4 - 0.005050998*u^5 + 0.0083572073*u^6
end
if y < 1700 then
local t = y - 1600
return 120 - 0.9808*t - 0.01532*t^2 + t^3/7129
end
if y < 1800 then
local t = y - 1700
return 8.83 + 0.1603*t - 0.0059285*t^2 + 0.00013336*t^3 - t^4/1174000
end
if y < 1860 then
local t = y - 1800
return 13.72 - 0.332447*t + 0.0068612*t^2 + 0.0041116*t^3 - 0.00037436*t^4 + 0.0000121272*t^5 - 0.0000001699*t^6 + 0.000000000875*t^7
end
if y < 1900 then
local t = y - 1860
return 7.62 + 0.5737*t - 0.251754*t^2 + 0.01680668*t^3 - 0.0004473624*t^4 + t^5/233174
end
if y < 1920 then
local t = y - 1900
return -2.79 + 1.494119*t - 0.0598939*t^2 + 0.0061966*t^3 - 0.000197*t^4
end
if y < 1941 then
local t = y - 1920
return 21.20 + 0.84493*t - 0.076100*t^2 + 0.0020936*t^3
end
if y < 1961 then
local t = y - 1950
return 29.07 + 0.407*t - t^2/233 + t^3/2547
end
if y < 1986 then
local t = y - 1975
return 45.45 + 1.067*t - t^2/260 - t^3/718
end
if y < 2005 then
local t = y - 2000
return 63.86 + 0.3345*t - 0.060374*t^2 + 0.0017275*t^3 + 0.000651814*t^4 + 0.00002373599*t^5
end
if y < 2050 then
local t = y - 2000
return 62.92 + 0.32217*t + 0.005589*t^2
end
if y < 2150 then
return -20 + 32 * ((y - 1820)/100)^2 - 0.5628 * (2150 - y)
end
local u = (y - 1820)/100
return -20 + 32 * (u * u)
end
-- convenience: given JDE (TT), compute UT JD by iterating deltaT correction
local function jde_tt_to_jd_ut(jde_tt)
-- iterate: UT = TT - deltaT/86400 -> JDE_TT - deltaT(UTdate)/86400
-- start with approximate date from TT
local y,m,d = date_from_jd(jde_tt)
local dt = delta_t_seconds(y,m)
local jd_ut = jde_tt - dt / 86400
-- one more iteration for accuracy (update y/m from jd_ut)
local y2,m2,d2 = date_from_jd(jd_ut)
local dt2 = delta_t_seconds(y2,m2)
jd_ut = jde_tt - dt2 / 86400
return jd_ut
end
-- ====== 太陽の黄経(高精度近似) ======
-- (Meeus の主要項を用いた近似。実用精度は数秒〜数分)
local function sun_ecliptic_longitude_tt(jde_tt)
local T = (jde_tt - 2451545.0) / 36525
local L0 = 280.4664567 + 36000.76982779 * T + 0.0003032*T*T
local M = 357.52911 + 35999.05029*T - 0.0001537*T*T
local e = 0.016708634 - 0.000042037*T - 0.0000001267*T*T
local C = (1.914602 - 0.004817*T - 0.000014*T*T)*sin(to_rad(M))
+ (0.019993 - 0.000101*T)*sin(to_rad(2*M))
+ 0.000289*sin(to_rad(3*M))
local true_long = L0 + C
return fix_angle(true_long)
end
-- ====== 高精度新月計算(Meeus の式 + 多項) ======
-- k: integer (new moon index)
local function new_moon_jde(k)
local kf = k
local T = kf / 1236.85
local T2 = T*T; local T3 = T2*T; local T4 = T3*T
local JDE = 2451550.09765 + 29.530588853 * kf + 0.0001337 * T2 - 0.000000150 * T3 + 0.00000000073 * T4
-- periodic terms (Meeus Table) -- many主要項を採用
local M = 2.5534 + 29.10535670 * kf - 0.0000014 * T2 - 0.00000011 * T3
local Mprime = 201.5643 + 385.81693528 * kf + 0.0107582 * T2 + 0.00001238 * T3 - 0.000000058 * T4
local F = 160.7108 + 390.67050284 * kf - 0.0016118 * T2 - 0.00000227 * T3 + 0.000000011 * T4
local Omega = 124.7746 - 1.56375580 * kf + 0.0020691 * T2 + 0.00000215 * T3
local Mr = to_rad(M)
local Mpr = to_rad(Mprime)
local Fr = to_rad(F)
local Or = to_rad(Omega)
local E = 1 - 0.002516 * T - 0.0000074 * T2
local delta = -0.40720 * sin(Mpr)
+ 0.17241 * E * sin(Mr)
+ 0.01608 * sin(2*Mpr)
+ 0.01039 * sin(2*Fr)
+ 0.00739 * E * sin(Mpr - Mr)
- 0.00514 * E * sin(Mpr + Mr)
+ 0.00208 * E * E * sin(2*Mr)
- 0.00111 * sin(Mpr - 2*Fr)
- 0.00057 * sin(Mpr + 2*Fr)
+ 0.00056 * E * sin(2*Mpr + Mr)
- 0.00042 * sin(3*Mpr)
+ 0.00042 * E * sin(Mr + 2*Fr)
+ 0.00038 * E * sin(Mr - 2*Fr)
- 0.00024 * E * sin(2*Mpr - Mr)
- 0.00017 * sin(Or)
- 0.00007 * sin(Mpr + 2*Mr)
+ 0.00004 * sin(2*Mpr - 2*Fr)
+ 0.00004 * sin(3*Mr)
+ 0.00003 * sin(Mpr + Fr)
+ 0.00003 * sin(Mpr - Fr)
- 0.00003 * sin(Mpr + Mr + 2*Fr)
+ 0.00003 * sin(Mpr - Mr + 2*Fr)
- 0.00002 * sin(Mpr - Mr - 2*Fr)
- 0.00002 * sin(3*Mpr + Mr)
+ 0.00002 * sin(4*Mpr)
-- time of new moon in JDE (Terrestrial Time)
local jde_tt = JDE + delta
return jde_tt
end
-- find k such that new_moon(k) <= jde_tt < new_moon(k+1)
local function find_new_moon_k_for_jd(jd_tt)
local k = floor((jd_tt - 2451550.09765) / 29.530588853)
-- search nearby
for dk = -3, 3 do
local kk = k + dk
local j1 = new_moon_jde(kk)
local j2 = new_moon_jde(kk+1)
if j1 <= jd_tt and jd_tt < j2 then return kk, j1 end
end
-- broad search
for dk = -50, 50 do
local kk = k + dk
local j1 = new_moon_jde(kk)
local j2 = new_moon_jde(kk+1)
if j1 <= jd_tt and jd_tt < j2 then return kk, j1 end
end
return nil, nil
end
-- ====== 中気(principal term)判定(高精度) ======
-- ある太陰月 (nm_jd_tt .. nm_next_jd_tt) の間に major solar term (0,30,60,..330 deg) があるかを判定。
-- 見つかったかどうか・またその角度に対応する root 時刻を返す。
-- 判定は「角度差の符号変化」を見て、二分法で根を求める(高精度)
local function sun_longitude_tt_at(jde_tt)
return sun_ecliptic_longitude_tt(jde_tt)
end
local function find_solar_longitude_crossing(target_deg, a_jd_tt, b_jd_tt)
-- normalize target into [0,360)
target_deg = fix_angle(target_deg)
-- sample endpoints
local fa = norm180(sun_longitude_tt_at(a_jd_tt) - target_deg)
local fb = norm180(sun_longitude_tt_at(b_jd_tt) - target_deg)
-- if sign change, find root via bisection
if fa == 0 then return a_jd_tt end
if fb == 0 then return b_jd_tt end
if fa * fb > 0 then return nil end
local lo, hi = a_jd_tt, b_jd_tt
local FLO, FHI = fa, fb
for i = 1, 60 do
local mid = (lo + hi) / 2
local fm = norm180(sun_longitude_tt_at(mid) - target_deg)
if fm == 0 then return mid end
if FLO * fm <= 0 then
hi, FHI = mid, fm
else
lo, FLO = mid, fm
end
if math.abs(hi - lo) < 1e-7 then break end -- ~0.0086 sec
end
return (lo + hi) / 2
end
-- 判断: 太陰月に中気(major solar term)があるか
local function month_has_principal_term(nm_jde_tt, nm_next_jde_tt)
-- check for any k such that sun longitude crosses k*30° between nm and nm_next
for k = 0, 11 do
local target = k * 30
local cross = find_solar_longitude_crossing(target, nm_jde_tt, nm_next_jde_tt)
if cross then
return true, cross, target
end
end
return false
end
-- ====== 閏月検出: month11_jde_k を基準に年度の全新月を列挙して閏月の k を決める ======
-- ルール (中国暦準拠):
-- - 11月 (winter solstice を含む月) を基準に次の 11 月までの新月列を数える
-- - 13 個の新月があればその中に閏月がある → 最初の「中気を持たない月(principal term が無い)」が閏月
local function detect_leap_k_for_lunar_year(k11)
-- collect k list from k11 .. up to first k where month index wraps back (we need next k11)
local klist = {}
local k = k11
-- collect up to 16 months to be safe
for i = 1, 16 do
table.insert(klist, k)
k = k + 1
end
-- find next month11: the one whose sun longitude at its middle is near winter solstice sector 0?? but easier:
-- We'll compute principal-term presence for each month in a span of 14 months and count.
local no_principal = {}
for idx,k in ipairs(klist) do
local nm = new_moon_jde(k)
local nm2 = new_moon_jde(k+1)
local has_pt = month_has_principal_term(nm, nm2)
if not has_pt then
table.insert(no_principal, k)
end
end
-- If there is at least one month without principal term and there are 13 new moons in the lunar year,
-- the leap month is the first month without principal term.
-- For robust detection, we return first such k if found.
if #no_principal >= 1 then
return no_principal[1]
end
return nil
end
-- ====== Gregorian -> Lunar (高精度) ======
-- 戻り: table {lyear, lmonth, lday, isLeap, nm_start_jde, nm_end_jde}
local function convert_to_lunar_high(gy, gm, gd)
-- compute JDE (TT) for 0h UT of given date: use jd_from_date (which gives JD UT for 0h),
-- then add deltaT to get TT: JDE_TT = JD_UT + deltaT(UT)/86400
local jd_ut = jd_from_date(gy, gm, gd)
-- approximate deltaT using month for the central month
local dt = delta_t_seconds(gy, gm)
local jde_tt = jd_ut + dt / 86400
-- find new moon k for this jde_tt
local k_curr, nm_curr = find_new_moon_k_for_jd(jde_tt)
if not k_curr then return nil, "new moon not found" end
-- refine nm_curr using a few iterations (new_moon_jde already gives JDE TT)
local nm_next = new_moon_jde(k_curr + 1)
-- lunar day
local lday = floor( (jde_tt - nm_curr) + 1 + 1e-9 )
-- determine lunar year base: find month11 k for gy and depending on k_curr decide lyear
local function month11_k_for_year(gy2)
local jd_ws = jd_from_date(gy2, 12, 21)
-- convert to TT for searching
local dt_ws = delta_t_seconds(gy2, 12)
local jde_ws = jd_ws + dt_ws/86400
local kk, nm = find_new_moon_k_for_jd(jde_ws)
return kk, nm
end
local k11_curr, _ = month11_k_for_year(gy)
local k11_next, _ = month11_k_for_year(gy+1)
local base_k11, base_lyear
if k_curr >= k11_curr then
base_k11 = k11_curr; base_lyear = gy
else
base_k11, _ = month11_k_for_year(gy-1); base_lyear = gy - 1
end
-- detect leap month k for this lunar year
local leap_k = detect_leap_k_for_lunar_year(base_k11)
-- iterate months from base_k11 to k_curr and compute month numbers
local k = base_k11
local mnum = 11 -- first month is 11
while true do
local thisIsLeap = (leap_k and k == leap_k)
if k == k_curr then
-- found
local lmonth = mnum
local lyear = base_lyear
while lmonth > 12 do lmonth = lmonth - 12; lyear = lyear + 1 end
return {
lyear = lyear,
lmonth = lmonth,
lday = lday,
isLeap = thisIsLeap,
nm_start_jde = nm_curr,
nm_end_jde = nm_next
}
end
-- advance k and month counter; if current is leap then month number repeats for next interval
if thisIsLeap then
-- after a leap month, increment month number
mnum = mnum + 1
k = k + 1
else
mnum = mnum + 1
k = k + 1
end
end
return nil, "cannot map lunar"
end
-- ====== 表示用小関数(あなたの既定ルール) ======
-- 元号テーブル(簡易): 主要元号のみ(必要なら拡張)
local eras = {
{name="元和", start=16150905},
{name="寛永", start=16240417},
{name="正保", start=16450113},
{name="慶安", start=16480407},
{name="承応", start=16521020},
{name="明暦", start=16550518},
{name="万治", start=16580821},
{name="寛文", start=16610523},
{name="延宝", start=16731030},
{name="天和", start=16811109},
{name="貞享", start=16840405},
{name="元禄", start=16881023},
{name="宝永", start=17040416},
{name="正徳", start=17110611},
{name="享保", start=17160809},
{name="元文", start=17360607},
{name="寛保", start=17410412},
{name="延享", start=17440403},
{name="寛延", start=17480805},
{name="宝暦", start=17511214},
{name="明和", start=17640630},
{name="安永", start=17721210},
{name="天明", start=17810425},
{name="寛政", start=17890219},
{name="享和", start=18010319},
{name="文化", start=18040322},
{name="文政", start=18180526},
{name="天保", start=18310123},
{name="弘化", start=18450109},
{name="嘉永", start=18480401},
{name="安政", start=18550115},
{name="万延", start=18600408},
{name="文久", start=18610329},
{name="元治", start=18640327},
{name="慶応", start=18650501},
{name="明治", start=18680125},
{name="大正", start=19120730},
{name="昭和", start=19261225},
{name="平成", start=19890108},
{name="令和", start=20190501},
}
local function ymd_int(y,m,d) return y*10000 + (m or 1)*100 + (d or 1) end
local function wareki_year(y,m,d)
local yi = ymd_int(y,m or 1,d or 1)
for i = #eras, 1, -1 do
if yi >= eras[i].start then
local sy = floor(eras[i].start / 10000)
local ey = y - sy + 1
return eras[i].name .. ((ey == 1) and "元" or tostring(ey))
end
end
return ""
end
-- input parsing (same as before)
local function trim(s) return (s and s:gsub("^%s+", ""):gsub("%s+$", "")) or s end
local function parse_date_tokens(s)
if not s then return nil end
s = trim(s)
local a,b = s:match("^(.-)から(.-)$")
local sep = "から"
if not a then a,b = s:match("^(.-)/(.+)$"); sep = "/" end
if a and b then
return { is_range = true, sep = sep, parts = { parse_date_tokens(a), parse_date_tokens(b) } }
end
local year_token = s:match("(%[%[%d%d%d%d年%]%])") or s:match("(%d%d%d%d年)") or s:match("(%d%d%d%d)")
local year = year_token and tonumber(year_token:match("%d%d%d%d")) or nil
local md_token = s:match("(%[%[%d+月%d+日%]%])") or s:match("(%d+月%d+日)") or s:match("(%[%[%d+月%]%])") or s:match("(%d+月)")
local m,d
if md_token then
local mm,dd = md_token:match("(%d+)月(%d+)日")
if mm then m = tonumber(mm) end
if dd then d = tonumber(dd) end
end
return { is_range = false, year = year, month = m, day = d, year_token = year_token, month_token = md_token, raw = s }
end
local function year_label(t) return t.year_token or (t.year and (t.year.."年")) or "" end
local function md_label(t) return t.month_token or (t.month and (t.month.."月"..(t.day and (t.day.."日") or "")) or "") end
local function is_old_target(t) return t and t.year and t.month and t.day and t.year <= 1872 end
local function is_post_gregorian(t) return t and t.year and (t.year > 1872 or (t.year == 1873 and (t.month or 1) >= 1)) end
-- decorator single
local function decorate_single(t)
if not t then return "" end
-- 1873年以降: yyyy年(和暦年)mm月dd日 <-- 月日は外に
if is_post_gregorian(t) and t.month and t.day then
return string.format("%s(%s年)%s", year_label(t), wareki_year(t.year, t.month, t.day), md_label(t))
end
-- 1872年以前: 新暦 -> 旧暦変換(高精度)を行い、括弧内は和暦年+旧暦月日(閏月表記)
if is_old_target(t) then
local lunar, err = convert_to_lunar_high(t.year, t.month, t.day)
if lunar then
local wtxt = wareki_year(lunar.lyear, lunar.lmonth, lunar.lday)
if lunar.isLeap then
return string.format("%s%s(%s年閏%d月%d日)", year_label(t), md_label(t), wtxt, lunar.lmonth, lunar.lday)
else
return string.format("%s%s(%s年%d月%d日)", year_label(t), md_label(t), wtxt, lunar.lmonth, lunar.lday)
end
else
return year_label(t) .. md_label(t) .. "(旧暦計算失敗)"
end
end
-- 年のみ or 年月のみ: 表示しない(換算しない)
return year_label(t) .. (t.month and (t.month.."月") or "")
end
-- range decorator (left/right individually decorated)
local function decorate_range(parsed)
local a,b = parsed.parts[1], parsed.parts[2]
if not b.year then b.year = a.year end
local left = (a.year and a.month and a.day) and decorate_single(a) or (year_label(a) .. (a.month and (a.month.."月") or ""))
local right = (b.year and b.month and b.day) and decorate_single(b) or (year_label(b) .. (b.month and (b.month.."月") or ""))
return left .. parsed.sep .. right
end
-- public convert
function p.convert(frame)
local input = frame.args[1] or frame.args.date or frame.args["1"]
if not input then return "" end
local parsed = parse_date_tokens(input)
if not parsed then return "" end
if not parsed.is_range then
return decorate_single(parsed)
else
return decorate_range(parsed)
end
end
return p