saju8z 1.0.0 → 1.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.md +405 -108
- package/bin/saju8z.js +160 -0
- package/dist/core.cjs +168 -37
- package/dist/core.js +168 -37
- package/dist/format.cjs +234 -55
- package/dist/format.js +234 -55
- package/dist/index.cjs +579 -328
- package/dist/index.js +579 -328
- package/dist/lunar.cjs +4 -4
- package/dist/lunar.js +4 -4
- package/dist/types/application/calculateSajuChart.d.ts +7 -1
- package/dist/types/application/normalizeDateTime.d.ts +7 -0
- package/dist/types/calendar/dateMath.d.ts +2 -1
- package/dist/types/calendar/solarTerms.d.ts +1 -0
- package/dist/types/config/saju8z.d.ts +1 -1
- package/dist/types/contracts/sajuChart.d.ts +21 -4
- package/dist/types/format.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/input/parseSajuChartInput.d.ts +10 -0
- package/dist/types/output/chartPresentation.d.ts +14 -0
- package/dist/types/output/createSajuPrompt.d.ts +1 -0
- package/dist/types/output/formatSajuChart.d.ts +1 -1
- package/dist/types/output/formatSajuText.d.ts +2 -0
- package/dist/types/saju/calculateDayPillar.d.ts +1 -0
- package/dist/types/saju/calculateMonthPillar.d.ts +1 -0
- package/dist/types/saju/calculateTenGods.d.ts +1 -0
- package/dist/types/saju/calculateThreePillars.d.ts +3 -0
- package/dist/types-cjs/application/calculateSajuChart.d.ts +7 -1
- package/dist/types-cjs/application/normalizeDateTime.d.ts +7 -0
- package/dist/types-cjs/calendar/dateMath.d.ts +2 -1
- package/dist/types-cjs/calendar/solarTerms.d.ts +1 -0
- package/dist/types-cjs/config/saju8z.d.ts +1 -1
- package/dist/types-cjs/contracts/sajuChart.d.ts +21 -4
- package/dist/types-cjs/format.d.ts +1 -1
- package/dist/types-cjs/index.d.ts +1 -1
- package/dist/types-cjs/input/parseSajuChartInput.d.ts +10 -0
- package/dist/types-cjs/output/chartPresentation.d.ts +14 -0
- package/dist/types-cjs/output/createSajuPrompt.d.ts +1 -0
- package/dist/types-cjs/output/formatSajuChart.d.ts +1 -1
- package/dist/types-cjs/output/formatSajuText.d.ts +2 -0
- package/dist/types-cjs/saju/calculateDayPillar.d.ts +1 -0
- package/dist/types-cjs/saju/calculateMonthPillar.d.ts +1 -0
- package/dist/types-cjs/saju/calculateTenGods.d.ts +1 -0
- package/dist/types-cjs/saju/calculateThreePillars.d.ts +3 -0
- package/package.json +5 -1
package/bin/saju8z.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { calculateSajuChart, formatSajuChart, UserInputError } from "../dist/index.js";
|
|
5
|
+
|
|
6
|
+
const HELP_TEXT = `사용법: saju8z
|
|
7
|
+
saju8z --date YYYY-MM-DD [--time HH:mm] [--gmt ±HH:mm] [옵션]
|
|
8
|
+
saju8z --dt YYYYMMDD[HHmm] [--gmt ±HH:mm] [옵션]
|
|
9
|
+
|
|
10
|
+
인자 없이 실행하면 날짜와 시각을 차례로 입력받습니다.
|
|
11
|
+
날짜 예: 1998-03-05 / 시각 예: 11:00 (24시간제, 모르면 비워두고 Enter)
|
|
12
|
+
대화형 입력은 양력·현재 GMT 시차·text 출력을 사용합니다.
|
|
13
|
+
|
|
14
|
+
옵션으로 실행할 때 필수 입력 (둘 중 하나):
|
|
15
|
+
--dt 생년월일 8자리 (YYYYMMDD) 또는 생년월일시 12자리 (YYYYMMDDHHmm)
|
|
16
|
+
--date 생년월일 (YYYY-MM-DD)
|
|
17
|
+
--time 출생 시각 (선택, HH:mm, 00:00~23:59). 생략하면 삼주만 계산
|
|
18
|
+
--date 또는 --dt는 필수이며, --dt와 --date·--time은 혼용할 수 없습니다.
|
|
19
|
+
시간이 없으면 입력 날짜 기준으로 삼주를 계산하고 GMT 환산·-30분 보정은 생략합니다.
|
|
20
|
+
|
|
21
|
+
옵션:
|
|
22
|
+
--gmt 고정 GMT 시차 (기본: 실행 환경의 현재 시차, -12:00~+14:00)
|
|
23
|
+
--calendar solar (기본) 또는 lunar (한국 음력)
|
|
24
|
+
--leap 음력 윤달 지정 (--calendar lunar와 함께 사용)
|
|
25
|
+
--format text (기본 만세력 텍스트), json 또는 prompt (AI 전달용)
|
|
26
|
+
--help, -h 도움말
|
|
27
|
+
|
|
28
|
+
예제:
|
|
29
|
+
saju8z
|
|
30
|
+
saju8z --dt 19980305
|
|
31
|
+
saju8z --dt 199803051100
|
|
32
|
+
saju8z --date 2000-02-29 --time 00:10
|
|
33
|
+
saju8z --date 2000-02-29 --time 00:10 --gmt +09:00
|
|
34
|
+
saju8z --date 2000-02-29 --time 00:10 --gmt=-05:00 --format json
|
|
35
|
+
saju8z --date 2000-02-29 --time 00:10 --format prompt
|
|
36
|
+
saju8z --date 2017-05-01 --time 12:00 --gmt +09:00 --calendar lunar --leap
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
function getCurrentGmtOffset() {
|
|
40
|
+
const minutes = -new Date().getTimezoneOffset();
|
|
41
|
+
const sign = minutes < 0 ? "-" : "+";
|
|
42
|
+
const absolute = Math.abs(minutes);
|
|
43
|
+
const hours = String(Math.floor(absolute / 60)).padStart(2, "0");
|
|
44
|
+
const remainder = String(absolute % 60).padStart(2, "0");
|
|
45
|
+
return `${sign}${hours}:${remainder}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readInteractiveArguments() {
|
|
49
|
+
const reader = createInterface({
|
|
50
|
+
input: process.stdin,
|
|
51
|
+
output: process.stderr,
|
|
52
|
+
terminal: Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
53
|
+
crlfDelay: Infinity
|
|
54
|
+
});
|
|
55
|
+
const answers = reader[Symbol.asyncIterator]();
|
|
56
|
+
/** @param {string} prompt */
|
|
57
|
+
async function ask(prompt) {
|
|
58
|
+
process.stderr.write(prompt);
|
|
59
|
+
const answer = await answers.next();
|
|
60
|
+
if (answer.done) {
|
|
61
|
+
throw new UserInputError("입력이 종료되었습니다. --date 또는 --dt를 사용하세요.");
|
|
62
|
+
}
|
|
63
|
+
return answer.value.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
process.stderr.write("생년월일과 출생 시각을 입력하세요. 양력·현재 GMT 시차로 계산합니다.\n");
|
|
68
|
+
const date = await ask("생년월일 (YYYY-MM-DD, 예: 1998-03-05): ");
|
|
69
|
+
const time = await ask("출생 시각 (HH:mm, 예: 11:00 / 시간을 모르면 비워두고 Enter): ");
|
|
70
|
+
return time ? ["--date", date, "--time", time] : ["--date", date];
|
|
71
|
+
} finally {
|
|
72
|
+
reader.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const VALUE_OPTIONS = new Set(["--dt", "--date", "--time", "--gmt", "--calendar", "--format"]);
|
|
77
|
+
|
|
78
|
+
/** @param {string[]} args */
|
|
79
|
+
function parseOptions(args) {
|
|
80
|
+
/** @type {Map<string, string>} */
|
|
81
|
+
const values = new Map();
|
|
82
|
+
let leapMonth = false;
|
|
83
|
+
for (let index = 0; index < args.length; index++) {
|
|
84
|
+
const argument = args[index];
|
|
85
|
+
const separator = argument.indexOf("=");
|
|
86
|
+
const option = separator < 0 ? argument : argument.slice(0, separator);
|
|
87
|
+
if (option === "--leap" && separator < 0) {
|
|
88
|
+
if (leapMonth) throw new UserInputError("중복된 옵션입니다: --leap");
|
|
89
|
+
leapMonth = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!VALUE_OPTIONS.has(option)) throw new UserInputError(`지원하지 않는 옵션입니다: ${option}`);
|
|
93
|
+
if (values.has(option)) throw new UserInputError(`중복된 옵션입니다: ${option}`);
|
|
94
|
+
const value = separator < 0 ? args[++index] : argument.slice(separator + 1);
|
|
95
|
+
if (!value || value.startsWith("--")) throw new UserInputError(`${option} 값을 입력하세요.`);
|
|
96
|
+
values.set(option, value);
|
|
97
|
+
}
|
|
98
|
+
return { values, leapMonth };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** @param {Map<string, string>} values */
|
|
102
|
+
function resolveDateTime(values) {
|
|
103
|
+
const dt = values.get("--dt");
|
|
104
|
+
if (dt !== undefined) {
|
|
105
|
+
if (values.has("--date") || values.has("--time")) {
|
|
106
|
+
throw new UserInputError("--dt는 --date 또는 --time과 함께 사용할 수 없습니다.");
|
|
107
|
+
}
|
|
108
|
+
if (![8, 12].includes(dt.length) || !/^\d+$/.test(dt)) {
|
|
109
|
+
throw new UserInputError(
|
|
110
|
+
"--dt는 YYYYMMDD 형식의 8자리 또는 YYYYMMDDHHmm 형식의 12자리 숫자여야 합니다."
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
date: `${dt.slice(0, 4)}-${dt.slice(4, 6)}-${dt.slice(6, 8)}`,
|
|
115
|
+
...(dt.length === 12 ? { time: `${dt.slice(8, 10)}:${dt.slice(10, 12)}` } : {})
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const date = values.get("--date");
|
|
119
|
+
const time = values.get("--time");
|
|
120
|
+
if (!date) {
|
|
121
|
+
throw new UserInputError("--date 또는 --dt를 입력하세요.");
|
|
122
|
+
}
|
|
123
|
+
return { date, ...(time === undefined ? {} : { time }) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {string[]} args */
|
|
127
|
+
function parseArguments(args) {
|
|
128
|
+
const { values, leapMonth } = parseOptions(args);
|
|
129
|
+
const { date, time } = resolveDateTime(values);
|
|
130
|
+
const gmt = values.get("--gmt") ?? getCurrentGmtOffset();
|
|
131
|
+
const calendar = values.get("--calendar") ?? "solar";
|
|
132
|
+
if (calendar !== "solar" && calendar !== "lunar")
|
|
133
|
+
throw new UserInputError("달력은 solar 또는 lunar여야 합니다.");
|
|
134
|
+
const format = values.get("--format") ?? "text";
|
|
135
|
+
if (format !== "text" && format !== "json" && format !== "prompt")
|
|
136
|
+
throw new UserInputError("출력 형식은 text, json 또는 prompt이어야 합니다.");
|
|
137
|
+
return {
|
|
138
|
+
input: { date, ...(time === undefined ? {} : { time }), gmt, calendar, leapMonth },
|
|
139
|
+
format
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const args = process.argv.slice(2);
|
|
145
|
+
const showHelp = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
|
|
146
|
+
if (showHelp) {
|
|
147
|
+
process.stdout.write(HELP_TEXT);
|
|
148
|
+
} else {
|
|
149
|
+
const inputArgs = args.length === 0 ? await readInteractiveArguments() : args;
|
|
150
|
+
const { input, format } = parseArguments(inputArgs);
|
|
151
|
+
const result = calculateSajuChart(input);
|
|
152
|
+
const leadingNewline = format === "text" ? "\n" : "";
|
|
153
|
+
process.stdout.write(`${leadingNewline}${formatSajuChart(result, format)}\n`);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
process.stderr.write(
|
|
157
|
+
`saju8z: ${error instanceof Error ? error.message : "계산에 실패했습니다."}\n도움말: saju8z --help\n`
|
|
158
|
+
);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
}
|
package/dist/core.cjs
CHANGED
|
@@ -37,7 +37,9 @@ __export(core_exports, {
|
|
|
37
37
|
addMinutes: () => addMinutes,
|
|
38
38
|
applySolarTimeCorrection: () => applySolarTimeCorrection,
|
|
39
39
|
branchIndex: () => branchIndex,
|
|
40
|
+
calculateDateTenGods: () => calculateDateTenGods,
|
|
40
41
|
calculateDayPillar: () => calculateDayPillar,
|
|
42
|
+
calculateDayPillarFromDate: () => calculateDayPillarFromDate,
|
|
41
43
|
calculateFourPillars: () => calculateFourPillars,
|
|
42
44
|
calculateHourBranch: () => calculateHourBranch,
|
|
43
45
|
calculateHourPillar: () => calculateHourPillar,
|
|
@@ -45,10 +47,12 @@ __export(core_exports, {
|
|
|
45
47
|
calculateTenGod: () => calculateTenGod,
|
|
46
48
|
calculateTenGods: () => calculateTenGods,
|
|
47
49
|
calculateYearPillar: () => calculateYearPillar,
|
|
50
|
+
compareLocalDate: () => compareLocalDate,
|
|
48
51
|
compareLocalDateTime: () => compareLocalDateTime,
|
|
49
52
|
daysInMonth: () => daysInMonth,
|
|
50
53
|
formatLocalDateTime: () => formatLocalDateTime,
|
|
51
54
|
formatPillar: () => formatPillar,
|
|
55
|
+
getMonthBranchBySolarDate: () => getMonthBranchBySolarDate,
|
|
52
56
|
getMonthBranchBySolarTerm: () => getMonthBranchBySolarTerm,
|
|
53
57
|
getSolarTermDateTime: () => getSolarTermDateTime,
|
|
54
58
|
hasLunarLeapMonth: () => hasLunarLeapMonth,
|
|
@@ -58,6 +62,7 @@ __export(core_exports, {
|
|
|
58
62
|
lunarMonthDays: () => lunarMonthDays,
|
|
59
63
|
lunarToSolar: () => lunarToSolar,
|
|
60
64
|
monthBranchesInOrder: () => monthBranchesInOrder,
|
|
65
|
+
monthPillarFromBranch: () => monthPillarFromBranch,
|
|
61
66
|
pillarFromIndex: () => pillarFromIndex,
|
|
62
67
|
positiveModulo: () => positiveModulo,
|
|
63
68
|
sexagenaryIndexOfPillar: () => sexagenaryIndexOfPillar,
|
|
@@ -74,7 +79,7 @@ var SUPPORTED_START_YEAR = 1900;
|
|
|
74
79
|
var SUPPORTED_END_YEAR = 2100;
|
|
75
80
|
|
|
76
81
|
// src/config/saju8z.ts
|
|
77
|
-
var SAJU8Z_VERSION = "1.
|
|
82
|
+
var SAJU8Z_VERSION = "1.2.0";
|
|
78
83
|
var SAJU8Z_CALCULATION_PROFILE = "saju8z-fixed-gmt-kst-minus30-v1";
|
|
79
84
|
var SAJU8Z_CORRECTION_MINUTES = -30;
|
|
80
85
|
|
|
@@ -116,18 +121,112 @@ var EARTHLY_BRANCHES = [
|
|
|
116
121
|
"\uD574"
|
|
117
122
|
];
|
|
118
123
|
var BRANCH_INFO = {
|
|
119
|
-
\uC790: {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
\
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
\uC790: {
|
|
125
|
+
name: "\uC790",
|
|
126
|
+
element: "\u6C34",
|
|
127
|
+
mainHiddenStem: "\uACC4",
|
|
128
|
+
hiddenStems: [{ stem: "\uACC4", role: "main" }]
|
|
129
|
+
},
|
|
130
|
+
\uCD95: {
|
|
131
|
+
name: "\uCD95",
|
|
132
|
+
element: "\u571F",
|
|
133
|
+
mainHiddenStem: "\uAE30",
|
|
134
|
+
hiddenStems: [
|
|
135
|
+
{ stem: "\uAE30", role: "main" },
|
|
136
|
+
{ stem: "\uC2E0", role: "middle" },
|
|
137
|
+
{ stem: "\uACC4", role: "initial" }
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
\uC778: {
|
|
141
|
+
name: "\uC778",
|
|
142
|
+
element: "\u6728",
|
|
143
|
+
mainHiddenStem: "\uAC11",
|
|
144
|
+
hiddenStems: [
|
|
145
|
+
{ stem: "\uAC11", role: "main" },
|
|
146
|
+
{ stem: "\uBCD1", role: "middle" },
|
|
147
|
+
{ stem: "\uBB34", role: "initial" }
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
\uBB18: {
|
|
151
|
+
name: "\uBB18",
|
|
152
|
+
element: "\u6728",
|
|
153
|
+
mainHiddenStem: "\uC744",
|
|
154
|
+
hiddenStems: [{ stem: "\uC744", role: "main" }]
|
|
155
|
+
},
|
|
156
|
+
\uC9C4: {
|
|
157
|
+
name: "\uC9C4",
|
|
158
|
+
element: "\u571F",
|
|
159
|
+
mainHiddenStem: "\uBB34",
|
|
160
|
+
hiddenStems: [
|
|
161
|
+
{ stem: "\uBB34", role: "main" },
|
|
162
|
+
{ stem: "\uC744", role: "middle" },
|
|
163
|
+
{ stem: "\uACC4", role: "initial" }
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
\uC0AC: {
|
|
167
|
+
name: "\uC0AC",
|
|
168
|
+
element: "\u706B",
|
|
169
|
+
mainHiddenStem: "\uBCD1",
|
|
170
|
+
hiddenStems: [
|
|
171
|
+
{ stem: "\uBCD1", role: "main" },
|
|
172
|
+
{ stem: "\uACBD", role: "middle" },
|
|
173
|
+
{ stem: "\uBB34", role: "initial" }
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
\uC624: {
|
|
177
|
+
name: "\uC624",
|
|
178
|
+
element: "\u706B",
|
|
179
|
+
mainHiddenStem: "\uC815",
|
|
180
|
+
hiddenStems: [
|
|
181
|
+
{ stem: "\uC815", role: "main" },
|
|
182
|
+
{ stem: "\uAE30", role: "middle" }
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
\uBBF8: {
|
|
186
|
+
name: "\uBBF8",
|
|
187
|
+
element: "\u571F",
|
|
188
|
+
mainHiddenStem: "\uAE30",
|
|
189
|
+
hiddenStems: [
|
|
190
|
+
{ stem: "\uAE30", role: "main" },
|
|
191
|
+
{ stem: "\uC815", role: "middle" },
|
|
192
|
+
{ stem: "\uC744", role: "initial" }
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
\uC2E0: {
|
|
196
|
+
name: "\uC2E0",
|
|
197
|
+
element: "\u91D1",
|
|
198
|
+
mainHiddenStem: "\uACBD",
|
|
199
|
+
hiddenStems: [
|
|
200
|
+
{ stem: "\uACBD", role: "main" },
|
|
201
|
+
{ stem: "\uC784", role: "middle" },
|
|
202
|
+
{ stem: "\uBB34", role: "initial" }
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
\uC720: {
|
|
206
|
+
name: "\uC720",
|
|
207
|
+
element: "\u91D1",
|
|
208
|
+
mainHiddenStem: "\uC2E0",
|
|
209
|
+
hiddenStems: [{ stem: "\uC2E0", role: "main" }]
|
|
210
|
+
},
|
|
211
|
+
\uC220: {
|
|
212
|
+
name: "\uC220",
|
|
213
|
+
element: "\u571F",
|
|
214
|
+
mainHiddenStem: "\uBB34",
|
|
215
|
+
hiddenStems: [
|
|
216
|
+
{ stem: "\uBB34", role: "main" },
|
|
217
|
+
{ stem: "\uC2E0", role: "middle" },
|
|
218
|
+
{ stem: "\uC815", role: "initial" }
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
\uD574: {
|
|
222
|
+
name: "\uD574",
|
|
223
|
+
element: "\u6C34",
|
|
224
|
+
mainHiddenStem: "\uC784",
|
|
225
|
+
hiddenStems: [
|
|
226
|
+
{ stem: "\uC784", role: "main" },
|
|
227
|
+
{ stem: "\uAC11", role: "middle" }
|
|
228
|
+
]
|
|
229
|
+
}
|
|
131
230
|
};
|
|
132
231
|
function branchIndex(branch) {
|
|
133
232
|
return EARTHLY_BRANCHES.indexOf(branch);
|
|
@@ -206,6 +305,8 @@ function stemAt(index) {
|
|
|
206
305
|
}
|
|
207
306
|
|
|
208
307
|
// src/calendar/dateMath.ts
|
|
308
|
+
var MINUTES_PER_HOUR = 60;
|
|
309
|
+
var MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
|
209
310
|
function isLeapYear(year) {
|
|
210
311
|
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
211
312
|
}
|
|
@@ -216,11 +317,14 @@ function daysInMonth(year, month) {
|
|
|
216
317
|
function isValidDate(year, month, day) {
|
|
217
318
|
return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth(year, month);
|
|
218
319
|
}
|
|
219
|
-
function
|
|
320
|
+
function compareLocalDate(left, right) {
|
|
321
|
+
return left.year - right.year || left.month - right.month || left.day - right.day;
|
|
322
|
+
}
|
|
323
|
+
function compareLocalDateTime(left, right) {
|
|
220
324
|
const keys = ["year", "month", "day", "hour", "minute"];
|
|
221
325
|
for (const key of keys) {
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
326
|
+
const difference = left[key] - right[key];
|
|
327
|
+
if (difference !== 0) return difference;
|
|
224
328
|
}
|
|
225
329
|
return 0;
|
|
226
330
|
}
|
|
@@ -228,9 +332,9 @@ function addMinutes(dateTime, minutes) {
|
|
|
228
332
|
let year = dateTime.year;
|
|
229
333
|
let month = dateTime.month;
|
|
230
334
|
let day = dateTime.day;
|
|
231
|
-
let totalMinutes = dateTime.hour *
|
|
335
|
+
let totalMinutes = dateTime.hour * MINUTES_PER_HOUR + dateTime.minute + minutes;
|
|
232
336
|
while (totalMinutes < 0) {
|
|
233
|
-
totalMinutes +=
|
|
337
|
+
totalMinutes += MINUTES_PER_DAY;
|
|
234
338
|
day -= 1;
|
|
235
339
|
if (day < 1) {
|
|
236
340
|
month -= 1;
|
|
@@ -241,8 +345,8 @@ function addMinutes(dateTime, minutes) {
|
|
|
241
345
|
day = daysInMonth(year, month);
|
|
242
346
|
}
|
|
243
347
|
}
|
|
244
|
-
while (totalMinutes >=
|
|
245
|
-
totalMinutes -=
|
|
348
|
+
while (totalMinutes >= MINUTES_PER_DAY) {
|
|
349
|
+
totalMinutes -= MINUTES_PER_DAY;
|
|
246
350
|
day += 1;
|
|
247
351
|
if (day > daysInMonth(year, month)) {
|
|
248
352
|
day = 1;
|
|
@@ -257,18 +361,21 @@ function addMinutes(dateTime, minutes) {
|
|
|
257
361
|
year,
|
|
258
362
|
month,
|
|
259
363
|
day,
|
|
260
|
-
hour: Math.floor(totalMinutes /
|
|
261
|
-
minute: totalMinutes %
|
|
364
|
+
hour: Math.floor(totalMinutes / MINUTES_PER_HOUR),
|
|
365
|
+
minute: totalMinutes % MINUTES_PER_HOUR
|
|
262
366
|
};
|
|
263
367
|
}
|
|
264
368
|
function julianDayNumber(year, month, day) {
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
return day + Math.floor((153 *
|
|
369
|
+
const yearAdjustment = Math.floor((14 - month) / 12);
|
|
370
|
+
const adjustedYear = year + 4800 - yearAdjustment;
|
|
371
|
+
const adjustedMonth = month + 12 * yearAdjustment - 3;
|
|
372
|
+
return day + Math.floor((153 * adjustedMonth + 2) / 5) + 365 * adjustedYear + Math.floor(adjustedYear / 4) - Math.floor(adjustedYear / 100) + Math.floor(adjustedYear / 400) - 32045;
|
|
269
373
|
}
|
|
270
374
|
function formatLocalDateTime(dateTime) {
|
|
271
|
-
|
|
375
|
+
const pad = (value) => value.toString().padStart(2, "0");
|
|
376
|
+
const date = `${dateTime.year.toString().padStart(4, "0")}-${pad(dateTime.month)}-${pad(dateTime.day)}`;
|
|
377
|
+
const time = `${pad(dateTime.hour)}:${pad(dateTime.minute)}`;
|
|
378
|
+
return `${date}T${time}`;
|
|
272
379
|
}
|
|
273
380
|
|
|
274
381
|
// node_modules/korean-lunar-calendar/dist/korean-lunar-calendar.mjs
|
|
@@ -1672,16 +1779,16 @@ function lunarMonthDays(year, month, leap) {
|
|
|
1672
1779
|
return 29;
|
|
1673
1780
|
}
|
|
1674
1781
|
}
|
|
1675
|
-
var
|
|
1782
|
+
var leapMonthByYear = /* @__PURE__ */ new Map();
|
|
1676
1783
|
function hasLunarLeapMonth(year) {
|
|
1677
1784
|
if (!Number.isInteger(year) || year < 1900 || year > 2050) return false;
|
|
1678
|
-
const
|
|
1679
|
-
if (
|
|
1785
|
+
const cached = leapMonthByYear.get(year);
|
|
1786
|
+
if (cached !== void 0) return cached;
|
|
1680
1787
|
const calendar = createCalendar();
|
|
1681
1788
|
const hasLeap = Array.from({ length: 12 }, (_, index) => index + 1).some(
|
|
1682
1789
|
(month) => calendar.setLunarDate(year, month, 1, true) && calendar.getLunarCalendar().intercalation === true
|
|
1683
1790
|
);
|
|
1684
|
-
|
|
1791
|
+
leapMonthByYear.set(year, hasLeap);
|
|
1685
1792
|
return hasLeap;
|
|
1686
1793
|
}
|
|
1687
1794
|
|
|
@@ -1705,14 +1812,17 @@ var SOLAR_TERM_RULES = [
|
|
|
1705
1812
|
{ key: "daeseol", month: 12, day: 7, hour: 5, minute: 0, monthBranch: "\uC790" },
|
|
1706
1813
|
{ key: "sohan", month: 1, day: 6, hour: 5, minute: 0, monthBranch: "\uCD95" }
|
|
1707
1814
|
];
|
|
1815
|
+
function solarTermDateTime(year, rule) {
|
|
1816
|
+
return { year, month: rule.month, day: rule.day, hour: rule.hour, minute: rule.minute };
|
|
1817
|
+
}
|
|
1708
1818
|
function getSolarTermDateTime(year, key) {
|
|
1709
1819
|
const rule = SOLAR_TERM_RULES.find((item) => item.key === key);
|
|
1710
1820
|
if (!rule) throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC808\uAE30\uC785\uB2C8\uB2E4: ${key}`);
|
|
1711
|
-
return
|
|
1821
|
+
return solarTermDateTime(year, rule);
|
|
1712
1822
|
}
|
|
1713
1823
|
function getMonthBranchBySolarTerm(dateTime) {
|
|
1714
1824
|
const candidates = SOLAR_TERM_RULES.map((rule) => ({
|
|
1715
|
-
startsAt:
|
|
1825
|
+
startsAt: solarTermDateTime(dateTime.year, rule),
|
|
1716
1826
|
branch: rule.monthBranch
|
|
1717
1827
|
})).sort((a, b) => compareLocalDateTime(a.startsAt, b.startsAt));
|
|
1718
1828
|
let selected = "\uCD95";
|
|
@@ -1721,11 +1831,23 @@ function getMonthBranchBySolarTerm(dateTime) {
|
|
|
1721
1831
|
}
|
|
1722
1832
|
return selected;
|
|
1723
1833
|
}
|
|
1834
|
+
function getMonthBranchBySolarDate(date) {
|
|
1835
|
+
const rules = [...SOLAR_TERM_RULES].sort((left, right) => left.month - right.month);
|
|
1836
|
+
let selected = "\uCD95";
|
|
1837
|
+
for (const rule of rules) {
|
|
1838
|
+
if (compareLocalDate(date, solarTermDateTime(date.year, rule)) >= 0)
|
|
1839
|
+
selected = rule.monthBranch;
|
|
1840
|
+
}
|
|
1841
|
+
return selected;
|
|
1842
|
+
}
|
|
1724
1843
|
|
|
1725
1844
|
// src/saju/calculateDayPillar.ts
|
|
1726
1845
|
var JDN_TO_SEXAGENARY_OFFSET = 49;
|
|
1727
1846
|
function calculateDayPillar(dateTime) {
|
|
1728
|
-
|
|
1847
|
+
return calculateDayPillarFromDate(dateTime);
|
|
1848
|
+
}
|
|
1849
|
+
function calculateDayPillarFromDate(date) {
|
|
1850
|
+
const jdn = julianDayNumber(date.year, date.month, date.day);
|
|
1729
1851
|
return pillarFromIndex(positiveModulo(jdn + JDN_TO_SEXAGENARY_OFFSET, 60));
|
|
1730
1852
|
}
|
|
1731
1853
|
|
|
@@ -1778,6 +1900,9 @@ var IN_MONTH_START_STEM_BY_YEAR_STEM = {
|
|
|
1778
1900
|
};
|
|
1779
1901
|
function calculateMonthPillar(dateTime, yearStem) {
|
|
1780
1902
|
const branch = getMonthBranchBySolarTerm(dateTime);
|
|
1903
|
+
return monthPillarFromBranch(branch, yearStem);
|
|
1904
|
+
}
|
|
1905
|
+
function monthPillarFromBranch(branch, yearStem) {
|
|
1781
1906
|
const offsetFromIn = positiveModulo(branchIndex(branch) - branchIndex("\uC778"), 12);
|
|
1782
1907
|
const stem = stemAt(IN_MONTH_START_STEM_BY_YEAR_STEM[yearStem] + offsetFromIn);
|
|
1783
1908
|
return { stem, branch };
|
|
@@ -1831,14 +1956,20 @@ function calculateTenGod(dayStem, targetStem) {
|
|
|
1831
1956
|
|
|
1832
1957
|
// src/saju/calculateTenGods.ts
|
|
1833
1958
|
function calculateTenGods(fourPillars) {
|
|
1959
|
+
const dayStem = fourPillars.day.stem;
|
|
1960
|
+
return {
|
|
1961
|
+
...calculateDateTenGods(fourPillars),
|
|
1962
|
+
hourStem: calculateTenGod(dayStem, fourPillars.hour.stem),
|
|
1963
|
+
hourBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.hour.branch].mainHiddenStem)
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function calculateDateTenGods(fourPillars) {
|
|
1834
1967
|
const dayStem = fourPillars.day.stem;
|
|
1835
1968
|
return {
|
|
1836
1969
|
yearStem: calculateTenGod(dayStem, fourPillars.year.stem),
|
|
1837
1970
|
yearBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.year.branch].mainHiddenStem),
|
|
1838
1971
|
monthStem: calculateTenGod(dayStem, fourPillars.month.stem),
|
|
1839
1972
|
monthBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.month.branch].mainHiddenStem),
|
|
1840
|
-
dayBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.day.branch].mainHiddenStem)
|
|
1841
|
-
hourStem: calculateTenGod(dayStem, fourPillars.hour.stem),
|
|
1842
|
-
hourBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.hour.branch].mainHiddenStem)
|
|
1973
|
+
dayBranch: calculateTenGod(dayStem, BRANCH_INFO[fourPillars.day.branch].mainHiddenStem)
|
|
1843
1974
|
};
|
|
1844
1975
|
}
|