The surprising complexity of time zones, solar terms, and building a reliable Chinese astrology calculator.
You enter the same birth date into two different websites.
Same date. Same time. Same city.
But somehow, you get two different BaZi charts.
Which one is correct?
This is one of the most interesting problems I've encountered while working on Mingora, a Chinese astrology web app I've been building.
At first glance, calculating a BaZi chart seems straightforward: take someone's birth date and time, apply a set of traditional calendar rules, and generate four pairs of Chinese characters.
But once you start implementing those rules in software, things get surprisingly complicated.
And most of the complexity comes from something developers already know can be painful: working with dates and times.
A Quick Introduction to BaZi
BaZi (八字), also known as the Four Pillars of Destiny, is a traditional Chinese astrology system.
It represents a person's birth information using four pillars:
- Year Pillar — based on the year of birth
- Month Pillar — based on the solar-term month
- Day Pillar — based on the day of birth
- Hour Pillar — based on a traditional two-hour period
Each pillar contains a Heavenly Stem and an Earthly Branch, producing eight Chinese characters in total.
The underlying calendar calculations are deterministic, although different traditional conventions can produce different results.
The challenge is making sure the software interprets the birth information correctly.
Here are four reasons two calculators might disagree.
1. Time Zones Are More Complicated Than They Look
Imagine someone was born at 11:30 PM in New York.
A naive implementation might take the date and time, create a JavaScript Date object, and start calculating.
But what does 11:30 PM actually mean?
Was daylight saving time active on that date?
What was the historical UTC offset?
Was the birth time entered in the user's current time zone or the birthplace's time zone?
JavaScript timestamps represent an instant in time, but the original local birth date and its geographical context are equally important.
For example:
const birthDate = new Date("1995-06-15T23:30:00");
This looks harmless.
But the string has no explicit time zone. JavaScript interprets it as local time in the environment where the code runs.
Run the same code in environments with different time zones, and you can get different timestamps.
A more reliable approach is to keep the birthplace, local birth time, and time zone information together before performing calendar calculations.
This is especially important for people born near midnight.
2. The Chinese Astrological Year Doesn't Start on January 1
Here's another unexpected detail.
In many BaZi calculation conventions, the year changes at Li Chun (立春), the Beginning of Spring, rather than on January 1 or Lunar New Year.
Li Chun typically occurs around February 4, although the exact astronomical moment varies.
So someone born in early February may belong to the previous BaZi year, even when their Gregorian birth year has already changed.
The same principle applies to the Month Pillar.
BaZi months are traditionally determined using solar-term boundaries rather than simply matching January to the first month, February to the second, and so on.
This means a correct implementation needs to know when particular solar terms occur.
It isn't enough to write:
const month = birthDate.getMonth() + 1;
The Gregorian month number and the BaZi month branch are not interchangeable.
For developers, this is a good example of why calendar systems should be modeled explicitly rather than treated as formatting variations of the same date.
3. Clock Time Isn't Always Solar Time
This was one of the more fascinating parts of the project for me.
Modern time zones standardize clock time across large geographical areas.
But the Sun doesn't follow those administrative boundaries.
Two cities in the same time zone can experience solar noon at noticeably different clock times.
Consider China.
The entire country officially uses UTC+8, even though it spans a huge range of longitudes.
Beijing is located near 116.4°E, while the standard meridian for UTC+8 is 120°E.
Since Earth rotates approximately 15 degrees per hour, every degree of longitude corresponds to roughly four minutes of solar time.
A simplified longitude correction looks like this:
function getLongitudeCorrection(
longitude,
utcOffsetHours
) {
const standardMeridian = utcOffsetHours * 15;
return (longitude - standardMeridian) * 4;
}
The result is the correction in minutes.
For Beijing, the longitude component is approximately -14.4 minutes.
However, that is only part of the story.
Calculating true solar time, also called local apparent solar time, requires an additional adjustment known as the Equation of Time.
It accounts for the difference between mean solar time and apparent solar time throughout the year.
Depending on the BaZi convention being used, these adjustments can affect the Hour Pillar, especially when a birth happens close to a two-hour boundary.
And that's how two calculators can produce different charts even when both appear to accept exactly the same input.
4. Different Traditional Conventions Exist
Not every disagreement is a programming bug.
Some differences come from the rules a calculator chooses to follow.
One particularly interesting example is Zi Hour (子时), which traditionally spans 11:00 PM to 1:00 AM.
Some calculation conventions treat 11:00 PM as the beginning of the next astrological day.
Others distinguish between early and late Zi Hour and handle the Day Pillar differently.
Consider someone born at 11:30 PM.
Two implementations using different day-boundary conventions may calculate different Day Pillars.
Neither result necessarily indicates that the developer made a mistake.
The important thing is to make these conventions explicit, document them, and apply them consistently.
From a software engineering perspective, that means calculation rules should be testable and reproducible.
What I Learned Building My Own Calculator
While working on Mingora, I started to appreciate how much detail goes into turning a traditional calendar system into a modern web application.
The interesting part wasn't just generating eight characters.
It was dealing with the edge cases:
- Births close to solar-term boundaries
- Historical time zones and daylight saving time
- Longitude-based time adjustments
- Birth times near midnight
- Different conventions for day and hour boundaries
- Missing or uncertain birth-time information
These are the kinds of problems that don't always show up in a simple demo, but become important when real people start using a tool.
I eventually separated the underlying chart calculations from the interpretation layer and open-sourced the TypeScript calculation engine.
If you're curious about how these rules work in practice, you can experiment with the free BaZi calculator on Mingora.
It lets you enter a birth date, time, and city to explore the Four Pillars, Day Master, Five Elements, and time adjustments.
The calculation engine is also available on GitHub for developers interested in the implementation.
Final Thoughts
Working with dates and times is already difficult in ordinary applications.
Add historical time zones, astronomical events, geographical coordinates, and centuries-old calendar conventions, and the problem becomes much more interesting.
My biggest takeaway is that a reliable calculator isn't just about implementing formulas correctly. It's about defining your assumptions clearly and handling boundary conditions consistently.
That lesson applies far beyond Chinese astrology.
Whether you're building scheduling software, financial systems, astronomical tools, or anything involving international dates, the smallest time-related assumptions can sometimes produce the biggest differences.
And that's exactly what makes these problems worth solving.
Top comments (0)