一個取模運算即可計算韓國四柱日柱(但首爾 1900 年的時區會令結果出錯)
One modulo gives you a Korean Saju day pillar (and Seoul's 1900 time zone breaks it)
韓國四柱日柱可由 60 日循環及單一索引計算,但以 JavaScript 本地日期配合 Math.floor 的寫法在首爾時區會令結果提前一柱。計算以 1900 年 1 月 31 日的甲辰日作錨點,改用 Date.UTC 和 Math.round 計算日數,以避開時區及夏令時間偏差。
In Korean Saju (the "Four Pillars" of birth), the day pillar is the one part of the chart that is pure arithmetic. No solar terms, no lunar calendar, no lookup tables — just a 60-day cycle that has been running without a break for a very long time.
So it should be a one-liner. It mostly is. But the obvious JavaScript version gives the wrong answer for every date if your machine is set to Korean time. Here is the math, the bug, and the fix.
The 60-day cycle
Each day gets a pair: one of 10 Heavenly Stems and one of 12 Earthly Branches. Both advance by one every day, so the pair repeats every lcm(10, 12) = 60 days.
const STEMS = "甲乙丙丁戊己庚辛壬癸"; // Gap, Eul, Byeong, Jeong, Mu, Gi, Gyeong, Sin, Im, Gye
const BRANCHES = "子丑寅卯辰巳午未申酉戌亥"; // Ja, Chuk, In, Myo, Jin, Sa, O, Mi, Sin, Yu, Sul, Hae
Because both counters move together, a single index i in 0..59 is enough: the stem is i % 10 and the branch is i % 12. Index 0 is 甲子 (Gap-ja), index 59 is 癸亥 (Gye-hae).
Pick an anchor, count days
All you need is one date whose pillar you know. 31 January 1900 was 甲辰 (Gap-jin), index 40. From there:
function dayPillar(y, m, d) {
const days = Math.round(
(Date.UTC(y, m - 1, d) - Date.UTC(1900, 0, 31)) / 86_400_000
);
const i = (((40 + days) % 60) + 60) % 60; // works for dates before 1900 too
return STEMS[i % 10] + BRANCHES[i % 12];
}
dayPillar(2000, 1, 1); // "戊午" (Mu-o, 55th day)
dayPillar(2000, 1, 2); // "己未" (Gi-mi)
dayPillar(1949, 10, 1); // "甲子" (start of a cycle)
dayPillar(2026, 10, 17); // "甲子" — also a cycle start
Two details matter:
-
((x % 60) + 60) % 60— JavaScript's%keeps the sign, so dates before the anchor would give a negative index without it. -
Date.UTCandMath.round— this is the part people skip.
The bug: local time + Math.floor
The "obvious" version uses local dates:
// Don't do this
const days = Math.floor((new Date(y, m - 1, d) - new Date(1900, 0, 31)) / 86_400_000);
I ran both versions for 15 May 1990 under different TZ settings:
| TZ | local-date version | UTC version |
|---|---|---|
| UTC | 庚辰 | 庚辰 |
| America/New_York | 己卯 (wrong) | 庚辰 |
| Europe/London | 己卯 (wrong) | 庚辰 |
| Asia/Seoul | 己卯 (wrong) | 庚辰 |
New York and London are wrong because of daylight saving: 15 May is in summer time, 31 January is not, so the gap is one hour short of a whole number of days, and Math.floor drops a day. In January the same code happens to work, which is why this bug survives testing.
Seoul is worse. Daylight saving isn't the problem there. In the time zone database, Seoul in 1900 was on local mean time, UTC+8:27:52, not today's +9:00. So the gap between any modern date and the 1900 anchor is about 32 minutes short of a whole day:
TZ=Asia/Seoul (new Date(1990,4,15) - new Date(1900,0,31)) / 864e5
→ 32975.9777
Math.floor turns that into 32975, and every date comes out one pillar early. The machine most likely to run a Korean astrology calculator is the one where the naive code always fails.
The fix is boring: do calendar math on calendar dates. Date.UTC has no time zone and no DST, and Math.round absorbs any leftover floating-point dust.
What the day pillar does not handle
- Year and month pillars are a different problem. They change at solar terms (the year at Ipchun, around 4 February), not on 1 January, so you need an astronomical table or a good library for those.
- The 23:00 boundary. Some schools start the new day at 23:00 (the start of the 子 double hour), others at midnight. My calculator uses midnight on the calendar date and says so on the page. Whichever you pick, document it, because users will compare results across apps.
- Birth time zone. Use the calendar date where the person was born. Don't use the date converted to the server's zone.
Try it
I put this exact function behind a small free page that shows your day pillar and what the Day Master element traditionally means: Find Your Day Master. It is entertainment and cultural reference, not prediction.
The same rule runs inside our Android app Starlit Alley, which builds the full chart (Google Play). I'm part of the small team that makes it.
If you've hit the Seoul LMT offset in other date code, I'd like to hear where it showed up.
This post was drafted with AI assistance. Every result in it, including the table, came from running the code above in Node.js.
來源:DEV Community · dev.to