@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.
Files changed (55) hide show
  1. package/README.en.md +314 -28
  2. package/README.md +314 -28
  3. package/dist/__tests__/four-pillars.test.js +52 -40
  4. package/dist/__tests__/luck.test.d.ts +2 -0
  5. package/dist/__tests__/luck.test.d.ts.map +1 -0
  6. package/dist/__tests__/luck.test.js +33 -0
  7. package/dist/__tests__/lunar.test.d.ts +2 -0
  8. package/dist/__tests__/lunar.test.d.ts.map +1 -0
  9. package/dist/__tests__/lunar.test.js +83 -0
  10. package/dist/__tests__/relations.test.d.ts +2 -0
  11. package/dist/__tests__/relations.test.d.ts.map +1 -0
  12. package/dist/__tests__/relations.test.js +90 -0
  13. package/dist/__tests__/saju.test.d.ts +2 -0
  14. package/dist/__tests__/saju.test.d.ts.map +1 -0
  15. package/dist/__tests__/saju.test.js +133 -0
  16. package/dist/__tests__/solar-terms.test.d.ts +2 -0
  17. package/dist/__tests__/solar-terms.test.d.ts.map +1 -0
  18. package/dist/__tests__/solar-terms.test.js +121 -0
  19. package/dist/__tests__/strength.test.d.ts +2 -0
  20. package/dist/__tests__/strength.test.d.ts.map +1 -0
  21. package/dist/__tests__/strength.test.js +44 -0
  22. package/dist/__tests__/ten-gods.test.d.ts +2 -0
  23. package/dist/__tests__/ten-gods.test.d.ts.map +1 -0
  24. package/dist/__tests__/ten-gods.test.js +119 -0
  25. package/dist/__tests__/yongshen.test.d.ts +2 -0
  26. package/dist/__tests__/yongshen.test.d.ts.map +1 -0
  27. package/dist/__tests__/yongshen.test.js +62 -0
  28. package/dist/core/four-pillars.d.ts +2 -0
  29. package/dist/core/four-pillars.d.ts.map +1 -1
  30. package/dist/core/four-pillars.js +7 -4
  31. package/dist/core/luck.d.ts +41 -0
  32. package/dist/core/luck.d.ts.map +1 -0
  33. package/dist/core/luck.js +96 -0
  34. package/dist/core/lunar.d.ts +13 -0
  35. package/dist/core/lunar.d.ts.map +1 -0
  36. package/dist/core/lunar.js +24 -0
  37. package/dist/core/relations.d.ts +94 -0
  38. package/dist/core/relations.d.ts.map +1 -0
  39. package/dist/core/relations.js +305 -0
  40. package/dist/core/solar-terms.d.ts +155 -0
  41. package/dist/core/solar-terms.d.ts.map +1 -0
  42. package/dist/core/solar-terms.js +266 -0
  43. package/dist/core/strength.d.ts +18 -0
  44. package/dist/core/strength.d.ts.map +1 -0
  45. package/dist/core/strength.js +255 -0
  46. package/dist/core/ten-gods.d.ts +130 -0
  47. package/dist/core/ten-gods.d.ts.map +1 -0
  48. package/dist/core/ten-gods.js +335 -0
  49. package/dist/core/yongshen.d.ts +20 -0
  50. package/dist/core/yongshen.d.ts.map +1 -0
  51. package/dist/core/yongshen.js +216 -0
  52. package/dist/index.d.ts +54 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +48 -0
  55. package/package.json +15 -12
package/README.md CHANGED
@@ -15,7 +15,13 @@
15
15
  - **태양시 보정** - 경도 기반 평균 태양시 조정 옵션
16
16
  - **트리쉐이킹 지원** - 필요한 것만 import
17
17
  - **완전한 타입 지원** - TypeScript 정의 완비
18
- - **풍부한 테스트** - 85개 이상 테스트, 91% 이상 커버리지
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(1992, 9, 12, 19, 16), // 주의: 월은 0부터 시작
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
- solarYear: number;
280
+ solarYearUsed: number;
233
281
  sunLonDeg: number;
234
282
  effectiveDayDate: { year: number; month: number; day: number };
235
- adjustedHour: number;
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: 1992, month: 10, day: 12, hour: 6, minute: 16 },
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: 1992, month: 10, day: 12 });
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
- │ │ └── four-pillars.ts # 메인 알고리즘
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 (1992-10-12 = 甲申)", () => {
24
- const result = dayPillarFromDate({ year: 1992, month: 10, day: 12 });
25
- expect(result.pillar).toBe("甲申");
26
- expect(result.idx60).toBe(20);
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: 1992, month: 10, day: 12 });
30
- const after60 = dayPillarFromDate({ year: 1992, month: 12, day: 11 });
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 1992 (壬申)", () => {
51
- const dt = DateTime.fromObject({ year: 1992, month: 10, day: 12 }, { zone: "Asia/Seoul" });
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(1992);
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: 1992, month: 1, day: 15 }, { zone: "Asia/Seoul" });
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(1991);
59
+ expect(result.solarYear).toBe(1984);
60
60
  });
61
61
  it("should handle date after Lichun", () => {
62
- const dt = DateTime.fromObject({ year: 1992, month: 3, day: 1 }, { zone: "Asia/Seoul" });
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(1992);
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 (1992-10-12)", () => {
76
- const dt = DateTime.fromObject({ year: 1992, month: 10, day: 12 }, { zone: "Asia/Seoul" });
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: 1992, month: 10, day: 12 }, { zone: "Asia/Seoul" });
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: 1992, month: 10, day: 12 }, { zone: "Asia/Seoul" });
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: 1992, month: 1, day: 15 }, { zone: "Asia/Seoul" });
93
- const jul = DateTime.fromObject({ year: 1992, month: 7, day: 15 }, { zone: "Asia/Seoul" });
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: 1992, month: 10, day: 12, hour: 22, minute: 30 }, { zone: "Asia/Seoul" });
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(1992);
104
- expect(result.month).toBe(10);
105
- expect(result.day).toBe(12);
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: 1992, month: 10, day: 12, hour: 23, minute: 30 }, { zone: "Asia/Seoul" });
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(1992);
111
- expect(result.month).toBe(10);
112
- expect(result.day).toBe(13);
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: 1992, month: 10, day: 12, hour: 22, minute: 59 }, { zone: "Asia/Seoul" });
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(1992);
118
- expect(result.month).toBe(10);
119
- expect(result.day).toBe(12);
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: 1992, month: 10, day: 31, hour: 23, minute: 30 }, { zone: "Asia/Seoul" });
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(1992);
125
- expect(result.month).toBe(11);
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: 1992, month: 10, day: 12, hour: 23, minute: 50 }, { zone: "Asia/Seoul" });
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(13);
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: 1992, month: 10, day: 12, hour: 0, minute: 30 }, { zone: "UTC" });
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=luck.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lunar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lunar.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lunar.test.ts"],"names":[],"mappings":""}