If you were born in January or early February, two zodiac sites can hand you two different animals for the same birthday. Only one of them is using the boundary that Korean saju (Four Pillars) actually uses.
Disclosure: I work on sajuapp.app, a Korean saju app. Everything below is standalone and you can use it in any project.
Three places a "year" can start
When a birthday feature asks "which year were you born in?", there are three candidate boundaries:
| Boundary | When it happens | Who uses it |
|---|---|---|
| January 1 | Fixed Gregorian date | Standard date libraries, by default |
| Lunar New Year | Moves between late January and mid-February | Popular zodiac-animal charts |
| Ipchun (Start of Spring) | The instant the Sun reaches 315 degrees of apparent ecliptic longitude, around February 3-5 | Saju / Four Pillars year pillar |
Saju does not use Lunar New Year for the year pillar. It uses Ipchun, which is a solar term, not a lunar date. That single rule is where a lot of birthday widgets drift.
A concrete case: late January 2025
Lunar New Year 2025 fell on January 29. Ipchun 2025 fell on February 3 (Korea time). So for someone born on February 1, 2025:
- January 1 rule: 2025, Wood Snake
- Lunar New Year rule: 2025 (lunar January 4), Wood Snake
- Ipchun rule: still 2024, Wood Dragon (甲辰)
2026 flips the order. Ipchun 2026 is on February 4 (KST) but Lunar New Year is not until February 17. Someone born on February 10, 2026 is still in the lunar year 2025, yet their saju year pillar is already Fire Horse (丙午).
The arithmetic is the easy part
Once you know the effective year, the 60-year cycle is two modulo operations. 1984 is a Gap-Ja (甲子) year, which is why the offset is 4:
const STEMS = ['Gap','Eul','Byeong','Jeong','Mu','Gi','Gyeong','Sin','Im','Gye'];
const BRANCHES = ['Rat','Ox','Tiger','Rabbit','Dragon','Snake',
'Horse','Goat','Monkey','Rooster','Dog','Pig'];
// Ipchun instants in UTC, computed with a low-precision solar formula
// (Meeus). Good to about 15 minutes; see the note below.
const IPCHUN_UTC = {
2021: '2021-02-03T14:56Z',
2024: '2024-02-04T08:20Z',
2025: '2025-02-03T14:07Z',
2026: '2026-02-03T19:54Z',
2029: '2029-02-03T13:18Z',
};
function yearPillar(birthUtc) {
const y = birthUtc.getUTCFullYear();
const ipchun = new Date(IPCHUN_UTC[y]);
const effective = birthUtc < ipchun ? y - 1 : y;
const stem = ((effective - 4) % 10 + 10) % 10;
const branch = ((effective - 4) % 12 + 12) % 12;
return `${STEMS[stem]} ${BRANCHES[branch]}`;
}
yearPillar(new Date('2025-02-01T03:00Z')); // "Gap Dragon"
yearPillar(new Date('2025-02-04T03:00Z')); // "Eul Snake"
The modulo wrap (+ 10) % 10) matters if you ever feed in years before 4 CE, and it costs nothing.
The hard part: Ipchun is an instant, not a date
Two details are easy to miss:
- The boundary day itself depends on the birth time. In 2025 the Sun crossed 315 degrees late in the evening of February 3, Korea time. A baby born at noon that day is still in the Dragon year. If your code compares dates instead of instants, it gets this wrong.
- The local date depends on the time zone. The 2026 crossing is early morning on February 4 in Seoul but still February 3 in New York. Store the instant in UTC and compare against the birth instant, not against a calendar date.
To compute the instant yourself, solve "apparent solar longitude = 315 degrees" with a few Newton steps, then convert from Terrestrial Time to UTC with a Delta T estimate. The low-precision series from Jean Meeus' Astronomical Algorithms gets you within minutes, which is fine unless a birth falls inside that window. For those, check an official almanac (in Korea, KASI publishes the solar term times).
Test cases worth keeping
If you ship a zodiac or saju feature, these dates cover the main edge cases:
- 2025-01-30 to 2025-02-02: after Lunar New Year, before Ipchun
- 2025-02-03 at noon KST: before the Ipchun instant on the boundary day
- 2026-02-04 to 2026-02-16: after Ipchun, before Lunar New Year
- Any 2026-02-03 evening birth in the Americas
Save this if you are building anything that takes a date of birth. The edge cases are easy to forget until a user in February tells you their animal is wrong.
If you want to see all four pillars (year, month, day, hour) set on solar-term boundaries rather than Lunar New Year, you can enter a birth date at sajuapp.app. It is free to start; the detailed reading is optional.
Top comments (0)