@gracefullight/saju 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +314 -28
- package/README.md +314 -28
- package/dist/__tests__/four-pillars.test.js +52 -40
- package/dist/__tests__/luck.test.d.ts +2 -0
- package/dist/__tests__/luck.test.d.ts.map +1 -0
- package/dist/__tests__/luck.test.js +33 -0
- package/dist/__tests__/lunar.test.d.ts +2 -0
- package/dist/__tests__/lunar.test.d.ts.map +1 -0
- package/dist/__tests__/lunar.test.js +83 -0
- package/dist/__tests__/relations.test.d.ts +2 -0
- package/dist/__tests__/relations.test.d.ts.map +1 -0
- package/dist/__tests__/relations.test.js +90 -0
- package/dist/__tests__/saju.test.d.ts +2 -0
- package/dist/__tests__/saju.test.d.ts.map +1 -0
- package/dist/__tests__/saju.test.js +133 -0
- package/dist/__tests__/solar-terms.test.d.ts +2 -0
- package/dist/__tests__/solar-terms.test.d.ts.map +1 -0
- package/dist/__tests__/solar-terms.test.js +121 -0
- package/dist/__tests__/strength.test.d.ts +2 -0
- package/dist/__tests__/strength.test.d.ts.map +1 -0
- package/dist/__tests__/strength.test.js +44 -0
- package/dist/__tests__/ten-gods.test.d.ts +2 -0
- package/dist/__tests__/ten-gods.test.d.ts.map +1 -0
- package/dist/__tests__/ten-gods.test.js +119 -0
- package/dist/__tests__/yongshen.test.d.ts +2 -0
- package/dist/__tests__/yongshen.test.d.ts.map +1 -0
- package/dist/__tests__/yongshen.test.js +62 -0
- package/dist/core/four-pillars.d.ts +2 -0
- package/dist/core/four-pillars.d.ts.map +1 -1
- package/dist/core/four-pillars.js +7 -4
- package/dist/core/luck.d.ts +41 -0
- package/dist/core/luck.d.ts.map +1 -0
- package/dist/core/luck.js +96 -0
- package/dist/core/lunar.d.ts +13 -0
- package/dist/core/lunar.d.ts.map +1 -0
- package/dist/core/lunar.js +24 -0
- package/dist/core/relations.d.ts +94 -0
- package/dist/core/relations.d.ts.map +1 -0
- package/dist/core/relations.js +305 -0
- package/dist/core/solar-terms.d.ts +155 -0
- package/dist/core/solar-terms.d.ts.map +1 -0
- package/dist/core/solar-terms.js +266 -0
- package/dist/core/strength.d.ts +18 -0
- package/dist/core/strength.d.ts.map +1 -0
- package/dist/core/strength.js +255 -0
- package/dist/core/ten-gods.d.ts +130 -0
- package/dist/core/ten-gods.d.ts.map +1 -0
- package/dist/core/ten-gods.js +335 -0
- package/dist/core/yongshen.d.ts +20 -0
- package/dist/core/yongshen.d.ts.map +1 -0
- package/dist/core/yongshen.js +216 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -0
- package/package.json +15 -12
package/README.md
CHANGED
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
- **태양시 보정** - 경도 기반 평균 태양시 조정 옵션
|
|
16
16
|
- **트리쉐이킹 지원** - 필요한 것만 import
|
|
17
17
|
- **완전한 타입 지원** - TypeScript 정의 완비
|
|
18
|
-
- **풍부한 테스트** -
|
|
18
|
+
- **풍부한 테스트** - 180개 이상 테스트, 91% 이상 커버리지
|
|
19
|
+
- **십신 분석** - 장간(藏干)을 포함한 상세 십신 및 오행 분포 분석
|
|
20
|
+
- **신강/신약 판정** - 월령 득령(得令), 통근(通根), 투간(透干), 본중여기(本中餘氣) 가중치를 고려한 9단계 신강도 분석
|
|
21
|
+
- **합충형파해** - 천간합, 육합, 삼합, 방합 및 충, 해, 형, 파 분석. 합(合)과 화(化) 성립 조건 분리 표기
|
|
22
|
+
- **대운/세운 계산** - 절기(節氣) 기반 정확한 기운(起運) 계산, 성별 및 연간 음양을 고려한 대운 및 연도별 세운 계산
|
|
23
|
+
- **용신 추출** - 격국(格局), 억부(抑扶), 조후(調候) 순서로 용신 추천 및 개운법 가이드
|
|
24
|
+
- **절기 분석** - 현재/다음 절기 정보 및 경과일 계산
|
|
19
25
|
|
|
20
26
|
## 사주(四柱)란?
|
|
21
27
|
|
|
@@ -56,6 +62,39 @@ pnpm add date-fns date-fns-tz
|
|
|
56
62
|
|
|
57
63
|
## 빠른 시작
|
|
58
64
|
|
|
65
|
+
```typescript
|
|
66
|
+
import { DateTime } from "luxon";
|
|
67
|
+
import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
|
|
68
|
+
import { getSaju, STANDARD_PRESET } from "@gracefullight/saju";
|
|
69
|
+
|
|
70
|
+
const adapter = await createLuxonAdapter();
|
|
71
|
+
|
|
72
|
+
const birthDateTime = DateTime.fromObject(
|
|
73
|
+
{ year: 2000, month: 1, day: 1, hour: 18, minute: 0 },
|
|
74
|
+
{ zone: "Asia/Seoul" }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// getSaju: 사주 팔자, 십신, 신강약, 합충, 용신, 절기, 대운, 세운을 한 번에 계산
|
|
78
|
+
const result = getSaju(adapter, birthDateTime, {
|
|
79
|
+
longitudeDeg: 126.9778,
|
|
80
|
+
gender: "male", // 필수: 대운 계산에 필요
|
|
81
|
+
preset: STANDARD_PRESET,
|
|
82
|
+
currentYear: 2024, // 세운 기본 범위 계산용 (선택)
|
|
83
|
+
yearlyLuckRange: { from: 2024, to: 2030 }, // 세운 범위 직접 지정 (선택)
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log(result.pillars); // { year: "己卯", month: "丙子", ... }
|
|
87
|
+
console.log(result.tenGods); // 십신 및 장간 분석
|
|
88
|
+
console.log(result.strength); // 신강/신약 판정 (예: "신약")
|
|
89
|
+
console.log(result.relations); // 합충형파해 분석
|
|
90
|
+
console.log(result.yongShen); // 용신 및 개운법
|
|
91
|
+
console.log(result.solarTerms); // 절기 정보 (현재/다음 절기, 경과일)
|
|
92
|
+
console.log(result.majorLuck); // 대운 정보
|
|
93
|
+
console.log(result.yearlyLuck); // 세운 정보
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 사주 팔자만 계산하기
|
|
97
|
+
|
|
59
98
|
```typescript
|
|
60
99
|
import { DateTime } from "luxon";
|
|
61
100
|
import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
|
|
@@ -74,18 +113,6 @@ const result = getFourPillars(adapter, birthDateTime, {
|
|
|
74
113
|
});
|
|
75
114
|
|
|
76
115
|
console.log(result);
|
|
77
|
-
// {
|
|
78
|
-
// year: "己卯", // 연주 (천간 + 지지)
|
|
79
|
-
// month: "丙子", // 월주
|
|
80
|
-
// day: "庚辰", // 일주
|
|
81
|
-
// hour: "辛酉", // 시주
|
|
82
|
-
// meta: {
|
|
83
|
-
// solarYear: 1999,
|
|
84
|
-
// sunLonDeg: 280.9,
|
|
85
|
-
// effectiveDayDate: { year: 2000, month: 1, day: 1 },
|
|
86
|
-
// adjustedHour: 18
|
|
87
|
-
// }
|
|
88
|
-
// }
|
|
89
116
|
```
|
|
90
117
|
|
|
91
118
|
## 사용법
|
|
@@ -126,7 +153,7 @@ import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
|
|
|
126
153
|
const adapter = await createDateFnsAdapter();
|
|
127
154
|
|
|
128
155
|
const dt = {
|
|
129
|
-
date: new Date(
|
|
156
|
+
date: new Date(1985, 4, 15, 14, 30), // 주의: 월은 0부터 시작
|
|
130
157
|
timeZone: "Asia/Seoul",
|
|
131
158
|
};
|
|
132
159
|
|
|
@@ -200,12 +227,27 @@ const myAdapter: DateAdapter<MyDateType> = {
|
|
|
200
227
|
}
|
|
201
228
|
```
|
|
202
229
|
|
|
203
|
-
#### 사용 중단된 별칭
|
|
204
|
-
- `presetA` → `STANDARD_PRESET` 사용 권장
|
|
205
|
-
- `presetB` → `TRADITIONAL_PRESET` 사용 권장
|
|
206
|
-
|
|
207
230
|
### 핵심 함수
|
|
208
231
|
|
|
232
|
+
#### `getSaju(adapter, datetime, options)`
|
|
233
|
+
|
|
234
|
+
사주 분석의 모든 결과(팔자, 십신, 신강약, 합충, 용신, 대운)를 한 번에 계산합니다.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
function getSaju<T>(
|
|
238
|
+
adapter: DateAdapter<T>,
|
|
239
|
+
dtLocal: T,
|
|
240
|
+
options: {
|
|
241
|
+
longitudeDeg: number;
|
|
242
|
+
gender: "male" | "female"; // 필수
|
|
243
|
+
tzOffsetHours?: number;
|
|
244
|
+
preset?: typeof STANDARD_PRESET;
|
|
245
|
+
currentYear?: number; // 세운 기본 범위 계산용
|
|
246
|
+
yearlyLuckRange?: { from: number; to: number }; // 세운 범위 직접 지정
|
|
247
|
+
}
|
|
248
|
+
): SajuResult;
|
|
249
|
+
```
|
|
250
|
+
|
|
209
251
|
#### `getFourPillars(adapter, datetime, options)`
|
|
210
252
|
|
|
211
253
|
네 기둥(연주, 월주, 일주, 시주) 모두 계산
|
|
@@ -228,11 +270,17 @@ function getFourPillars<T>(
|
|
|
228
270
|
month: string;
|
|
229
271
|
day: string;
|
|
230
272
|
hour: string;
|
|
273
|
+
lunar: {
|
|
274
|
+
lunarYear: number;
|
|
275
|
+
lunarMonth: number;
|
|
276
|
+
lunarDay: number;
|
|
277
|
+
isLeapMonth: boolean;
|
|
278
|
+
};
|
|
231
279
|
meta: {
|
|
232
|
-
|
|
280
|
+
solarYearUsed: number;
|
|
233
281
|
sunLonDeg: number;
|
|
234
282
|
effectiveDayDate: { year: number; month: number; day: number };
|
|
235
|
-
|
|
283
|
+
adjustedDtForHour: string;
|
|
236
284
|
};
|
|
237
285
|
}
|
|
238
286
|
```
|
|
@@ -245,7 +293,7 @@ function getFourPillars<T>(
|
|
|
245
293
|
- `preset`: 설정 프리셋 (`STANDARD_PRESET` 또는 `TRADITIONAL_PRESET` 사용)
|
|
246
294
|
- `tzOffsetHours`: 타임존 오프셋(시간 단위), 선택사항 (기본값: 9, KST)
|
|
247
295
|
|
|
248
|
-
**반환값:** 연월일시
|
|
296
|
+
**반환값:** 연월일시 기둥, 음력 날짜, 메타데이터를 포함한 객체
|
|
249
297
|
|
|
250
298
|
#### `yearPillar(adapter, datetime)`
|
|
251
299
|
|
|
@@ -291,6 +339,58 @@ function dayPillarFromDate(date: {
|
|
|
291
339
|
}
|
|
292
340
|
```
|
|
293
341
|
|
|
342
|
+
### 음력 변환 함수
|
|
343
|
+
|
|
344
|
+
#### `getLunarDate(year, month, day)`
|
|
345
|
+
|
|
346
|
+
양력(그레고리력) 날짜를 음력 날짜로 변환
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
function getLunarDate(
|
|
350
|
+
year: number,
|
|
351
|
+
month: number,
|
|
352
|
+
day: number
|
|
353
|
+
): {
|
|
354
|
+
lunarYear: number;
|
|
355
|
+
lunarMonth: number;
|
|
356
|
+
lunarDay: number;
|
|
357
|
+
isLeapMonth: boolean;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**예시:**
|
|
362
|
+
```typescript
|
|
363
|
+
import { getLunarDate } from "@gracefullight/saju";
|
|
364
|
+
|
|
365
|
+
const lunar = getLunarDate(2000, 1, 1);
|
|
366
|
+
// { lunarYear: 1999, lunarMonth: 11, lunarDay: 25, isLeapMonth: false }
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### `getSolarDate(lunarYear, lunarMonth, lunarDay, isLeapMonth)`
|
|
370
|
+
|
|
371
|
+
음력 날짜를 양력(그레고리력) 날짜로 변환
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
function getSolarDate(
|
|
375
|
+
lunarYear: number,
|
|
376
|
+
lunarMonth: number,
|
|
377
|
+
lunarDay: number,
|
|
378
|
+
isLeapMonth?: boolean
|
|
379
|
+
): {
|
|
380
|
+
year: number;
|
|
381
|
+
month: number;
|
|
382
|
+
day: number;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**예시:**
|
|
387
|
+
```typescript
|
|
388
|
+
import { getSolarDate } from "@gracefullight/saju";
|
|
389
|
+
|
|
390
|
+
const solar = getSolarDate(1999, 11, 25, false);
|
|
391
|
+
// { year: 2000, month: 1, day: 1 }
|
|
392
|
+
```
|
|
393
|
+
|
|
294
394
|
#### `hourPillar(adapter, datetime, options)`
|
|
295
395
|
|
|
296
396
|
태양시 보정 옵션과 함께 시주만 계산
|
|
@@ -361,6 +461,109 @@ function effectiveDayDate<T>(
|
|
|
361
461
|
}
|
|
362
462
|
```
|
|
363
463
|
|
|
464
|
+
### 분석 함수
|
|
465
|
+
|
|
466
|
+
#### `analyzeTenGods(year, month, day, hour)`
|
|
467
|
+
|
|
468
|
+
사주 팔자의 십신과 지장간을 분석합니다.
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
function analyzeTenGods(
|
|
472
|
+
year: string,
|
|
473
|
+
month: string,
|
|
474
|
+
day: string,
|
|
475
|
+
hour: string
|
|
476
|
+
): FourPillarsTenGods;
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### `analyzeStrength(year, month, day, hour)`
|
|
480
|
+
|
|
481
|
+
사주의 신강/신약을 9단계로 판정합니다.
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
function analyzeStrength(
|
|
485
|
+
year: string,
|
|
486
|
+
month: string,
|
|
487
|
+
day: string,
|
|
488
|
+
hour: string
|
|
489
|
+
): StrengthResult;
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### `analyzeRelations(year, month, day, hour)`
|
|
493
|
+
|
|
494
|
+
천간과 지지의 합, 충, 형, 파, 해 관계를 분석합니다.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
function analyzeRelations(
|
|
498
|
+
year: string,
|
|
499
|
+
month: string,
|
|
500
|
+
day: string,
|
|
501
|
+
hour: string
|
|
502
|
+
): RelationsResult;
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### `calculateMajorLuck(adapter, datetime, gender, year, month)`
|
|
506
|
+
|
|
507
|
+
대운의 흐름과 시작 연령을 계산합니다.
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
function calculateMajorLuck<T>(
|
|
511
|
+
adapter: DateAdapter<T>,
|
|
512
|
+
birthDateTime: T,
|
|
513
|
+
gender: "male" | "female",
|
|
514
|
+
yearPillar: string,
|
|
515
|
+
monthPillar: string
|
|
516
|
+
): MajorLuckResult;
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### `analyzeYongShen(year, month, day, hour)`
|
|
520
|
+
|
|
521
|
+
억부와 조후를 고려하여 용신과 희신을 추출합니다.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
function analyzeYongShen(
|
|
525
|
+
year: string,
|
|
526
|
+
month: string,
|
|
527
|
+
day: string,
|
|
528
|
+
hour: string
|
|
529
|
+
): YongShenResult;
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### `analyzeSolarTerms(adapter, datetime)`
|
|
533
|
+
|
|
534
|
+
현재 및 다음 절기 정보와 경과일을 계산합니다.
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
function analyzeSolarTerms<T>(
|
|
538
|
+
adapter: DateAdapter<T>,
|
|
539
|
+
dtLocal: T
|
|
540
|
+
): SolarTermInfo;
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**반환값:**
|
|
544
|
+
```typescript
|
|
545
|
+
{
|
|
546
|
+
current: { name: "소한", hanja: "小寒", longitude: 285 },
|
|
547
|
+
currentDate: { year: 2024, month: 1, day: 6, hour: 5, minute: 30 },
|
|
548
|
+
daysSinceCurrent: 5,
|
|
549
|
+
next: { name: "대한", hanja: "大寒", longitude: 300 },
|
|
550
|
+
nextDate: { year: 2024, month: 1, day: 20, hour: 12, minute: 15 },
|
|
551
|
+
daysUntilNext: 10
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### `getSolarTermsForYear(adapter, year, timezone)`
|
|
556
|
+
|
|
557
|
+
특정 연도의 24절기 날짜를 모두 계산합니다.
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
function getSolarTermsForYear<T>(
|
|
561
|
+
adapter: DateAdapter<T>,
|
|
562
|
+
year: number,
|
|
563
|
+
timezone: string
|
|
564
|
+
): Array<{ term: SolarTerm; date: {...} }>;
|
|
565
|
+
```
|
|
566
|
+
|
|
364
567
|
## 고급 사용법
|
|
365
568
|
|
|
366
569
|
### 태양시 보정
|
|
@@ -435,6 +638,85 @@ const result = getFourPillars(adapter, dt, {
|
|
|
435
638
|
|
|
436
639
|
## 예제
|
|
437
640
|
|
|
641
|
+
### 대운과 세운 계산
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
const saju = getSaju(adapter, dt, {
|
|
645
|
+
longitudeDeg: 126.9778,
|
|
646
|
+
gender: "female",
|
|
647
|
+
yearlyLuckRange: { from: 2024, to: 2030 }
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// 대운 확인
|
|
651
|
+
console.log(saju.majorLuck.pillars); // 대운 목록
|
|
652
|
+
console.log(saju.majorLuck.startAge); // 대운 시작 나이
|
|
653
|
+
|
|
654
|
+
// 세운 확인
|
|
655
|
+
saju.yearlyLuck.forEach(luck => {
|
|
656
|
+
console.log(`${luck.year}년(${luck.pillar}): ${luck.age}세`);
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### 절기 정보 확인
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
const saju = getSaju(adapter, dt, {
|
|
664
|
+
longitudeDeg: 126.9778,
|
|
665
|
+
gender: "male",
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// 현재 절기
|
|
669
|
+
console.log(saju.solarTerms.current.name); // "소한"
|
|
670
|
+
console.log(saju.solarTerms.current.hanja); // "小寒"
|
|
671
|
+
console.log(saju.solarTerms.daysSinceCurrent); // 5 (절기 경과일)
|
|
672
|
+
|
|
673
|
+
// 다음 절기
|
|
674
|
+
console.log(saju.solarTerms.next.name); // "대한"
|
|
675
|
+
console.log(saju.solarTerms.daysUntilNext); // 10 (다음 절기까지 남은 일)
|
|
676
|
+
|
|
677
|
+
// 절기 시작 날짜
|
|
678
|
+
console.log(saju.solarTerms.currentDate); // { year: 2024, month: 1, day: 6, ... }
|
|
679
|
+
console.log(saju.solarTerms.nextDate); // { year: 2024, month: 1, day: 20, ... }
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### 십신 및 오행 분석
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { analyzeTenGods, countElements } from "@gracefullight/saju";
|
|
686
|
+
|
|
687
|
+
const tenGods = analyzeTenGods("己卯", "丙子", "辛巳", "戊戌");
|
|
688
|
+
console.log(tenGods.dayMaster); // "辛"
|
|
689
|
+
|
|
690
|
+
const elements = countElements(tenGods);
|
|
691
|
+
console.log(elements); // { wood: 1, fire: 1, earth: 3, metal: 1, water: 2 }
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### 신강약 및 용신 분석
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
import { analyzeStrength, analyzeYongShen, getElementRecommendations } from "@gracefullight/saju";
|
|
698
|
+
|
|
699
|
+
const strength = analyzeStrength("己卯", "丙子", "辛巳", "戊戌");
|
|
700
|
+
console.log(strength.level); // "신약"
|
|
701
|
+
|
|
702
|
+
const yongShen = analyzeYongShen("己卯", "丙子", "辛巳", "戊戌");
|
|
703
|
+
console.log(yongShen.primary); // 용신 오행 (예: "earth")
|
|
704
|
+
|
|
705
|
+
const tips = getElementRecommendations(yongShen);
|
|
706
|
+
console.log(tips.colors); // 행운의 색상
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### 합충형파해 분석
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
import { analyzeRelations } from "@gracefullight/saju";
|
|
713
|
+
|
|
714
|
+
const relations = analyzeRelations("己卯", "丙子", "辛巳", "戊戌");
|
|
715
|
+
relations.clashes.forEach(c => {
|
|
716
|
+
console.log(`${c.positions[0]}-${c.positions[1]} 지지 충: ${c.pair[0]}-${c.pair[1]}`);
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
438
720
|
### 다양한 타임존에서 계산
|
|
439
721
|
|
|
440
722
|
```typescript
|
|
@@ -445,7 +727,7 @@ const adapter = await createLuxonAdapter();
|
|
|
445
727
|
|
|
446
728
|
// 뉴욕 출생 시간
|
|
447
729
|
const nyTime = DateTime.fromObject(
|
|
448
|
-
{ year:
|
|
730
|
+
{ year: 1985, month: 5, day: 15, hour: 6, minute: 30 },
|
|
449
731
|
{ zone: "America/New_York" }
|
|
450
732
|
);
|
|
451
733
|
|
|
@@ -470,7 +752,7 @@ const month = monthPillar(adapter, dt);
|
|
|
470
752
|
console.log(month.pillar, month.sunLonDeg);
|
|
471
753
|
|
|
472
754
|
// 일주 (어댑터 불필요)
|
|
473
|
-
const day = dayPillarFromDate({ year:
|
|
755
|
+
const day = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
|
|
474
756
|
console.log(day.pillar);
|
|
475
757
|
|
|
476
758
|
// 태양시를 사용한 시주
|
|
@@ -542,7 +824,15 @@ packages/saju/
|
|
|
542
824
|
│ │ ├── luxon.ts # Luxon 어댑터
|
|
543
825
|
│ │ └── date-fns.ts # date-fns 어댑터
|
|
544
826
|
│ ├── core/ # 핵심 계산 로직
|
|
545
|
-
│ │
|
|
827
|
+
│ │ ├── four-pillars.ts # 사주 팔자 계산
|
|
828
|
+
│ │ ├── ten-gods.ts # 십신 분석
|
|
829
|
+
│ │ ├── strength.ts # 신강/신약 판정
|
|
830
|
+
│ │ ├── relations.ts # 합충형파해 분석
|
|
831
|
+
│ │ ├── luck.ts # 대운/세운 계산
|
|
832
|
+
│ │ ├── yongshen.ts # 용신 추출
|
|
833
|
+
│ │ ├── solar-terms.ts # 절기 계산
|
|
834
|
+
│ │ └── lunar.ts # 음력 변환
|
|
835
|
+
│ ├── types/ # 타입 정의
|
|
546
836
|
│ ├── __tests__/ # 테스트 스위트
|
|
547
837
|
│ └── index.ts # 공개 API
|
|
548
838
|
├── dist/ # 컴파일된 출력
|
|
@@ -659,7 +949,3 @@ MIT © [gracefullight](https://github.com/gracefullight)
|
|
|
659
949
|
- [문서](https://github.com/gracefullight/saju#readme)
|
|
660
950
|
- [이슈 트래커](https://github.com/gracefullight/saju/issues)
|
|
661
951
|
- [토론](https://github.com/gracefullight/saju/discussions)
|
|
662
|
-
|
|
663
|
-
---
|
|
664
|
-
|
|
665
|
-
Made by [gracefullight](https://github.com/gracefullight)
|
|
@@ -20,14 +20,14 @@ describe("Four Pillars Core", () => {
|
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
describe("dayPillarFromDate", () => {
|
|
23
|
-
it("should calculate day pillar for test case (
|
|
24
|
-
const result = dayPillarFromDate({ year:
|
|
25
|
-
expect(result.pillar).toBe("
|
|
26
|
-
expect(result.idx60).toBe(
|
|
23
|
+
it("should calculate day pillar for test case (1985-05-15 = 甲寅)", () => {
|
|
24
|
+
const result = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
|
|
25
|
+
expect(result.pillar).toBe("甲寅");
|
|
26
|
+
expect(result.idx60).toBe(50);
|
|
27
27
|
});
|
|
28
28
|
it("should handle 60-day cycle correctly", () => {
|
|
29
|
-
const start = dayPillarFromDate({ year:
|
|
30
|
-
const after60 = dayPillarFromDate({ year:
|
|
29
|
+
const start = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
|
|
30
|
+
const after60 = dayPillarFromDate({ year: 1985, month: 7, day: 14 });
|
|
31
31
|
expect(start.idx60).toBe(after60.idx60);
|
|
32
32
|
});
|
|
33
33
|
it("should calculate day pillar for leap year date", () => {
|
|
@@ -47,21 +47,21 @@ describe("Four Pillars Core", () => {
|
|
|
47
47
|
expect(result.pillar).toBe("甲子");
|
|
48
48
|
expect(result.solarYear).toBe(1984);
|
|
49
49
|
});
|
|
50
|
-
it("should calculate year pillar for
|
|
51
|
-
const dt = DateTime.fromObject({ year:
|
|
50
|
+
it("should calculate year pillar for 1985 (乙丑)", () => {
|
|
51
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15 }, { zone: "Asia/Seoul" });
|
|
52
52
|
const result = yearPillar(adapter, dt);
|
|
53
|
-
expect(result.pillar).toBe("
|
|
54
|
-
expect(result.solarYear).toBe(
|
|
53
|
+
expect(result.pillar).toBe("乙丑");
|
|
54
|
+
expect(result.solarYear).toBe(1985);
|
|
55
55
|
});
|
|
56
56
|
it("should handle date before Lichun (立春)", () => {
|
|
57
|
-
const dt = DateTime.fromObject({ year:
|
|
57
|
+
const dt = DateTime.fromObject({ year: 1985, month: 1, day: 15 }, { zone: "Asia/Seoul" });
|
|
58
58
|
const result = yearPillar(adapter, dt);
|
|
59
|
-
expect(result.solarYear).toBe(
|
|
59
|
+
expect(result.solarYear).toBe(1984);
|
|
60
60
|
});
|
|
61
61
|
it("should handle date after Lichun", () => {
|
|
62
|
-
const dt = DateTime.fromObject({ year:
|
|
62
|
+
const dt = DateTime.fromObject({ year: 1985, month: 3, day: 1 }, { zone: "Asia/Seoul" });
|
|
63
63
|
const result = yearPillar(adapter, dt);
|
|
64
|
-
expect(result.solarYear).toBe(
|
|
64
|
+
expect(result.solarYear).toBe(1985);
|
|
65
65
|
});
|
|
66
66
|
it("should follow 60-year cycle", () => {
|
|
67
67
|
const dt1 = DateTime.fromObject({ year: 1984, month: 3, day: 1 }, { zone: "Asia/Seoul" });
|
|
@@ -72,25 +72,25 @@ describe("Four Pillars Core", () => {
|
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
describe("monthPillar", () => {
|
|
75
|
-
it("should calculate month pillar for test case (
|
|
76
|
-
const dt = DateTime.fromObject({ year:
|
|
75
|
+
it("should calculate month pillar for test case (1985-05-15)", () => {
|
|
76
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15 }, { zone: "Asia/Seoul" });
|
|
77
77
|
const result = monthPillar(adapter, dt);
|
|
78
|
-
expect(result.pillar).toBe("
|
|
78
|
+
expect(result.pillar).toBe("辛巳");
|
|
79
79
|
});
|
|
80
80
|
it("should return valid stem-branch combination", () => {
|
|
81
|
-
const dt = DateTime.fromObject({ year:
|
|
81
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15 }, { zone: "Asia/Seoul" });
|
|
82
82
|
const result = monthPillar(adapter, dt);
|
|
83
83
|
expect(result.pillar).toMatch(/^[甲乙丙丁戊己庚辛壬癸][子丑寅卯辰巳午未申酉戌亥]$/);
|
|
84
84
|
});
|
|
85
85
|
it("should include sun longitude in result", () => {
|
|
86
|
-
const dt = DateTime.fromObject({ year:
|
|
86
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15 }, { zone: "Asia/Seoul" });
|
|
87
87
|
const result = monthPillar(adapter, dt);
|
|
88
88
|
expect(result.sunLonDeg).toBeGreaterThanOrEqual(0);
|
|
89
89
|
expect(result.sunLonDeg).toBeLessThan(360);
|
|
90
90
|
});
|
|
91
91
|
it("should calculate different months correctly", () => {
|
|
92
|
-
const jan = DateTime.fromObject({ year:
|
|
93
|
-
const jul = DateTime.fromObject({ year:
|
|
92
|
+
const jan = DateTime.fromObject({ year: 1985, month: 1, day: 15 }, { zone: "Asia/Seoul" });
|
|
93
|
+
const jul = DateTime.fromObject({ year: 1985, month: 7, day: 15 }, { zone: "Asia/Seoul" });
|
|
94
94
|
const result1 = monthPillar(adapter, jan);
|
|
95
95
|
const result2 = monthPillar(adapter, jul);
|
|
96
96
|
expect(result1.pillar).not.toBe(result2.pillar);
|
|
@@ -98,41 +98,41 @@ describe("Four Pillars Core", () => {
|
|
|
98
98
|
});
|
|
99
99
|
describe("effectiveDayDate", () => {
|
|
100
100
|
it("should return same date for midnight boundary before 23:00", () => {
|
|
101
|
-
const dt = DateTime.fromObject({ year:
|
|
101
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15, hour: 22, minute: 30 }, { zone: "Asia/Seoul" });
|
|
102
102
|
const result = effectiveDayDate(adapter, dt, { dayBoundary: "midnight" });
|
|
103
|
-
expect(result.year).toBe(
|
|
104
|
-
expect(result.month).toBe(
|
|
105
|
-
expect(result.day).toBe(
|
|
103
|
+
expect(result.year).toBe(1985);
|
|
104
|
+
expect(result.month).toBe(5);
|
|
105
|
+
expect(result.day).toBe(15);
|
|
106
106
|
});
|
|
107
107
|
it("should advance date for zi23 boundary after 23:00", () => {
|
|
108
|
-
const dt = DateTime.fromObject({ year:
|
|
108
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15, hour: 23, minute: 30 }, { zone: "Asia/Seoul" });
|
|
109
109
|
const result = effectiveDayDate(adapter, dt, { dayBoundary: "zi23" });
|
|
110
|
-
expect(result.year).toBe(
|
|
111
|
-
expect(result.month).toBe(
|
|
112
|
-
expect(result.day).toBe(
|
|
110
|
+
expect(result.year).toBe(1985);
|
|
111
|
+
expect(result.month).toBe(5);
|
|
112
|
+
expect(result.day).toBe(16);
|
|
113
113
|
});
|
|
114
114
|
it("should not advance date for zi23 boundary before 23:00", () => {
|
|
115
|
-
const dt = DateTime.fromObject({ year:
|
|
115
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15, hour: 22, minute: 59 }, { zone: "Asia/Seoul" });
|
|
116
116
|
const result = effectiveDayDate(adapter, dt, { dayBoundary: "zi23" });
|
|
117
|
-
expect(result.year).toBe(
|
|
118
|
-
expect(result.month).toBe(
|
|
119
|
-
expect(result.day).toBe(
|
|
117
|
+
expect(result.year).toBe(1985);
|
|
118
|
+
expect(result.month).toBe(5);
|
|
119
|
+
expect(result.day).toBe(15);
|
|
120
120
|
});
|
|
121
121
|
it("should handle month boundary with zi23", () => {
|
|
122
|
-
const dt = DateTime.fromObject({ year:
|
|
122
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 31, hour: 23, minute: 30 }, { zone: "Asia/Seoul" });
|
|
123
123
|
const result = effectiveDayDate(adapter, dt, { dayBoundary: "zi23" });
|
|
124
|
-
expect(result.year).toBe(
|
|
125
|
-
expect(result.month).toBe(
|
|
124
|
+
expect(result.year).toBe(1985);
|
|
125
|
+
expect(result.month).toBe(6);
|
|
126
126
|
expect(result.day).toBe(1);
|
|
127
127
|
});
|
|
128
128
|
it("should apply mean solar time correction when enabled", () => {
|
|
129
|
-
const dt = DateTime.fromObject({ year:
|
|
129
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15, hour: 23, minute: 50 }, { zone: "Asia/Seoul" });
|
|
130
130
|
const result = effectiveDayDate(adapter, dt, {
|
|
131
131
|
dayBoundary: "zi23",
|
|
132
132
|
longitudeDeg: 126.9,
|
|
133
133
|
useMeanSolarTimeForBoundary: true,
|
|
134
134
|
});
|
|
135
|
-
expect(result.day).toBe(
|
|
135
|
+
expect(result.day).toBe(16);
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
138
|
describe("hourPillar", () => {
|
|
@@ -175,7 +175,7 @@ describe("Four Pillars Core", () => {
|
|
|
175
175
|
});
|
|
176
176
|
expect(result.year).toBe("己卯");
|
|
177
177
|
expect(result.month).toBe("丙子");
|
|
178
|
-
expect(result.day).toBe("
|
|
178
|
+
expect(result.day).toBe("戊午");
|
|
179
179
|
expect(result.hour).toBe("辛酉");
|
|
180
180
|
});
|
|
181
181
|
it("should include metadata", () => {
|
|
@@ -189,6 +189,18 @@ describe("Four Pillars Core", () => {
|
|
|
189
189
|
expect(result.meta.effectiveDayDate).toEqual({ year: 2000, month: 1, day: 1 });
|
|
190
190
|
expect(result.meta.preset).toEqual(presetA);
|
|
191
191
|
});
|
|
192
|
+
it("should include lunar date information", () => {
|
|
193
|
+
const dt = DateTime.fromObject({ year: 2000, month: 1, day: 1, hour: 18, minute: 0 }, { zone: "Asia/Seoul" });
|
|
194
|
+
const result = getFourPillars(adapter, dt, {
|
|
195
|
+
longitudeDeg: 126.9,
|
|
196
|
+
preset: presetA,
|
|
197
|
+
});
|
|
198
|
+
expect(result.lunar).toBeDefined();
|
|
199
|
+
expect(result.lunar.lunarYear).toBe(1999);
|
|
200
|
+
expect(result.lunar.lunarMonth).toBe(11);
|
|
201
|
+
expect(result.lunar.lunarDay).toBe(25);
|
|
202
|
+
expect(result.lunar.isLeapMonth).toBe(false);
|
|
203
|
+
});
|
|
192
204
|
});
|
|
193
205
|
describe("getFourPillars - Preset B", () => {
|
|
194
206
|
it("should calculate four pillars with preset B", () => {
|
|
@@ -274,7 +286,7 @@ describe("Four Pillars Core", () => {
|
|
|
274
286
|
expect(resultSeoul.day).toBe(resultTokyo.day);
|
|
275
287
|
});
|
|
276
288
|
it("should handle different longitudes affecting hour pillar", () => {
|
|
277
|
-
const dt = DateTime.fromObject({ year:
|
|
289
|
+
const dt = DateTime.fromObject({ year: 1985, month: 5, day: 15, hour: 0, minute: 30 }, { zone: "UTC" });
|
|
278
290
|
const westLong = getFourPillars(adapter, dt, {
|
|
279
291
|
longitudeDeg: -120,
|
|
280
292
|
preset: presetB,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luck.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/luck.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { calculateYearlyLuck, getYearPillar } from "@/core/luck";
|
|
3
|
+
describe("luck", () => {
|
|
4
|
+
describe("getYearPillar", () => {
|
|
5
|
+
it("returns correct pillar for 1984 (甲子)", () => {
|
|
6
|
+
expect(getYearPillar(1984)).toBe("甲子");
|
|
7
|
+
});
|
|
8
|
+
it("returns correct pillar for 2024 (甲辰)", () => {
|
|
9
|
+
expect(getYearPillar(2024)).toBe("甲辰");
|
|
10
|
+
});
|
|
11
|
+
it("returns correct pillar for 2000 (庚辰)", () => {
|
|
12
|
+
expect(getYearPillar(2000)).toBe("庚辰");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe("calculateYearlyLuck", () => {
|
|
16
|
+
it("calculates yearly luck for a range", () => {
|
|
17
|
+
const result = calculateYearlyLuck(1990, 2020, 2025);
|
|
18
|
+
expect(result).toHaveLength(6);
|
|
19
|
+
expect(result[0].year).toBe(2020);
|
|
20
|
+
expect(result[5].year).toBe(2025);
|
|
21
|
+
});
|
|
22
|
+
it("includes correct age calculation", () => {
|
|
23
|
+
const result = calculateYearlyLuck(1990, 2020, 2020);
|
|
24
|
+
expect(result[0].age).toBe(31);
|
|
25
|
+
});
|
|
26
|
+
it("returns pillar, stem, and branch for each year", () => {
|
|
27
|
+
const result = calculateYearlyLuck(1990, 2024, 2024);
|
|
28
|
+
expect(result[0].pillar).toBe("甲辰");
|
|
29
|
+
expect(result[0].stem).toBe("甲");
|
|
30
|
+
expect(result[0].branch).toBe("辰");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lunar.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lunar.test.ts"],"names":[],"mappings":""}
|