@gracefullight/saju 1.1.0 → 1.1.2

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.ko.md ADDED
@@ -0,0 +1,958 @@
1
+ # @gracefullight/saju
2
+
3
+ > 유연한 날짜 어댑터를 지원하는 사주(四柱命理) 계산 TypeScript 라이브러리
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@gracefullight/saju.svg)](https://www.npmjs.com/package/@gracefullight/saju)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ [English](./README.md) | **한국어**
9
+
10
+ ## 주요 기능
11
+
12
+ - **정확한 사주 계산** - 천문학적 정확도로 전통 중국 역법 알고리즘 구현
13
+ - **유연한 날짜 어댑터 패턴** - Luxon, date-fns 또는 원하는 날짜 라이브러리 사용 가능
14
+ - **타임존 & 위치 지원** - 타임존 및 지리적 좌표를 올바르게 처리
15
+ - **태양시 보정** - 경도 기반 평균 태양시 조정 옵션
16
+ - **트리쉐이킹 지원** - 필요한 것만 import
17
+ - **완전한 타입 지원** - TypeScript 정의 완비
18
+ - **풍부한 테스트** - 180개 이상 테스트, 91% 이상 커버리지
19
+ - **십신 분석** - 장간(藏干)을 포함한 상세 십신 및 오행 분포 분석
20
+ - **신강/신약 판정** - 월령 득령(得令), 통근(通根), 투간(透干), 본중여기(本中餘氣) 가중치를 고려한 9단계 신강도 분석
21
+ - **합충형파해** - 천간합, 육합, 삼합, 방합 및 충, 해, 형, 파 분석. 합(合)과 화(化) 성립 조건 분리 표기
22
+ - **대운/세운 계산** - 절기(節氣) 기반 정확한 기운(起運) 계산, 성별 및 연간 음양을 고려한 대운 및 연도별 세운 계산
23
+ - **용신 추출** - 격국(格局), 억부(抑扶), 조후(調候) 순서로 용신 추천 및 개운법 가이드
24
+ - **절기 분석** - 현재/다음 절기 정보 및 경과일 계산
25
+
26
+ ## 사주(四柱)란?
27
+
28
+ 사주(四柱), 또는 사주명리는 출생 연월일시를 기반으로 한 전통 한국/중국 운명 분석 시스템입니다. 각 기둥은 다음으로 구성됩니다:
29
+ - **천간(天干)**: 10개 원소 (甲乙丙丁戊己庚辛壬癸)
30
+ - **지지(地支)**: 12지지 (子丑寅卯辰巳午未申酉戌亥)
31
+
32
+ 이 라이브러리는 다음을 사용하여 기둥을 계산합니다:
33
+ - **입춘(立春)** 을 기준으로 한 연주 전환
34
+ - **태양 황경** 을 이용한 월주 결정
35
+ - **율리우스 적일** 을 이용한 일주 계산
36
+ - **전통 중국 시진(時辰) 체계** 를 이용한 시주
37
+
38
+ ## 설치
39
+
40
+ ```bash
41
+ # pnpm 사용
42
+ pnpm add @gracefullight/saju
43
+
44
+ # npm 사용
45
+ npm install @gracefullight/saju
46
+
47
+ # yarn 사용
48
+ yarn add @gracefullight/saju
49
+ ```
50
+
51
+ ### 날짜 라이브러리 어댑터
52
+
53
+ 선호도에 따라 선택:
54
+
55
+ ```bash
56
+ # 옵션 1: Luxon (현대적인 앱에 권장)
57
+ pnpm add luxon @types/luxon
58
+
59
+ # 옵션 2: date-fns (가벼운 대안)
60
+ pnpm add date-fns date-fns-tz
61
+ ```
62
+
63
+ ## 빠른 시작
64
+
65
+ ```typescript
66
+ import { DateTime } from "luxon";
67
+ import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
68
+ import { getSaju } 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(birthDateTime, {
79
+ adapter,
80
+ gender: "male", // 필수: 대운 계산에 필요
81
+ // longitudeDeg: 126.9778, // 선택: 생략 시 타임존 기준 경도 사용
82
+ // preset: STANDARD_PRESET, // 선택: 기본값은 STANDARD_PRESET
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
+
98
+ ```typescript
99
+ import { DateTime } from "luxon";
100
+ import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
101
+ import { getFourPillars } from "@gracefullight/saju";
102
+
103
+ const adapter = await createLuxonAdapter();
104
+
105
+ const birthDateTime = DateTime.fromObject(
106
+ { year: 2000, month: 1, day: 1, hour: 18, minute: 0 },
107
+ { zone: "Asia/Seoul" }
108
+ );
109
+
110
+ const result = getFourPillars(birthDateTime, { adapter });
111
+
112
+ console.log(result);
113
+ ```
114
+
115
+ ## 사용법
116
+
117
+ ### Luxon 사용
118
+
119
+ ```typescript
120
+ import { DateTime } from "luxon";
121
+ import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
122
+ import { getFourPillars, STANDARD_PRESET, TRADITIONAL_PRESET } from "@gracefullight/saju";
123
+
124
+ const adapter = await createLuxonAdapter();
125
+
126
+ const dt = DateTime.fromObject(
127
+ { year: 2000, month: 1, day: 1, hour: 18, minute: 0 },
128
+ { zone: "Asia/Seoul" }
129
+ );
130
+
131
+ // 표준 프리셋: 자정(00:00) 날짜 경계, 태양시 보정 없음
132
+ const resultStandard = getFourPillars(dt, {
133
+ adapter,
134
+ longitudeDeg: 126.9778,
135
+ preset: STANDARD_PRESET,
136
+ });
137
+
138
+ // 전통 프리셋: 자시(23:00) 날짜 경계, 태양시 보정 사용
139
+ const resultTraditional = getFourPillars(dt, {
140
+ adapter,
141
+ longitudeDeg: 126.9778,
142
+ preset: TRADITIONAL_PRESET,
143
+ });
144
+ ```
145
+
146
+ ### date-fns 사용
147
+
148
+ ```typescript
149
+ import { createDateFnsAdapter } from "@gracefullight/saju/adapters/date-fns";
150
+ import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
151
+
152
+ const adapter = await createDateFnsAdapter();
153
+
154
+ const dt = {
155
+ date: new Date(1985, 4, 15, 14, 30), // 주의: 월은 0부터 시작
156
+ timeZone: "Asia/Seoul",
157
+ };
158
+
159
+ const result = getFourPillars(dt, {
160
+ adapter,
161
+ longitudeDeg: 126.9778,
162
+ preset: STANDARD_PRESET,
163
+ });
164
+ ```
165
+
166
+ ### 커스텀 날짜 어댑터
167
+
168
+ `DateAdapter` 인터페이스를 구현하여 원하는 날짜 라이브러리 사용:
169
+
170
+ ```typescript
171
+ import type { DateAdapter } from "@gracefullight/saju";
172
+
173
+ const myAdapter: DateAdapter<MyDateType> = {
174
+ // 날짜 컴포넌트 getter
175
+ getYear: (date) => date.year,
176
+ getMonth: (date) => date.month,
177
+ getDay: (date) => date.day,
178
+ getHour: (date) => date.hour,
179
+ getMinute: (date) => date.minute,
180
+ getSecond: (date) => date.second,
181
+ getZoneName: (date) => date.zoneName,
182
+
183
+ // 날짜 연산
184
+ plusMinutes: (date, minutes) => date.add({ minutes }),
185
+ plusDays: (date, days) => date.add({ days }),
186
+ minusDays: (date, days) => date.subtract({ days }),
187
+
188
+ // 타임존 연산
189
+ toUTC: (date) => date.toUTC(),
190
+ setZone: (date, zoneName) => date.setZone(zoneName),
191
+
192
+ // 변환
193
+ toISO: (date) => date.toISO(),
194
+ toMillis: (date) => date.valueOf(),
195
+ fromMillis: (millis, zone) => MyDate.fromMillis(millis, zone),
196
+
197
+ // 유틸리티
198
+ createUTC: (year, month, day, hour, minute, second) =>
199
+ MyDate.utc(year, month, day, hour, minute, second),
200
+ isGreaterThanOrEqual: (date1, date2) => date1 >= date2,
201
+ };
202
+ ```
203
+
204
+ ## API 레퍼런스
205
+
206
+ ### 설정 프리셋
207
+
208
+ #### `STANDARD_PRESET`
209
+ 현대적 해석: 자정 날짜 경계와 태양시 보정 없음
210
+
211
+ ```typescript
212
+ {
213
+ dayBoundary: "midnight", // 날짜는 00:00에 시작
214
+ useMeanSolarTimeForHour: false, // 시주는 현지 시간 사용
215
+ useMeanSolarTimeForBoundary: false // 날짜 경계는 현지 시간 사용
216
+ }
217
+ ```
218
+
219
+ #### `TRADITIONAL_PRESET`
220
+ 전통적 해석: 자시(23:00) 날짜 경계와 태양시 보정 사용
221
+
222
+ ```typescript
223
+ {
224
+ dayBoundary: "zi23", // 날짜는 23:00(子時)에 시작
225
+ useMeanSolarTimeForHour: true, // 시주는 태양시 사용
226
+ useMeanSolarTimeForBoundary: true // 날짜 경계는 태양시 사용
227
+ }
228
+ ```
229
+
230
+ ### 핵심 함수
231
+
232
+ #### `getSaju(datetime, options)`
233
+
234
+ 사주 분석의 모든 결과(팔자, 십신, 신강약, 합충, 용신, 대운)를 한 번에 계산합니다.
235
+
236
+ ```typescript
237
+ function getSaju<T>(
238
+ dtLocal: T,
239
+ options: {
240
+ adapter: DateAdapter<T>;
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
+
251
+ #### `getFourPillars(datetime, options)`
252
+
253
+ 네 기둥(연주, 월주, 일주, 시주) 모두 계산
254
+
255
+ ```typescript
256
+ function getFourPillars<T>(
257
+ datetime: T,
258
+ options: {
259
+ adapter: DateAdapter<T>;
260
+ longitudeDeg?: number;
261
+ preset?: {
262
+ dayBoundary: "midnight" | "zi23";
263
+ useMeanSolarTimeForHour: boolean;
264
+ useMeanSolarTimeForBoundary: boolean;
265
+ };
266
+ tzOffsetHours?: number;
267
+ }
268
+ ): {
269
+ year: string;
270
+ month: string;
271
+ day: string;
272
+ hour: string;
273
+ lunar: {
274
+ lunarYear: number;
275
+ lunarMonth: number;
276
+ lunarDay: number;
277
+ isLeapMonth: boolean;
278
+ };
279
+ meta: {
280
+ solarYearUsed: number;
281
+ sunLonDeg: number;
282
+ effectiveDayDate: { year: number; month: number; day: number };
283
+ adjustedDtForHour: string;
284
+ };
285
+ }
286
+ ```
287
+
288
+ **매개변수:**
289
+ - `datetime`: 어댑터 형식의 날짜/시간 객체
290
+ - `options`:
291
+ - `adapter`: DateAdapter 인스턴스
292
+ - `longitudeDeg`: 지리적 경도(도 단위) (예: 서울 126.9778), 선택사항
293
+ - `preset`: 설정 프리셋 (`STANDARD_PRESET` 또는 `TRADITIONAL_PRESET` 사용)
294
+ - `tzOffsetHours`: 타임존 오프셋(시간 단위), 선택사항 (기본값: 9, KST)
295
+
296
+ **반환값:** 연월일시 기둥, 음력 날짜, 메타데이터를 포함한 객체
297
+
298
+ #### `yearPillar(datetime, options)`
299
+
300
+ 입춘(立春, 봄의 시작) 기준으로 연주만 계산
301
+
302
+ ```typescript
303
+ function yearPillar<T>(
304
+ datetime: T,
305
+ options: { adapter: DateAdapter<T> }
306
+ ): {
307
+ idx60: number;
308
+ pillar: string;
309
+ solarYear: number;
310
+ }
311
+ ```
312
+
313
+ #### `monthPillar(datetime, options)`
314
+
315
+ 태양 황경 기준으로 월주만 계산
316
+
317
+ ```typescript
318
+ function monthPillar<T>(
319
+ datetime: T,
320
+ options: { adapter: DateAdapter<T> }
321
+ ): {
322
+ pillar: string;
323
+ sunLonDeg: number;
324
+ }
325
+ ```
326
+
327
+ #### `dayPillarFromDate({ year, month, day })`
328
+
329
+ 율리우스 적일을 사용하여 일주만 계산
330
+
331
+ ```typescript
332
+ function dayPillarFromDate(date: {
333
+ year: number;
334
+ month: number;
335
+ day: number;
336
+ }): {
337
+ idx60: number;
338
+ pillar: string;
339
+ }
340
+ ```
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
+
394
+ #### `hourPillar(datetime, options)`
395
+
396
+ 태양시 보정 옵션과 함께 시주만 계산
397
+
398
+ ```typescript
399
+ function hourPillar<T>(
400
+ datetime: T,
401
+ options: {
402
+ adapter: DateAdapter<T>;
403
+ longitudeDeg?: number;
404
+ tzOffsetHours?: number;
405
+ useMeanSolarTimeForHour?: boolean;
406
+ dayBoundary?: "midnight" | "zi23";
407
+ useMeanSolarTimeForBoundary?: boolean;
408
+ }
409
+ ): {
410
+ pillar: string;
411
+ adjustedDt: T;
412
+ adjustedHour: number;
413
+ }
414
+ ```
415
+
416
+ ### 상수
417
+
418
+ ```typescript
419
+ // 10 천간(天干)
420
+ export const STEMS: string[];
421
+ // ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
422
+
423
+ // 12 지지(地支)
424
+ export const BRANCHES: string[];
425
+ // ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]
426
+ ```
427
+
428
+ ### 헬퍼 함수
429
+
430
+ #### `applyMeanSolarTime(adapter, dtLocal, longitudeDeg, tzOffsetHours)`
431
+
432
+ 경도 기반 평균 태양시 보정 적용
433
+
434
+ ```typescript
435
+ function applyMeanSolarTime<T>(
436
+ adapter: DateAdapter<T>,
437
+ dtLocal: T,
438
+ longitudeDeg: number,
439
+ tzOffsetHours: number
440
+ ): T
441
+ ```
442
+
443
+ #### `effectiveDayDate(dtLocal, options)`
444
+
445
+ 날짜 경계 규칙을 고려한 유효 날짜 계산
446
+
447
+ ```typescript
448
+ function effectiveDayDate<T>(
449
+ dtLocal: T,
450
+ options: {
451
+ adapter: DateAdapter<T>;
452
+ dayBoundary?: "midnight" | "zi23";
453
+ longitudeDeg?: number;
454
+ tzOffsetHours?: number;
455
+ useMeanSolarTimeForBoundary?: boolean;
456
+ }
457
+ ): {
458
+ year: number;
459
+ month: number;
460
+ day: number;
461
+ }
462
+ ```
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(birthDateTime, gender, yearPillar, monthPillar, options)`
506
+
507
+ 대운의 흐름과 시작 연령을 계산합니다.
508
+
509
+ ```typescript
510
+ function calculateMajorLuck<T>(
511
+ birthDateTime: T,
512
+ gender: "male" | "female",
513
+ yearPillar: string,
514
+ monthPillar: string,
515
+ options: { adapter: DateAdapter<T>; longitudeDeg?: number; tzOffsetHours?: number }
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(datetime, options)`
533
+
534
+ 현재 및 다음 절기 정보와 경과일을 계산합니다.
535
+
536
+ ```typescript
537
+ function analyzeSolarTerms<T>(
538
+ dtLocal: T,
539
+ options: { adapter: DateAdapter<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(year, options)`
556
+
557
+ 특정 연도의 24절기 날짜를 모두 계산합니다.
558
+
559
+ ```typescript
560
+ function getSolarTermsForYear<T>(
561
+ year: number,
562
+ options: { adapter: DateAdapter<T>; timezone: string }
563
+ ): Array<{ term: SolarTerm; date: {...} }>;
564
+ ```
565
+
566
+ ## 고급 사용법
567
+
568
+ ### 태양시 보정
569
+
570
+ 태양시 보정은 현지 시계 시간과 실제 태양시의 차이를 고려하여 경도에 따라 현지 시간을 조정합니다.
571
+
572
+ ```typescript
573
+ import { applyMeanSolarTime, createLuxonAdapter } from "@gracefullight/saju";
574
+ import { DateTime } from "luxon";
575
+
576
+ const adapter = await createLuxonAdapter();
577
+ const localTime = DateTime.local(2024, 1, 1, 12, 0, 0, { zone: "Asia/Seoul" });
578
+
579
+ // 서울은 경도 126.9778°E이지만 UTC+9(표준 자오선 135°E) 사용
580
+ // 이로 인해 약 32분 차이 발생
581
+ const solarTime = applyMeanSolarTime(adapter, localTime, 126.9778, 9);
582
+ console.log(solarTime.hour); // ~11.47 (11:28)
583
+ ```
584
+
585
+ ### 날짜 경계 모드
586
+
587
+ **자정 모드** (`dayBoundary: "midnight"`):
588
+ - 날짜가 00:00 현지 시간에 변경
589
+ - 더 단순하고 현대 달력과 일치
590
+ - 일반적인 사용에 적합
591
+
592
+ **자시 모드** (`dayBoundary: "zi23"`):
593
+ - 날짜가 23:00 현지 시간에 변경
594
+ - 전통 중국 시간 체계
595
+ - 자시(子時)가 자정을 걸침 (23:00-01:00)
596
+
597
+ ```typescript
598
+ const result1 = getFourPillars(dt, {
599
+ adapter,
600
+ longitudeDeg: 126.9778,
601
+ preset: { ...STANDARD_PRESET, dayBoundary: "midnight" },
602
+ });
603
+
604
+ const result2 = getFourPillars(dt, {
605
+ adapter,
606
+ longitudeDeg: 126.9778,
607
+ preset: { ...STANDARD_PRESET, dayBoundary: "zi23" },
608
+ });
609
+ ```
610
+
611
+ ### 커스텀 설정
612
+
613
+ 특정 요구사항에 맞게 설정 조합:
614
+
615
+ ```typescript
616
+ const customConfig = {
617
+ dayBoundary: "midnight" as const, // 현대적인 자정 경계
618
+ useMeanSolarTimeForHour: true, // 하지만 시주는 태양시 사용
619
+ useMeanSolarTimeForBoundary: false, // 날짜 경계는 현지 시간 사용
620
+ };
621
+
622
+ const result = getFourPillars(dt, {
623
+ adapter,
624
+ longitudeDeg: 126.9778,
625
+ preset: customConfig,
626
+ });
627
+ ```
628
+
629
+ ## 지리적 좌표
630
+
631
+ 참고용 주요 도시 경도:
632
+
633
+ | 도시 | 경도 | 예시 |
634
+ |------|------|------|
635
+ | 서울, 대한민국 | 126.9778°E | `longitudeDeg: 126.9778` |
636
+ | 베이징, 중국 | 116.4074°E | `longitudeDeg: 116.4074` |
637
+ | 도쿄, 일본 | 139.6917°E | `longitudeDeg: 139.6917` |
638
+ | 상하이, 중국 | 121.4737°E | `longitudeDeg: 121.4737` |
639
+ | 타이베이, 대만 | 121.5654°E | `longitudeDeg: 121.5654` |
640
+
641
+ ## 예제
642
+
643
+ ### 대운과 세운 계산
644
+
645
+ ```typescript
646
+ const saju = getSaju(dt, {
647
+ adapter,
648
+ longitudeDeg: 126.9778,
649
+ gender: "female",
650
+ yearlyLuckRange: { from: 2024, to: 2030 }
651
+ });
652
+
653
+ // 대운 확인
654
+ console.log(saju.majorLuck.pillars); // 대운 목록
655
+ console.log(saju.majorLuck.startAge); // 대운 시작 나이
656
+
657
+ // 세운 확인
658
+ saju.yearlyLuck.forEach(luck => {
659
+ console.log(`${luck.year}년(${luck.pillar}): ${luck.age}세`);
660
+ });
661
+ ```
662
+
663
+ ### 절기 정보 확인
664
+
665
+ ```typescript
666
+ const saju = getSaju(dt, {
667
+ adapter,
668
+ longitudeDeg: 126.9778,
669
+ gender: "male",
670
+ });
671
+
672
+ // 현재 절기
673
+ console.log(saju.solarTerms.current.name); // "소한"
674
+ console.log(saju.solarTerms.current.hanja); // "小寒"
675
+ console.log(saju.solarTerms.daysSinceCurrent); // 5 (절기 경과일)
676
+
677
+ // 다음 절기
678
+ console.log(saju.solarTerms.next.name); // "대한"
679
+ console.log(saju.solarTerms.daysUntilNext); // 10 (다음 절기까지 남은 일)
680
+
681
+ // 절기 시작 날짜
682
+ console.log(saju.solarTerms.currentDate); // { year: 2024, month: 1, day: 6, ... }
683
+ console.log(saju.solarTerms.nextDate); // { year: 2024, month: 1, day: 20, ... }
684
+ ```
685
+
686
+ ### 십신 및 오행 분석
687
+
688
+ ```typescript
689
+ import { analyzeTenGods, countElements } from "@gracefullight/saju";
690
+
691
+ const tenGods = analyzeTenGods("己卯", "丙子", "辛巳", "戊戌");
692
+ console.log(tenGods.dayMaster); // "辛"
693
+
694
+ const elements = countElements(tenGods);
695
+ console.log(elements); // { wood: 1, fire: 1, earth: 3, metal: 1, water: 2 }
696
+ ```
697
+
698
+ ### 신강약 및 용신 분석
699
+
700
+ ```typescript
701
+ import { analyzeStrength, analyzeYongShen, getElementRecommendations } from "@gracefullight/saju";
702
+
703
+ const strength = analyzeStrength("己卯", "丙子", "辛巳", "戊戌");
704
+ console.log(strength.level); // "신약"
705
+
706
+ const yongShen = analyzeYongShen("己卯", "丙子", "辛巳", "戊戌");
707
+ console.log(yongShen.primary); // 용신 오행 (예: "earth")
708
+
709
+ const tips = getElementRecommendations(yongShen);
710
+ console.log(tips.colors); // 행운의 색상
711
+ ```
712
+
713
+ ### 합충형파해 분석
714
+
715
+ ```typescript
716
+ import { analyzeRelations } from "@gracefullight/saju";
717
+
718
+ const relations = analyzeRelations("己卯", "丙子", "辛巳", "戊戌");
719
+ relations.clashes.forEach(c => {
720
+ console.log(`${c.positions[0]}-${c.positions[1]} 지지 충: ${c.pair[0]}-${c.pair[1]}`);
721
+ });
722
+ ```
723
+
724
+ ### 다양한 타임존에서 계산
725
+
726
+ ```typescript
727
+ import { DateTime } from "luxon";
728
+ import { createLuxonAdapter, getFourPillars, TRADITIONAL_PRESET } from "@gracefullight/saju";
729
+
730
+ const adapter = await createLuxonAdapter();
731
+
732
+ // 뉴욕 출생 시간
733
+ const nyTime = DateTime.fromObject(
734
+ { year: 1985, month: 5, day: 15, hour: 6, minute: 30 },
735
+ { zone: "America/New_York" }
736
+ );
737
+
738
+ const result = getFourPillars(nyTime, {
739
+ adapter,
740
+ longitudeDeg: -74.0060, // 뉴욕 경도
741
+ tzOffsetHours: -5, // EST 오프셋
742
+ preset: TRADITIONAL_PRESET,
743
+ });
744
+ ```
745
+
746
+ ### 개별 기둥 계산
747
+
748
+ ```typescript
749
+ import { yearPillar, monthPillar, dayPillarFromDate, hourPillar } from "@gracefullight/saju";
750
+
751
+ // 연주
752
+ const year = yearPillar(dt, { adapter });
753
+ console.log(year.pillar, year.solarYear);
754
+
755
+ // 월주
756
+ const month = monthPillar(dt, { adapter });
757
+ console.log(month.pillar, month.sunLonDeg);
758
+
759
+ // 일주 (어댑터 불필요)
760
+ const day = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
761
+ console.log(day.pillar);
762
+
763
+ // 태양시를 사용한 시주
764
+ const hour = hourPillar(dt, {
765
+ adapter,
766
+ longitudeDeg: 126.9778,
767
+ useMeanSolarTimeForHour: true,
768
+ });
769
+ console.log(hour.pillar, hour.adjustedHour);
770
+ ```
771
+
772
+ ### 일괄 처리
773
+
774
+ ```typescript
775
+ const birthDates = [
776
+ { year: 1990, month: 1, day: 15, hour: 10, minute: 30 },
777
+ { year: 1995, month: 5, day: 20, hour: 14, minute: 45 },
778
+ { year: 2000, month: 12, day: 25, hour: 18, minute: 0 },
779
+ ];
780
+
781
+ const adapter = await createLuxonAdapter();
782
+
783
+ const results = birthDates.map((birth) => {
784
+ const dt = DateTime.fromObject(birth, { zone: "Asia/Seoul" });
785
+ return {
786
+ birth,
787
+ pillars: getFourPillars(dt, {
788
+ adapter,
789
+ longitudeDeg: 126.9778,
790
+ preset: STANDARD_PRESET,
791
+ }),
792
+ };
793
+ });
794
+ ```
795
+
796
+ ## 개발
797
+
798
+ ### 설정
799
+
800
+ ```bash
801
+ # 저장소 클론
802
+ git clone https://github.com/gracefullight/saju.git
803
+ cd saju
804
+
805
+ # 의존성 설치
806
+ pnpm install
807
+
808
+ # 테스트 실행
809
+ pnpm test
810
+
811
+ # 커버리지 확인
812
+ pnpm test:coverage
813
+
814
+ # 빌드
815
+ pnpm build
816
+
817
+ # 린트
818
+ pnpm lint
819
+
820
+ # 포맷
821
+ pnpm lint:fix
822
+ ```
823
+
824
+ ### 프로젝트 구조
825
+
826
+ ```
827
+ packages/saju/
828
+ ├── src/
829
+ │ ├── adapters/ # 날짜 라이브러리 어댑터
830
+ │ │ ├── date-adapter.ts # 어댑터 인터페이스
831
+ │ │ ├── luxon.ts # Luxon 어댑터
832
+ │ │ └── date-fns.ts # date-fns 어댑터
833
+ │ ├── core/ # 핵심 계산 로직
834
+ │ │ ├── four-pillars.ts # 사주 팔자 계산
835
+ │ │ ├── ten-gods.ts # 십신 분석
836
+ │ │ ├── strength.ts # 신강/신약 판정
837
+ │ │ ├── relations.ts # 합충형파해 분석
838
+ │ │ ├── luck.ts # 대운/세운 계산
839
+ │ │ ├── yongshen.ts # 용신 추출
840
+ │ │ ├── solar-terms.ts # 절기 계산
841
+ │ │ └── lunar.ts # 음력 변환
842
+ │ ├── types/ # 타입 정의
843
+ │ ├── __tests__/ # 테스트 스위트
844
+ │ └── index.ts # 공개 API
845
+ ├── dist/ # 컴파일된 출력
846
+ ├── coverage/ # 테스트 커버리지 리포트
847
+ └── README.md
848
+ ```
849
+
850
+ ### 테스트 실행
851
+
852
+ ```bash
853
+ # 모든 테스트 실행
854
+ pnpm test
855
+
856
+ # watch 모드로 테스트 실행
857
+ pnpm test:watch
858
+
859
+ # 커버리지 리포트 생성
860
+ pnpm test:coverage
861
+ ```
862
+
863
+ 커버리지 결과:
864
+ ```
865
+ File | % Stmts | % Branch | % Funcs | % Lines
866
+ -------------------|---------|----------|---------|----------
867
+ All files | 91.45 | 80.68 | 96.55 | 91.45
868
+ src/adapters | 94.59 | 90.24 | 100 | 94.59
869
+ src/core | 96.87 | 75.55 | 100 | 96.87
870
+ ```
871
+
872
+ ## 자주 묻는 질문
873
+
874
+ ### 단일 날짜 라이브러리 대신 날짜 어댑터를 사용하는 이유는?
875
+
876
+ 프로젝트마다 다른 날짜 라이브러리를 사용합니다. 어댑터 패턴을 통해:
877
+ - 추가 의존성 없이 기존 날짜 라이브러리 사용 가능
878
+ - 필요한 것만 포함하여 번들 크기 최소화
879
+ - 프로젝트의 날짜 처리 방식과 일관성 유지
880
+
881
+ ### STANDARD_PRESET과 TRADITIONAL_PRESET의 차이는?
882
+
883
+ **STANDARD_PRESET**은 현대적 관례 사용:
884
+ - 날짜가 자정(00:00)에 시작
885
+ - 현지 시계 시간 사용
886
+ - 일반적인 사용에 더 간단
887
+
888
+ **TRADITIONAL_PRESET**은 전통 중국 점성술을 따름:
889
+ - 날짜가 자시(23:00)에 시작
890
+ - 경도 기반 태양시 보정 적용
891
+ - 역사적으로 더 정확
892
+
893
+ ### 계산은 얼마나 정확한가요?
894
+
895
+ 이 라이브러리는 다음을 구현합니다:
896
+ - 일주를 위한 율리우스 적일 알고리즘 (모든 역사적 날짜에 정확)
897
+ - 월주를 위한 천문학적 태양 황경 계산
898
+ - 연주를 위한 입춘(봄의 시작) 계산
899
+ - 시주를 위한 전통 중국 시진(時辰) 체계
900
+
901
+ 모든 알고리즘은 알려진 역사적 날짜로 테스트되었으며 전통 중국 달력 참고자료와 일치합니다.
902
+
903
+ ### 역사적 날짜에도 사용할 수 있나요?
904
+
905
+ 네! 율리우스 적일 알고리즘은 다음에서 올바르게 작동합니다:
906
+ - 그레고리력의 모든 날짜 (1582년 이후)
907
+ - 율리우스력의 대부분 날짜 (적절한 달력 변환 포함)
908
+ - 먼 미래의 날짜
909
+
910
+ 다만, 약 1970년 이전 날짜의 타임존 데이터는 덜 정확할 수 있습니다.
911
+
912
+ ### 같은 출생 시간이 다른 프리셋에서 다른 결과를 주는 이유는?
913
+
914
+ 프리셋은 다음에 영향을 미칩니다:
915
+ 1. **날짜 경계**: 날짜가 실제로 변경되는 시점 (자정 vs. 23:00)
916
+ 2. **태양시**: 경도 차이에 대한 조정 여부
917
+
918
+ 예를 들어, 23:30은 다음과 같을 수 있습니다:
919
+ - 같은 날의 자시 (자정 경계 사용 시)
920
+ - 다음 날의 자시 (자시23 경계 사용 시)
921
+
922
+ 이는 의도적이며 사주 해석의 다양한 전통을 반영합니다.
923
+
924
+ ## 기여하기
925
+
926
+ 기여를 환영합니다! Pull Request를 자유롭게 제출해주세요.
927
+
928
+ 1. 저장소 포크
929
+ 2. feature 브랜치 생성 (`git checkout -b feature/amazing-feature`)
930
+ 3. 변경사항 커밋 (`git commit -m 'Add some amazing feature'`)
931
+ 4. 브랜치에 푸시 (`git push origin feature/amazing-feature`)
932
+ 5. Pull Request 오픈
933
+
934
+ ### 가이드라인
935
+
936
+ - 새 기능에 대한 테스트 작성
937
+ - 코드 커버리지 유지 또는 개선
938
+ - 기존 코드 스타일 따르기 (Biome으로 강제)
939
+ - 필요에 따라 문서 업데이트
940
+
941
+ ## 라이선스
942
+
943
+ MIT © [gracefullight](https://github.com/gracefullight)
944
+
945
+ ## 크레딧
946
+
947
+ 이 라이브러리는 사주명리(四柱命理)에 사용되는 전통 중국 역법 알고리즘과 천문 계산을 기반으로 합니다.
948
+
949
+ ## 관련 프로젝트
950
+
951
+ - [Luxon](https://moment.github.io/luxon/) - 현대적인 날짜/시간 라이브러리
952
+ - [date-fns](https://date-fns.org/) - 현대적인 JavaScript 날짜 유틸리티 라이브러리
953
+
954
+ ## 지원
955
+
956
+ - [문서](https://github.com/gracefullight/saju#readme)
957
+ - [이슈 트래커](https://github.com/gracefullight/saju/issues)
958
+ - [토론](https://github.com/gracefullight/saju/discussions)