diff --git a/src/analyze.ts b/src/analyze.ts index bd11754..056a367 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -61,9 +61,10 @@ export function analyzeChart(args: { fourPillars: FourPillarsDetail; normalizedBirth: NormalizedBirth; normalizedInput: NormalizedInput; - currentYear: number; + currentDate: { year: number; month: number; day: number }; }): SajuAnalysis { - const { fourPillars, normalizedBirth, normalizedInput, currentYear } = args; + const { fourPillars, normalizedBirth, normalizedInput, currentDate } = args; + const currentYear = currentDate.year; const yearPillar = buildPillarDetail(fourPillars.year.heavenlyStem, fourPillars.year.earthlyBranch); const monthPillar = buildPillarDetail(fourPillars.month.heavenlyStem, fourPillars.month.earthlyBranch); @@ -164,7 +165,7 @@ export function analyzeChart(args: { hour: { stem: hourPillar.stem, branch: hourPillar.branch }, }); - const currentAge = currentYear - normalizedBirth.solar.year + 1; + const currentAge = calculateInternationalAge(normalizedBirth.solar, currentDate); const daeun = calculateDaeun({ yearStem: yearPillar.stem, @@ -175,7 +176,7 @@ export function analyzeChart(args: { birthCalculation: normalizedBirth.calculation, dayStem, dayBranch, - currentYear, + currentDate, }); const seyun = calculateSeyun(currentYear, dayStem); @@ -654,7 +655,7 @@ function calculateDaeun(args: { birthCalculation: { year: number; month: number; day: number; hour: number; minute: number }; dayStem: string; dayBranch: string; - currentYear: number; + currentDate: { year: number; month: number; day: number }; }): { startAge: number; startAgePrecise: number; @@ -717,7 +718,7 @@ function calculateDaeun(args: { }); } - const currentAge = args.currentYear - args.birthSolar.year + 1; + const currentAge = calculateInternationalAge(args.birthSolar, args.currentDate); let current: DaeunItem | null = null; for (const d of list) { if (currentAge >= d.startAge && currentAge <= d.endAge) { @@ -740,6 +741,16 @@ function calculateDaeun(args: { }; } +function calculateInternationalAge( + birthDate: { year: number; month: number; day: number }, + referenceDate: { year: number; month: number; day: number }, +): number { + const birthUtc = Date.UTC(birthDate.year, birthDate.month - 1, birthDate.day); + const referenceUtc = Date.UTC(referenceDate.year, referenceDate.month - 1, referenceDate.day); + const diffDays = Math.trunc((referenceUtc - birthUtc) / DAY_IN_MS); + return Math.floor(diffDays / 365.25); +} + function calculateSeyun(centerYear: number, dayStem: string, count = 10): SeyunItem[] { const out: SeyunItem[] = []; const half = Math.floor(count / 2); diff --git a/src/calculate.ts b/src/calculate.ts index 25d1fee..c09bf3c 100644 --- a/src/calculate.ts +++ b/src/calculate.ts @@ -9,7 +9,7 @@ import { generateMarkdownSummary, generateCompactText } from "./format.ts"; import { calculateFourPillars, - getKstNowYear, + getKstNowDate, buildReferenceCodes, lunarToSolar, normalizeBirthDate, @@ -48,16 +48,16 @@ export function calculateSaju(input: SajuInput): SajuResult { minute: calcBirth.minute, }); - const currentYear = getKstNowYear(normalizedInput.now); + const refNow = normalizedInput.now ? new Date(normalizedInput.now) : new Date(); + const currentDate = getKstNowDate(refNow); const analysis = analyzeChart({ fourPillars, normalizedBirth, normalizedInput, - currentYear, + currentDate, }); - const refNow = normalizedInput.now ? new Date(normalizedInput.now) : new Date(); const reference = buildReferenceCodes(refNow); const result: SajuResult = { diff --git a/src/format.ts b/src/format.ts index 661d83a..4d505d7 100644 --- a/src/format.ts +++ b/src/format.ts @@ -170,7 +170,7 @@ function countElementsByStemBranch(data: SajuResult): { stem: Record r.desc).join("; ") : "없음"; const hiddenByPillar = formatHiddenStemsByPillar(data); @@ -324,7 +324,7 @@ export function generateMarkdownSummary(data: SajuResult): string { export function generateCompactText(data: SajuResult): string { const lines: string[] = []; - const currentYear = data.currentAge + data.solar.year - 1; + const currentYear = Number(data.reference.now.slice(0, 4)); const dayDetail = data.pillarDetails.day; const pillarKeys = PILLAR_KEYS; const strengthChar = data.advanced.dayStrength.strength === "strong" ? "강" : data.advanced.dayStrength.strength === "weak" ? "약" : "중"; diff --git a/src/manse.ts b/src/manse.ts index fa18b69..500d2a6 100644 --- a/src/manse.ts +++ b/src/manse.ts @@ -282,9 +282,10 @@ function getTimezoneStandardLongitude(timeZone: string, referenceDate: Date): nu return standardOffset / 4; } -export function getKstNowYear(reference?: Date): number { +export function getKstNowDate(reference?: Date): { year: number; month: number; day: number } { const now = reference ? new Date(reference) : new Date(); - return formatInTimeZone(now, KOREA_TIMEZONE).year; + const kst = formatInTimeZone(now, KOREA_TIMEZONE); + return { year: kst.year, month: kst.month, day: kst.day }; } // ============================= diff --git a/tests/calculateSaju.test.ts b/tests/calculateSaju.test.ts index 553cd04..2db832e 100644 --- a/tests/calculateSaju.test.ts +++ b/tests/calculateSaju.test.ts @@ -42,8 +42,8 @@ test("golden case: 1992-10-24 05:30 solar", () => { assert(result.dayBranch === "酉", "day branch should be 酉"); assert(result.advanced.geukguk === "종왕격", "geukguk should be 종왕격"); assertEquals(result.advanced.yongsin, ["庚", "甲", "丁"], "yongsin mismatch"); - assert(result.currentAge === 35, "currentAge should be 35 when now is 2026"); - assert(result.daeun.current?.age_range === "35", "current daeun should start at age 35"); + assert(result.currentAge === 33, "currentAge should be 33 when now is 2026"); + assert(result.daeun.current?.age_range === "25", "current daeun should start at age 25"); assertEquals( result.gongmang.branches, @@ -427,11 +427,26 @@ test("toCompact should contain correct pillar data", () => { assert(compact.includes("酉(유)금-"), "compact should contain day branch with element and yinyang"); assert(compact.includes("격: 종왕격"), "compact should contain geukguk"); assert(compact.includes("공망 戌(술) 亥(해)"), "compact should contain gongmang values"); - assert(compact.includes("만 35세"), "compact should contain current age"); + assert(compact.includes("만 33세"), "compact should contain current age"); assert(compact.includes("## 세운 2026 기준"), "compact seyun should reference current year"); assert(compact.includes("★2026"), "compact seyun should mark current year"); }); +test("currentAge should follow floor(day_diff/365.25): 1998-02-22 -> 28", () => { + const result = calculateSaju({ + year: 1998, + month: 2, + day: 22, + hour: 0, + minute: 0, + gender: "남", + calendar: "solar", + now: new Date("2026-03-04T00:00:00+09:00"), + }); + + assert(result.currentAge === 28, "currentAge should be 28 for 1998-02-22 at 2026-03-04 KST"); +}); + test("daeun should be solar-term-time based by default", () => { const result = calculateSaju({ year: 1970,