Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -175,7 +176,7 @@ export function analyzeChart(args: {
birthCalculation: normalizedBirth.calculation,
dayStem,
dayBranch,
currentYear,
currentDate,
});

const seyun = calculateSeyun(currentYear, dayStem);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/calculate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { generateMarkdownSummary, generateCompactText } from "./format.ts";
import {
calculateFourPillars,
getKstNowYear,
getKstNowDate,
buildReferenceCodes,
lunarToSolar,
normalizeBirthDate,
Expand Down Expand Up @@ -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 = {
Expand Down
4 changes: 2 additions & 2 deletions src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function countElementsByStemBranch(data: SajuResult): { stem: Record<string, num

export function generateMarkdownSummary(data: SajuResult): string {
let md = "";
const currentYear = data.currentAge + data.solar.year - 1;
const currentYear = Number(data.reference.now.slice(0, 4));
const genderText = data.input.gender === "남" ? "남성" : "여성";
const stemRelationText = data.stemRelations.length ? data.stemRelations.map((r) => r.desc).join("; ") : "없음";
const hiddenByPillar = formatHiddenStemsByPillar(data);
Expand Down Expand Up @@ -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" ? "약" : "중";
Expand Down
5 changes: 3 additions & 2 deletions src/manse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

// =============================
Expand Down
21 changes: 18 additions & 3 deletions tests/calculateSaju.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down