When building high-precision East Asian calendar engines or Four Pillars (Bazi / 八字) astrolabes, relying on standard civil clock time—such as UTC+8 (China Standard Time)—introduces significant astronomical errors.
Standard time zones are geopolitical constructs. For instance, the entire landmass of China shares a single time zone anchored at the 120°E meridian, even though the country spans longitudes from roughly 73°E to 135°E. In traditional horology and Ganzhi (干支) hour-pillar calculations, the day is divided into 12 double-hours (Shichen / 时辰) based strictly on the actual position of the sun relative to the local observer's meridian.
To accurately determine the Shichen boundary (especially the critical transition at 23:00 for the Zi hour / 子时), an algorithm must convert civil clock time into Local Apparent Solar Time (真太阳时 / True Solar Time). This conversion requires a two-step astronomical correction:
- Geographic Longitude Offset (经度时差) — converting standard zone time to Local Mean Solar Time (平太阳时).
- The Equation of Time (均时差) — correcting for Earth's elliptical orbit and axial tilt.
Step 1: Geographic Longitude Correction (Local Mean Solar Time)
The Earth rotates 360° in approximately 24 hours (1,440 minutes), which translates to 1° every 4 minutes (or 1' of arc every 4 seconds).
If a civil clock is synchronized to a standard meridian λ_std (e.g., 120°E for UTC+8), any observer located at local longitude λ_local experiences solar noon earlier or later than the standard meridian:
Δt_lon = (λ_local - λ_std) × 4 minutes - Example: For an observer in Chengdu (
λ_local ≈ 104.06°E), the local mean solar time lags behind Beijing Standard Time by:
(104.06 - 120.0) × 4 = -63.76 minutes ≈ -1 hour 3 minutes 46 seconds However, even after applying this geographic offset, the sun still does not cross the local meridian at exactly 12:00:00 Local Mean Time. That brings us to the orbital mechanics of the Earth.
Step 2: The Equation of Time (均时差)
Local Mean Solar Time assumes a fictitious "mean sun" moving along the celestial equator at a uniform speed. In reality, apparent solar time fluctuates throughout the year due to two Keplerian factors:
- Orbital Eccentricity (
e ≈ 0.0167): According to Kepler's Second Law, Earth moves faster near perihelion (early January) and slower near aphelion (early July). - Obliquity of the Ecliptic (
ε ≈ 23.44°): The projection of the sun's ecliptic motion onto the celestial equator varies seasonally.
The cumulative discrepancy between apparent solar time and mean solar time is known in positional astronomy as the Equation of Time (EoT). It ranges from approximately -14 minutes 6 seconds (around February 11) to +16 minutes 33 seconds (around November 3).
Spencer's Fourier Series Approximation
For most calendrical and Bazi software applications, solving Kepler's equation iteratively is unnecessary. J.W. Spencer (1971) derived a highly reliable Fourier series approximation for the Equation of Time, accurate to within ~30 seconds.
Given the day angle B (in radians) for day N of the year (where N = 1 on January 1):
B = 2π × (N - 1) / 365 The Equation of Time (in minutes) is expressed as:
EoT = 229.18 × (0.000075 + 0.001868·cos(B) - 0.032077·sin(B) - 0.014615·cos(2B) - 0.040849·sin(2B)) Step 3: Reference Implementation in TypeScript / JavaScript
Below is a zero-dependency implementation that converts a standard Date object and geographic longitude into True Solar Time.
/** * Calculates the day of the year (1 - 366) in UTC. */ function getDayOfYear(date: Date): number { const start = Date.UTC(date.getUTCFullYear(), 0, 1); const current = Date.UTC( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() ); return Math.floor((current - start) / 86_400_000) + 1; } /** * Computes the Equation of Time (in minutes) using Spencer's (1971) formula. */ function getEquationOfTimeMinutes(dayOfYear: number): number { const B = (2 * Math.PI * (dayOfYear - 1)) / 365.2422; return ( 229.18 * (0.000075 + 0.001868 * Math.cos(B) - 0.032077 * Math.sin(B) - 0.014615 * Math.cos(2 * B) - 0.040849 * Math.sin(2 * B)) ); } /** * Converts civil clock time to True Solar Time (Apparent Solar Time). * * @param civilDate - Input timestamp (JavaScript Date instance) * @param localLongitude - Observer's longitude in degrees (East positive, West negative) * @param standardMeridian - Reference meridian of the civil time zone (default: 120.0 for UTC+8) * @returns Adjusted Date object representing True Solar Time */ export function toTrueSolarTime( civilDate: Date, localLongitude: number, standardMeridian: number = 120.0 ): Date { // 1. Longitude offset in minutes (4 minutes per degree) const longitudeOffsetMin = (localLongitude - standardMeridian) * 4.0; // 2. Equation of Time offset in minutes const dayOfYear = getDayOfYear(civilDate); const eotOffsetMin = getEquationOfTimeMinutes(dayOfYear); // 3. Total correction in milliseconds const totalOffsetMs = (longitudeOffsetMin + eotOffsetMin) * 60_000; return new Date(civilDate.getTime() + totalOffsetMs); } // --- Example Usage --- // Civil Time: 2026-02-11 12:00:00 (UTC+8) in Urumqi (87.62°E) const civilTime = new Date("2026-02-11T12:00:00+08:00"); const trueSolar = toTrueSolarTime(civilTime, 87.62, 120.0); console.log("Civil Clock Time (UTC+8):", civilTime.toISOString()); console.log("True Solar Time (Urumqi):", trueSolar.toISOString()); // Output difference exceeds ~2 hours and 23 minutes! Why This Matters for Boundary Edge Cases
In traditional calendrical systems, each Shichen spans 120 minutes. A combined longitude and EoT offset in western regions (such as Tibet or Xinjiang, or even central cities like Xi'an and Chengdu) easily exceeds 60 to 140 minutes.
If a user's timestamp falls near the odd-hour boundaries (01:00, 03:00, ..., 23:00), skipping the True Solar Time reduction will almost certainly assign the wrong Earthly Branch (Dizhi / 地支) to the Hour Pillar—and if the timestamp crosses the 23:00 (Zi hour) threshold, it will corrupt the Day Pillar (日柱) as well.
By chaining True Solar Time reduction for diurnal hour boundaries with exact solar longitude (λ_sun) root-finding for 24 Solar Terms (二十四节气) in monthly transitions, a calendar engine achieves true astronomical fidelity regardless of where the observer is located on Earth.