@gracefullight/saju 0.1.0 → 0.2.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 +85 -21
- package/README.md +83 -19
- package/dist/__tests__/four-pillars.test.js +52 -40
- 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/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/lunar.d.ts +13 -0
- package/dist/core/lunar.d.ts.map +1 -0
- package/dist/core/lunar.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +4 -1
package/README.en.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @gracefullight/saju
|
|
2
2
|
|
|
3
|
-
> TypeScript library for calculating Four Pillars of Destiny (
|
|
3
|
+
> TypeScript library for calculating Four Pillars of Destiny (Saju, 四柱命理) with flexible date adapter support.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@gracefullight/saju)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
## What is Saju (四柱)?
|
|
21
21
|
|
|
22
|
-
Saju
|
|
22
|
+
Saju (Four Pillars of Destiny, 四柱命理) is a traditional Korean and Chinese divination system based on one's birth year, month, day, and hour. Each pillar consists of:
|
|
23
23
|
- **Heavenly Stem (天干)**: 10 elements (甲乙丙丁戊己庚辛壬癸)
|
|
24
24
|
- **Earthly Branch (地支)**: 12 zodiac signs (子丑寅卯辰巳午未申酉戌亥)
|
|
25
25
|
|
|
26
26
|
This library calculates these pillars using:
|
|
27
|
-
- **Lichun (
|
|
27
|
+
- **Lichun (立春, Start of Spring)** for year pillar transitions
|
|
28
28
|
- **Solar longitude** for month pillar determination
|
|
29
29
|
- **Julian Day Number** for day pillar calculation
|
|
30
|
-
- **Traditional Chinese hour system (
|
|
30
|
+
- **Traditional Chinese double-hour system (時辰, shichen)** for hour pillar
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
33
33
|
|
|
@@ -77,13 +77,19 @@ console.log(result);
|
|
|
77
77
|
// {
|
|
78
78
|
// year: "己卯", // Year Pillar (Heavenly Stem + Earthly Branch)
|
|
79
79
|
// month: "丙子", // Month Pillar
|
|
80
|
-
// day: "
|
|
81
|
-
// hour: "
|
|
80
|
+
// day: "辛巳", // Day Pillar
|
|
81
|
+
// hour: "戊戌", // Hour Pillar
|
|
82
|
+
// lunar: {
|
|
83
|
+
// lunarYear: 1999,
|
|
84
|
+
// lunarMonth: 11,
|
|
85
|
+
// lunarDay: 25,
|
|
86
|
+
// isLeapMonth: false
|
|
87
|
+
// },
|
|
82
88
|
// meta: {
|
|
83
|
-
//
|
|
89
|
+
// solarYearUsed: 1999,
|
|
84
90
|
// sunLonDeg: 280.9,
|
|
85
91
|
// effectiveDayDate: { year: 2000, month: 1, day: 1 },
|
|
86
|
-
//
|
|
92
|
+
// adjustedDtForHour: "2000-01-01T18:00:00.000+09:00"
|
|
87
93
|
// }
|
|
88
94
|
// }
|
|
89
95
|
```
|
|
@@ -126,7 +132,7 @@ import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
|
|
|
126
132
|
const adapter = await createDateFnsAdapter();
|
|
127
133
|
|
|
128
134
|
const dt = {
|
|
129
|
-
date: new Date(
|
|
135
|
+
date: new Date(1985, 4, 15, 14, 30), // Note: month is 0-indexed
|
|
130
136
|
timeZone: "Asia/Seoul",
|
|
131
137
|
};
|
|
132
138
|
|
|
@@ -179,7 +185,7 @@ const myAdapter: DateAdapter<MyDateType> = {
|
|
|
179
185
|
### Configuration Presets
|
|
180
186
|
|
|
181
187
|
#### `STANDARD_PRESET`
|
|
182
|
-
|
|
188
|
+
Contemporary interpretation with midnight day boundary and no solar time correction.
|
|
183
189
|
|
|
184
190
|
```typescript
|
|
185
191
|
{
|
|
@@ -228,11 +234,17 @@ function getFourPillars<T>(
|
|
|
228
234
|
month: string;
|
|
229
235
|
day: string;
|
|
230
236
|
hour: string;
|
|
237
|
+
lunar: {
|
|
238
|
+
lunarYear: number;
|
|
239
|
+
lunarMonth: number;
|
|
240
|
+
lunarDay: number;
|
|
241
|
+
isLeapMonth: boolean;
|
|
242
|
+
};
|
|
231
243
|
meta: {
|
|
232
|
-
|
|
244
|
+
solarYearUsed: number;
|
|
233
245
|
sunLonDeg: number;
|
|
234
246
|
effectiveDayDate: { year: number; month: number; day: number };
|
|
235
|
-
|
|
247
|
+
adjustedDtForHour: string;
|
|
236
248
|
};
|
|
237
249
|
}
|
|
238
250
|
```
|
|
@@ -245,7 +257,7 @@ function getFourPillars<T>(
|
|
|
245
257
|
- `preset`: Configuration preset (use `STANDARD_PRESET` or `TRADITIONAL_PRESET`)
|
|
246
258
|
- `tzOffsetHours`: Optional timezone offset in hours (default: 9 for KST)
|
|
247
259
|
|
|
248
|
-
**Returns:** Object with year, month, day, hour pillars and metadata
|
|
260
|
+
**Returns:** Object with year, month, day, hour pillars, lunar date, and metadata
|
|
249
261
|
|
|
250
262
|
#### `yearPillar(adapter, datetime)`
|
|
251
263
|
|
|
@@ -291,6 +303,58 @@ function dayPillarFromDate(date: {
|
|
|
291
303
|
}
|
|
292
304
|
```
|
|
293
305
|
|
|
306
|
+
### Lunar Conversion Functions
|
|
307
|
+
|
|
308
|
+
#### `getLunarDate(year, month, day)`
|
|
309
|
+
|
|
310
|
+
Convert a solar (Gregorian) date to a lunar date.
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
function getLunarDate(
|
|
314
|
+
year: number,
|
|
315
|
+
month: number,
|
|
316
|
+
day: number
|
|
317
|
+
): {
|
|
318
|
+
lunarYear: number;
|
|
319
|
+
lunarMonth: number;
|
|
320
|
+
lunarDay: number;
|
|
321
|
+
isLeapMonth: boolean;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Example:**
|
|
326
|
+
```typescript
|
|
327
|
+
import { getLunarDate } from "@gracefullight/saju";
|
|
328
|
+
|
|
329
|
+
const lunar = getLunarDate(2000, 1, 1);
|
|
330
|
+
// { lunarYear: 1999, lunarMonth: 11, lunarDay: 25, isLeapMonth: false }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### `getSolarDate(lunarYear, lunarMonth, lunarDay, isLeapMonth)`
|
|
334
|
+
|
|
335
|
+
Convert a lunar date to a solar (Gregorian) date.
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
function getSolarDate(
|
|
339
|
+
lunarYear: number,
|
|
340
|
+
lunarMonth: number,
|
|
341
|
+
lunarDay: number,
|
|
342
|
+
isLeapMonth?: boolean
|
|
343
|
+
): {
|
|
344
|
+
year: number;
|
|
345
|
+
month: number;
|
|
346
|
+
day: number;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Example:**
|
|
351
|
+
```typescript
|
|
352
|
+
import { getSolarDate } from "@gracefullight/saju";
|
|
353
|
+
|
|
354
|
+
const solar = getSolarDate(1999, 11, 25, false);
|
|
355
|
+
// { year: 2000, month: 1, day: 1 }
|
|
356
|
+
```
|
|
357
|
+
|
|
294
358
|
#### `hourPillar(adapter, datetime, options)`
|
|
295
359
|
|
|
296
360
|
Calculate only the hour pillar with optional solar time correction.
|
|
@@ -384,8 +448,8 @@ console.log(solarTime.hour); // ~11.47 (11:28)
|
|
|
384
448
|
|
|
385
449
|
**Midnight Mode** (`dayBoundary: "midnight"`):
|
|
386
450
|
- Day changes at 00:00 local time
|
|
387
|
-
- Simpler,
|
|
388
|
-
-
|
|
451
|
+
- Simpler, aligns with contemporary calendar systems
|
|
452
|
+
- Suitable for general use
|
|
389
453
|
|
|
390
454
|
**Zi Hour Mode** (`dayBoundary: "zi23"`):
|
|
391
455
|
- Day changes at 23:00 local time
|
|
@@ -410,7 +474,7 @@ Mix and match settings for specific needs:
|
|
|
410
474
|
|
|
411
475
|
```typescript
|
|
412
476
|
const customConfig = {
|
|
413
|
-
dayBoundary: "midnight" as const, //
|
|
477
|
+
dayBoundary: "midnight" as const, // Contemporary midnight boundary
|
|
414
478
|
useMeanSolarTimeForHour: true, // But use solar time for hour
|
|
415
479
|
useMeanSolarTimeForBoundary: false, // Local time for day boundary
|
|
416
480
|
};
|
|
@@ -445,7 +509,7 @@ const adapter = await createLuxonAdapter();
|
|
|
445
509
|
|
|
446
510
|
// New York birth time
|
|
447
511
|
const nyTime = DateTime.fromObject(
|
|
448
|
-
{ year:
|
|
512
|
+
{ year: 1985, month: 5, day: 15, hour: 6, minute: 30 },
|
|
449
513
|
{ zone: "America/New_York" }
|
|
450
514
|
);
|
|
451
515
|
|
|
@@ -470,7 +534,7 @@ const month = monthPillar(adapter, dt);
|
|
|
470
534
|
console.log(month.pillar, month.sunLonDeg);
|
|
471
535
|
|
|
472
536
|
// Day pillar (no adapter needed)
|
|
473
|
-
const day = dayPillarFromDate({ year:
|
|
537
|
+
const day = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
|
|
474
538
|
console.log(day.pillar);
|
|
475
539
|
|
|
476
540
|
// Hour pillar with solar time
|
|
@@ -583,12 +647,12 @@ Different projects use different date libraries. The adapter pattern allows you
|
|
|
583
647
|
|
|
584
648
|
### What's the difference between STANDARD_PRESET and TRADITIONAL_PRESET?
|
|
585
649
|
|
|
586
|
-
**STANDARD_PRESET** uses
|
|
650
|
+
**STANDARD_PRESET** uses contemporary conventions:
|
|
587
651
|
- Day starts at midnight (00:00)
|
|
588
652
|
- Uses local clock time
|
|
589
653
|
- Simpler for general use
|
|
590
654
|
|
|
591
|
-
**TRADITIONAL_PRESET** follows traditional Chinese astrology:
|
|
655
|
+
**TRADITIONAL_PRESET** follows traditional Chinese astrology practices:
|
|
592
656
|
- Day starts at Zi hour (23:00)
|
|
593
657
|
- Applies solar time correction based on longitude
|
|
594
658
|
- More historically accurate
|
|
@@ -622,7 +686,7 @@ For example, 23:30 could be:
|
|
|
622
686
|
- Same day's Zi hour (with midnight boundary)
|
|
623
687
|
- Next day's Zi hour (with Zi23 boundary)
|
|
624
688
|
|
|
625
|
-
This is intentional and reflects different
|
|
689
|
+
This is intentional and reflects different interpretative traditions in Saju analysis.
|
|
626
690
|
|
|
627
691
|
## Contributing
|
|
628
692
|
|
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
## 주요 기능
|
|
11
11
|
|
|
12
|
-
- **정확한 사주 계산** - 천문학적
|
|
12
|
+
- **정확한 사주 계산** - 천문학적 정확도로 전통 중국 역법 알고리즘 구현
|
|
13
13
|
- **유연한 날짜 어댑터 패턴** - Luxon, date-fns 또는 원하는 날짜 라이브러리 사용 가능
|
|
14
|
-
- **타임존 & 위치 지원** - 타임존 및 지리적
|
|
14
|
+
- **타임존 & 위치 지원** - 타임존 및 지리적 좌표를 올바르게 처리
|
|
15
15
|
- **태양시 보정** - 경도 기반 평균 태양시 조정 옵션
|
|
16
16
|
- **트리쉐이킹 지원** - 필요한 것만 import
|
|
17
17
|
- **완전한 타입 지원** - TypeScript 정의 완비
|
|
18
|
-
-
|
|
18
|
+
- **풍부한 테스트** - 85개 이상 테스트, 91% 이상 커버리지
|
|
19
19
|
|
|
20
20
|
## 사주(四柱)란?
|
|
21
21
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- **지지(地支)**: 12지지 (子丑寅卯辰巳午未申酉戌亥)
|
|
25
25
|
|
|
26
26
|
이 라이브러리는 다음을 사용하여 기둥을 계산합니다:
|
|
27
|
-
- **입춘(立春)** 을
|
|
27
|
+
- **입춘(立春)** 을 기준으로 한 연주 전환
|
|
28
28
|
- **태양 황경** 을 이용한 월주 결정
|
|
29
29
|
- **율리우스 적일** 을 이용한 일주 계산
|
|
30
30
|
- **전통 중국 시진(時辰) 체계** 를 이용한 시주
|
|
@@ -77,13 +77,19 @@ console.log(result);
|
|
|
77
77
|
// {
|
|
78
78
|
// year: "己卯", // 연주 (천간 + 지지)
|
|
79
79
|
// month: "丙子", // 월주
|
|
80
|
-
// day: "
|
|
81
|
-
// hour: "
|
|
80
|
+
// day: "辛巳", // 일주
|
|
81
|
+
// hour: "戊戌", // 시주
|
|
82
|
+
// lunar: {
|
|
83
|
+
// lunarYear: 1999,
|
|
84
|
+
// lunarMonth: 11,
|
|
85
|
+
// lunarDay: 25,
|
|
86
|
+
// isLeapMonth: false
|
|
87
|
+
// },
|
|
82
88
|
// meta: {
|
|
83
|
-
//
|
|
89
|
+
// solarYearUsed: 1999,
|
|
84
90
|
// sunLonDeg: 280.9,
|
|
85
91
|
// effectiveDayDate: { year: 2000, month: 1, day: 1 },
|
|
86
|
-
//
|
|
92
|
+
// adjustedDtForHour: "2000-01-01T18:00:00.000+09:00"
|
|
87
93
|
// }
|
|
88
94
|
// }
|
|
89
95
|
```
|
|
@@ -126,7 +132,7 @@ import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
|
|
|
126
132
|
const adapter = await createDateFnsAdapter();
|
|
127
133
|
|
|
128
134
|
const dt = {
|
|
129
|
-
date: new Date(
|
|
135
|
+
date: new Date(1985, 4, 15, 14, 30), // 주의: 월은 0부터 시작
|
|
130
136
|
timeZone: "Asia/Seoul",
|
|
131
137
|
};
|
|
132
138
|
|
|
@@ -179,7 +185,7 @@ const myAdapter: DateAdapter<MyDateType> = {
|
|
|
179
185
|
### 설정 프리셋
|
|
180
186
|
|
|
181
187
|
#### `STANDARD_PRESET`
|
|
182
|
-
자정 날짜 경계와 태양시 보정
|
|
188
|
+
현대적 해석: 자정 날짜 경계와 태양시 보정 없음
|
|
183
189
|
|
|
184
190
|
```typescript
|
|
185
191
|
{
|
|
@@ -190,7 +196,7 @@ const myAdapter: DateAdapter<MyDateType> = {
|
|
|
190
196
|
```
|
|
191
197
|
|
|
192
198
|
#### `TRADITIONAL_PRESET`
|
|
193
|
-
자시(23:00) 날짜 경계와 태양시
|
|
199
|
+
전통적 해석: 자시(23:00) 날짜 경계와 태양시 보정 사용
|
|
194
200
|
|
|
195
201
|
```typescript
|
|
196
202
|
{
|
|
@@ -228,11 +234,17 @@ function getFourPillars<T>(
|
|
|
228
234
|
month: string;
|
|
229
235
|
day: string;
|
|
230
236
|
hour: string;
|
|
237
|
+
lunar: {
|
|
238
|
+
lunarYear: number;
|
|
239
|
+
lunarMonth: number;
|
|
240
|
+
lunarDay: number;
|
|
241
|
+
isLeapMonth: boolean;
|
|
242
|
+
};
|
|
231
243
|
meta: {
|
|
232
|
-
|
|
244
|
+
solarYearUsed: number;
|
|
233
245
|
sunLonDeg: number;
|
|
234
246
|
effectiveDayDate: { year: number; month: number; day: number };
|
|
235
|
-
|
|
247
|
+
adjustedDtForHour: string;
|
|
236
248
|
};
|
|
237
249
|
}
|
|
238
250
|
```
|
|
@@ -245,7 +257,7 @@ function getFourPillars<T>(
|
|
|
245
257
|
- `preset`: 설정 프리셋 (`STANDARD_PRESET` 또는 `TRADITIONAL_PRESET` 사용)
|
|
246
258
|
- `tzOffsetHours`: 타임존 오프셋(시간 단위), 선택사항 (기본값: 9, KST)
|
|
247
259
|
|
|
248
|
-
**반환값:** 연월일시
|
|
260
|
+
**반환값:** 연월일시 기둥, 음력 날짜, 메타데이터를 포함한 객체
|
|
249
261
|
|
|
250
262
|
#### `yearPillar(adapter, datetime)`
|
|
251
263
|
|
|
@@ -291,6 +303,58 @@ function dayPillarFromDate(date: {
|
|
|
291
303
|
}
|
|
292
304
|
```
|
|
293
305
|
|
|
306
|
+
### 음력 변환 함수
|
|
307
|
+
|
|
308
|
+
#### `getLunarDate(year, month, day)`
|
|
309
|
+
|
|
310
|
+
양력(그레고리력) 날짜를 음력 날짜로 변환
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
function getLunarDate(
|
|
314
|
+
year: number,
|
|
315
|
+
month: number,
|
|
316
|
+
day: number
|
|
317
|
+
): {
|
|
318
|
+
lunarYear: number;
|
|
319
|
+
lunarMonth: number;
|
|
320
|
+
lunarDay: number;
|
|
321
|
+
isLeapMonth: boolean;
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**예시:**
|
|
326
|
+
```typescript
|
|
327
|
+
import { getLunarDate } from "@gracefullight/saju";
|
|
328
|
+
|
|
329
|
+
const lunar = getLunarDate(2000, 1, 1);
|
|
330
|
+
// { lunarYear: 1999, lunarMonth: 11, lunarDay: 25, isLeapMonth: false }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### `getSolarDate(lunarYear, lunarMonth, lunarDay, isLeapMonth)`
|
|
334
|
+
|
|
335
|
+
음력 날짜를 양력(그레고리력) 날짜로 변환
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
function getSolarDate(
|
|
339
|
+
lunarYear: number,
|
|
340
|
+
lunarMonth: number,
|
|
341
|
+
lunarDay: number,
|
|
342
|
+
isLeapMonth?: boolean
|
|
343
|
+
): {
|
|
344
|
+
year: number;
|
|
345
|
+
month: number;
|
|
346
|
+
day: number;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**예시:**
|
|
351
|
+
```typescript
|
|
352
|
+
import { getSolarDate } from "@gracefullight/saju";
|
|
353
|
+
|
|
354
|
+
const solar = getSolarDate(1999, 11, 25, false);
|
|
355
|
+
// { year: 2000, month: 1, day: 1 }
|
|
356
|
+
```
|
|
357
|
+
|
|
294
358
|
#### `hourPillar(adapter, datetime, options)`
|
|
295
359
|
|
|
296
360
|
태양시 보정 옵션과 함께 시주만 계산
|
|
@@ -445,7 +509,7 @@ const adapter = await createLuxonAdapter();
|
|
|
445
509
|
|
|
446
510
|
// 뉴욕 출생 시간
|
|
447
511
|
const nyTime = DateTime.fromObject(
|
|
448
|
-
{ year:
|
|
512
|
+
{ year: 1985, month: 5, day: 15, hour: 6, minute: 30 },
|
|
449
513
|
{ zone: "America/New_York" }
|
|
450
514
|
);
|
|
451
515
|
|
|
@@ -470,7 +534,7 @@ const month = monthPillar(adapter, dt);
|
|
|
470
534
|
console.log(month.pillar, month.sunLonDeg);
|
|
471
535
|
|
|
472
536
|
// 일주 (어댑터 불필요)
|
|
473
|
-
const day = dayPillarFromDate({ year:
|
|
537
|
+
const day = dayPillarFromDate({ year: 1985, month: 5, day: 15 });
|
|
474
538
|
console.log(day.pillar);
|
|
475
539
|
|
|
476
540
|
// 태양시를 사용한 시주
|
|
@@ -519,7 +583,7 @@ pnpm install
|
|
|
519
583
|
# 테스트 실행
|
|
520
584
|
pnpm test
|
|
521
585
|
|
|
522
|
-
#
|
|
586
|
+
# 커버리지 확인
|
|
523
587
|
pnpm test:coverage
|
|
524
588
|
|
|
525
589
|
# 빌드
|
|
@@ -588,7 +652,7 @@ All files | 91.45 | 80.68 | 96.55 | 91.45
|
|
|
588
652
|
- 현지 시계 시간 사용
|
|
589
653
|
- 일반적인 사용에 더 간단
|
|
590
654
|
|
|
591
|
-
**TRADITIONAL_PRESET**은 전통 중국
|
|
655
|
+
**TRADITIONAL_PRESET**은 전통 중국 점성술을 따름:
|
|
592
656
|
- 날짜가 자시(23:00)에 시작
|
|
593
657
|
- 경도 기반 태양시 보정 적용
|
|
594
658
|
- 역사적으로 더 정확
|
|
@@ -622,7 +686,7 @@ All files | 91.45 | 80.68 | 96.55 | 91.45
|
|
|
622
686
|
- 같은 날의 자시 (자정 경계 사용 시)
|
|
623
687
|
- 다음 날의 자시 (자시23 경계 사용 시)
|
|
624
688
|
|
|
625
|
-
이는 의도적이며 사주 해석의 다양한
|
|
689
|
+
이는 의도적이며 사주 해석의 다양한 전통을 반영합니다.
|
|
626
690
|
|
|
627
691
|
## 기여하기
|
|
628
692
|
|
|
@@ -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":"lunar.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lunar.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getLunarDate, getSolarDate } from "@/core/lunar";
|
|
3
|
+
describe("Lunar Calendar", () => {
|
|
4
|
+
describe("getLunarDate", () => {
|
|
5
|
+
it("should convert solar date to lunar date (2000-01-01)", () => {
|
|
6
|
+
const result = getLunarDate(2000, 1, 1);
|
|
7
|
+
expect(result.lunarYear).toBe(1999);
|
|
8
|
+
expect(result.lunarMonth).toBe(11);
|
|
9
|
+
expect(result.lunarDay).toBe(25);
|
|
10
|
+
expect(result.isLeapMonth).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it("should convert solar date to lunar date (1985-05-15)", () => {
|
|
13
|
+
const result = getLunarDate(1985, 5, 15);
|
|
14
|
+
expect(result.lunarYear).toBe(1985);
|
|
15
|
+
expect(result.lunarMonth).toBe(3);
|
|
16
|
+
expect(result.lunarDay).toBe(26);
|
|
17
|
+
expect(result.isLeapMonth).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it("should handle leap month correctly (2023-03-22 is in leap 2nd month)", () => {
|
|
20
|
+
const result = getLunarDate(2023, 3, 22);
|
|
21
|
+
expect(result.lunarYear).toBe(2023);
|
|
22
|
+
expect(result.lunarMonth).toBe(2);
|
|
23
|
+
expect(result.isLeapMonth).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it("should convert first day of lunar year", () => {
|
|
26
|
+
const result = getLunarDate(2024, 2, 10);
|
|
27
|
+
expect(result.lunarYear).toBe(2024);
|
|
28
|
+
expect(result.lunarMonth).toBe(1);
|
|
29
|
+
expect(result.lunarDay).toBe(1);
|
|
30
|
+
expect(result.isLeapMonth).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("getSolarDate", () => {
|
|
34
|
+
it("should convert lunar date to solar date", () => {
|
|
35
|
+
const result = getSolarDate(1999, 11, 25, false);
|
|
36
|
+
expect(result.year).toBe(2000);
|
|
37
|
+
expect(result.month).toBe(1);
|
|
38
|
+
expect(result.day).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
it("should convert lunar date (1985-03-26) to solar date", () => {
|
|
41
|
+
const result = getSolarDate(1985, 3, 26, false);
|
|
42
|
+
expect(result.year).toBe(1985);
|
|
43
|
+
expect(result.month).toBe(5);
|
|
44
|
+
expect(result.day).toBe(15);
|
|
45
|
+
});
|
|
46
|
+
it("should handle leap month in reverse conversion", () => {
|
|
47
|
+
const result = getSolarDate(2023, 2, 1, true);
|
|
48
|
+
expect(result.year).toBe(2023);
|
|
49
|
+
expect(result.month).toBe(3);
|
|
50
|
+
expect(result.day).toBe(22);
|
|
51
|
+
});
|
|
52
|
+
it("should convert first day of lunar year 2024", () => {
|
|
53
|
+
const result = getSolarDate(2024, 1, 1, false);
|
|
54
|
+
expect(result.year).toBe(2024);
|
|
55
|
+
expect(result.month).toBe(2);
|
|
56
|
+
expect(result.day).toBe(10);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("round-trip conversion", () => {
|
|
60
|
+
it("should convert solar -> lunar -> solar correctly", () => {
|
|
61
|
+
const original = { year: 2000, month: 6, day: 15 };
|
|
62
|
+
const lunar = getLunarDate(original.year, original.month, original.day);
|
|
63
|
+
const backToSolar = getSolarDate(lunar.lunarYear, lunar.lunarMonth, lunar.lunarDay, lunar.isLeapMonth);
|
|
64
|
+
expect(backToSolar.year).toBe(original.year);
|
|
65
|
+
expect(backToSolar.month).toBe(original.month);
|
|
66
|
+
expect(backToSolar.day).toBe(original.day);
|
|
67
|
+
});
|
|
68
|
+
it("should handle various dates in round-trip", () => {
|
|
69
|
+
const testDates = [
|
|
70
|
+
{ year: 1990, month: 3, day: 20 },
|
|
71
|
+
{ year: 2010, month: 8, day: 1 },
|
|
72
|
+
{ year: 2020, month: 12, day: 31 },
|
|
73
|
+
];
|
|
74
|
+
for (const date of testDates) {
|
|
75
|
+
const lunar = getLunarDate(date.year, date.month, date.day);
|
|
76
|
+
const backToSolar = getSolarDate(lunar.lunarYear, lunar.lunarMonth, lunar.lunarDay, lunar.isLeapMonth);
|
|
77
|
+
expect(backToSolar.year).toBe(date.year);
|
|
78
|
+
expect(backToSolar.month).toBe(date.month);
|
|
79
|
+
expect(backToSolar.day).toBe(date.day);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DateAdapter } from "@/adapters/date-adapter";
|
|
2
|
+
import { type LunarDate } from "@/core/lunar";
|
|
2
3
|
export declare const STEMS: string[];
|
|
3
4
|
export declare const BRANCHES: string[];
|
|
4
5
|
export declare const STANDARD_PRESET: {
|
|
@@ -73,6 +74,7 @@ export declare function getFourPillars<T>(adapter: DateAdapter<T>, dtLocal: T, {
|
|
|
73
74
|
month: string;
|
|
74
75
|
day: string;
|
|
75
76
|
hour: string;
|
|
77
|
+
lunar: LunarDate;
|
|
76
78
|
meta: {
|
|
77
79
|
solarYearUsed: number;
|
|
78
80
|
sunLonDeg: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"four-pillars.d.ts","sourceRoot":"","sources":["../../src/core/four-pillars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"four-pillars.d.ts","sourceRoot":"","sources":["../../src/core/four-pillars.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLunarDate } from "@/core/lunar";
|
|
1
2
|
export const STEMS = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
|
|
2
3
|
export const BRANCHES = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
|
|
3
4
|
export const STANDARD_PRESET = {
|
|
@@ -33,7 +34,7 @@ function jdnFromDate(y, m, d) {
|
|
|
33
34
|
}
|
|
34
35
|
export function dayPillarFromDate({ year, month, day, }) {
|
|
35
36
|
const jdn = jdnFromDate(year, month, day);
|
|
36
|
-
const idx60 = (jdn +
|
|
37
|
+
const idx60 = (((jdn - 11) % 60) + 60) % 60;
|
|
37
38
|
return { idx60, pillar: pillarFrom60(idx60) };
|
|
38
39
|
}
|
|
39
40
|
export function applyMeanSolarTime(adapter, dtLocal, longitudeDeg, tzOffsetHours = 9) {
|
|
@@ -160,9 +161,9 @@ function hourBranchIndexFromHour(h) {
|
|
|
160
161
|
// Traditional Chinese hours (時辰) mapping:
|
|
161
162
|
// Each branch represents a 2-hour period, starting from 子時 at 23:00
|
|
162
163
|
// 子時 (0): 23:00-01:00, 丑時 (1): 01:00-03:00, ..., 亥時 (11): 21:00-23:00
|
|
163
|
-
// Formula: (hour + 1) / 2
|
|
164
|
-
//
|
|
165
|
-
return Math.floor((h + 1) / 2
|
|
164
|
+
// Formula: floor((hour + 1) / 2) % 12
|
|
165
|
+
// Examples: 0->0(子), 1->1(丑), 3->2(寅), 23->0(子)
|
|
166
|
+
return Math.floor((h + 1) / 2) % 12;
|
|
166
167
|
}
|
|
167
168
|
export function hourPillar(adapter, dtLocal, { longitudeDeg, tzOffsetHours = 9, useMeanSolarTimeForHour = false, dayBoundary = "midnight", useMeanSolarTimeForBoundary = false, } = {}) {
|
|
168
169
|
let dtUsed = dtLocal;
|
|
@@ -205,11 +206,13 @@ export function getFourPillars(adapter, dtLocal, { longitudeDeg, tzOffsetHours =
|
|
|
205
206
|
dayBoundary,
|
|
206
207
|
useMeanSolarTimeForBoundary,
|
|
207
208
|
});
|
|
209
|
+
const lunar = getLunarDate(effDate.year, effDate.month, effDate.day);
|
|
208
210
|
return {
|
|
209
211
|
year: y.pillar,
|
|
210
212
|
month: m.pillar,
|
|
211
213
|
day: d.pillar,
|
|
212
214
|
hour: h.pillar,
|
|
215
|
+
lunar,
|
|
213
216
|
meta: {
|
|
214
217
|
solarYearUsed: y.solarYear,
|
|
215
218
|
sunLonDeg: m.sunLonDeg,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface LunarDate {
|
|
2
|
+
lunarYear: number;
|
|
3
|
+
lunarMonth: number;
|
|
4
|
+
lunarDay: number;
|
|
5
|
+
isLeapMonth: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function getLunarDate(year: number, month: number, day: number): LunarDate;
|
|
8
|
+
export declare function getSolarDate(lunarYear: number, lunarMonth: number, lunarDay: number, isLeapMonth?: boolean): {
|
|
9
|
+
year: number;
|
|
10
|
+
month: number;
|
|
11
|
+
day: number;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=lunar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lunar.d.ts","sourceRoot":"","sources":["../../src/core/lunar.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAYhF;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,UAAQ,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAW9C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Lunar, Solar } from "lunar-javascript";
|
|
2
|
+
export function getLunarDate(year, month, day) {
|
|
3
|
+
const solar = Solar.fromYmd(year, month, day);
|
|
4
|
+
const lunar = solar.getLunar();
|
|
5
|
+
const rawMonth = lunar.getMonth();
|
|
6
|
+
const isLeapMonth = rawMonth < 0;
|
|
7
|
+
return {
|
|
8
|
+
lunarYear: lunar.getYear(),
|
|
9
|
+
lunarMonth: isLeapMonth ? Math.abs(rawMonth) : rawMonth,
|
|
10
|
+
lunarDay: lunar.getDay(),
|
|
11
|
+
isLeapMonth,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function getSolarDate(lunarYear, lunarMonth, lunarDay, isLeapMonth = false) {
|
|
15
|
+
// lunar-javascript uses negative month for leap months
|
|
16
|
+
const month = isLeapMonth ? -lunarMonth : lunarMonth;
|
|
17
|
+
const lunar = Lunar.fromYmd(lunarYear, month, lunarDay);
|
|
18
|
+
const solar = lunar.getSolar();
|
|
19
|
+
return {
|
|
20
|
+
year: solar.getYear(),
|
|
21
|
+
month: solar.getMonth(),
|
|
22
|
+
day: solar.getDay(),
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type { DateAdapter } from "@/adapters/date-adapter";
|
|
2
|
+
export { getLunarDate, getSolarDate, type LunarDate } from "@/core/lunar";
|
|
2
3
|
export { applyMeanSolarTime, BRANCHES, dayPillarFromDate, effectiveDayDate, getFourPillars, hourPillar, monthPillar, presetA, presetB, STANDARD_PRESET, STEMS, TRADITIONAL_PRESET, yearPillar, } from "@/core/four-pillars";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EACL,kBAAkB,EAClB,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,WAAW,EACX,OAAO,EACP,OAAO,EACP,eAAe,EACf,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,EACL,kBAAkB,EAClB,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,WAAW,EACX,OAAO,EACP,OAAO,EACP,eAAe,EACf,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
+
export { getLunarDate, getSolarDate } from "@/core/lunar";
|
|
1
2
|
export { applyMeanSolarTime, BRANCHES, dayPillarFromDate, effectiveDayDate, getFourPillars, hourPillar, monthPillar, presetA, presetB, STANDARD_PRESET, STEMS, TRADITIONAL_PRESET, yearPillar, } from "@/core/four-pillars";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gracefullight/saju",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Four Pillars (四柱) calculator with flexible date adapter support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,5 +66,8 @@
|
|
|
66
66
|
"date-fns-tz": "^3.2.0",
|
|
67
67
|
"luxon": "^3.5.0",
|
|
68
68
|
"vitest": "^2.1.8"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"lunar-javascript": "^1.7.7"
|
|
69
72
|
}
|
|
70
73
|
}
|