モジュール:GengoConvert

出典: 謎の百科事典もどき『エンペディア(Enpedia)』
ナビゲーションに移動 検索に移動

ここに呼び出す説明文 『 モジュール:GengoConvert/doc 』 が作成されていません。

-- 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