// 精确节气计算模块 // 基于天文算法和地理位置的高精度节气计算 class PreciseSolarTerms { constructor() { // 二十四节气名称 this.solarTermNames = [ '立春', '雨水', '惊蛰', '春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至', '小暑', '大暑', '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至', '小寒', '大寒' ]; // 节气对应的太阳黄经度数 this.solarTermLongitudes = [ 315, 330, 345, 0, 15, 30, // 立春到谷雨 45, 60, 75, 90, 105, 120, // 立夏到大暑 135, 150, 165, 180, 195, 210, // 立秋到霜降 225, 240, 255, 270, 285, 300 // 立冬到大寒 ]; // 地球轨道参数(简化) this.earthOrbitParams = { eccentricity: 0.0167, // 轨道偏心率 perihelionLongitude: 102.9, // 近日点黄经 obliquity: 23.44 // 黄赤交角 }; // 时区偏移缓存 this.timezoneCache = new Map(); } // 计算指定年份的所有节气时间 calculateYearSolarTerms(year, longitude = 116.4, latitude = 39.9) { const solarTerms = []; for (let i = 0; i < 24; i++) { const termTime = this.calculateSolarTermTime(year, i, longitude, latitude); solarTerms.push({ index: i, name: this.solarTermNames[i], longitude: this.solarTermLongitudes[i], time: termTime, localTime: this.convertToLocalTime(termTime, longitude), month: termTime.getMonth() + 1, day: termTime.getDate() }); } return solarTerms; } // 计算特定节气的精确时间 calculateSolarTermTime(year, termIndex, longitude = 116.4, latitude = 39.9) { const targetLongitude = this.solarTermLongitudes[termIndex]; // 估算初始时间(基于平均值) let estimatedTime = this.getEstimatedTermTime(year, termIndex); // 使用牛顿迭代法精确计算 for (let iteration = 0; iteration < 10; iteration++) { const currentLongitude = this.calculateSunLongitude(estimatedTime); const longitudeDiff = this.normalizeLongitude(targetLongitude - currentLongitude); if (Math.abs(longitudeDiff) < 0.0001) break; // 精度达到要求 // 计算太阳运动速度(度/天) const sunSpeed = this.calculateSunSpeed(estimatedTime); const timeDiff = longitudeDiff / sunSpeed; // 天数 estimatedTime = new Date(estimatedTime.getTime() + timeDiff * 24 * 60 * 60 * 1000); } return estimatedTime; } // 获取节气的估算时间 getEstimatedTermTime(year, termIndex) { // 基于2000年的节气时间进行估算 const baseYear = 2000; const yearDiff = year - baseYear; // 节气在一年中的大致时间(儒略日) const baseJulianDays = [ // 立春到大寒的大致儒略日偏移 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 259, 274, 289, 304, 319, 334, 349, 4, 19 ]; const baseJulianDay = this.getJulianDay(baseYear, 1, 1) + baseJulianDays[termIndex]; const estimatedJulianDay = baseJulianDay + yearDiff * 365.25; return this.julianDayToDate(estimatedJulianDay); } // 计算太阳黄经 calculateSunLongitude(date) { const julianDay = this.dateToJulianDay(date); const T = (julianDay - 2451545.0) / 36525.0; // 儒略世纪数 // 太阳平黄经(度) let L0 = 280.46646 + 36000.76983 * T + 0.0003032 * T * T; // 太阳平近点角(度) let M = 357.52911 + 35999.05029 * T - 0.0001537 * T * T; // 地球轨道偏心率 const e = 0.016708634 - 0.000042037 * T - 0.0000001267 * T * T; // 太阳中心方程(度) const C = (1.914602 - 0.004817 * T - 0.000014 * T * T) * Math.sin(this.toRadians(M)) + (0.019993 - 0.000101 * T) * Math.sin(this.toRadians(2 * M)) + 0.000289 * Math.sin(this.toRadians(3 * M)); // 太阳真黄经 const sunLongitude = L0 + C; return this.normalizeLongitude(sunLongitude); } // 计算太阳运动速度 calculateSunSpeed(date) { const julianDay = this.dateToJulianDay(date); const T = (julianDay - 2451545.0) / 36525.0; // 太阳平近点角 const M = 357.52911 + 35999.05029 * T - 0.0001537 * T * T; // 地球轨道偏心率 const e = 0.016708634 - 0.000042037 * T - 0.0000001267 * T * T; // 平均角速度(度/天) const meanSpeed = 0.9856474; // 考虑轨道偏心率的修正 const speedCorrection = 2 * e * Math.sin(this.toRadians(M)) * meanSpeed; return meanSpeed + speedCorrection; } // 标准化黄经到0-360度范围 normalizeLongitude(longitude) { while (longitude < 0) longitude += 360; while (longitude >= 360) longitude -= 360; return longitude; } // 转换为本地时间 convertToLocalTime(utcTime, longitude) { // 根据经度计算时区偏移 const timezoneOffset = Math.round(longitude / 15) * 60; // 分钟 const localTime = new Date(utcTime.getTime() + timezoneOffset * 60 * 1000); return localTime; } // 获取指定日期所在的节气 getSolarTermForDate(date, longitude = 116.4, latitude = 39.9) { const year = date.getFullYear(); const yearTerms = this.calculateYearSolarTerms(year, longitude, latitude); // 找到日期所在的节气区间 for (let i = 0; i < yearTerms.length; i++) { const currentTerm = yearTerms[i]; const nextTerm = yearTerms[(i + 1) % 24]; let nextTermTime = nextTerm.localTime; if (nextTerm.index === 0) { // 下一年的立春 const nextYearTerms = this.calculateYearSolarTerms(year + 1, longitude, latitude); nextTermTime = nextYearTerms[0].localTime; } if (date >= currentTerm.localTime && date < nextTermTime) { return { current: currentTerm, next: nextTerm, daysFromCurrent: Math.floor((date - currentTerm.localTime) / (24 * 60 * 60 * 1000)), daysToNext: Math.floor((nextTermTime - date) / (24 * 60 * 60 * 1000)) }; } } return null; } // 判断是否需要调整月柱(基于节气) shouldAdjustMonthPillar(birthDate, birthTime, longitude = 116.4, latitude = 39.9) { const fullDateTime = this.combineDateAndTime(birthDate, birthTime); const solarTermInfo = this.getSolarTermForDate(fullDateTime, longitude, latitude); if (!solarTermInfo) return { shouldAdjust: false }; const currentTerm = solarTermInfo.current; const isAfterTerm = fullDateTime >= currentTerm.localTime; // 判断是否跨越了节气边界 const month = fullDateTime.getMonth() + 1; const expectedTermMonth = this.getExpectedTermMonth(currentTerm.index); return { shouldAdjust: isAfterTerm && month !== expectedTermMonth, currentTerm: currentTerm, termTime: currentTerm.localTime, timeDifference: fullDateTime - currentTerm.localTime, recommendation: isAfterTerm ? '建议使用节气后的月柱' : '建议使用节气前的月柱' }; } // 获取节气对应的预期月份 getExpectedTermMonth(termIndex) { const termMonths = [ 2, 2, 3, 3, 4, 4, // 立春到谷雨 5, 5, 6, 6, 7, 7, // 立夏到大暑 8, 8, 9, 9, 10, 10, // 立秋到霜降 11, 11, 12, 12, 1, 1 // 立冬到大寒 ]; return termMonths[termIndex]; } // 合并日期和时间 combineDateAndTime(dateStr, timeStr) { const date = new Date(dateStr); if (!timeStr) return date; const timeMatch = timeStr.match(/(\d{1,2}):(\d{2})/); if (timeMatch) { date.setHours(parseInt(timeMatch[1]), parseInt(timeMatch[2]), 0, 0); } return date; } // 获取儒略日 getJulianDay(year, month, day) { if (month <= 2) { year -= 1; month += 12; } const A = Math.floor(year / 100); const B = 2 - A + Math.floor(A / 4); return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; } // 日期转儒略日 dateToJulianDay(date) { return this.getJulianDay(date.getFullYear(), date.getMonth() + 1, date.getDate()) + (date.getHours() + date.getMinutes() / 60 + date.getSeconds() / 3600) / 24; } // 儒略日转日期 julianDayToDate(julianDay) { const Z = Math.floor(julianDay + 0.5); const F = julianDay + 0.5 - Z; let A = Z; if (Z >= 2299161) { const alpha = Math.floor((Z - 1867216.25) / 36524.25); A = Z + 1 + alpha - Math.floor(alpha / 4); } const B = A + 1524; const C = Math.floor((B - 122.1) / 365.25); const D = Math.floor(365.25 * C); const E = Math.floor((B - D) / 30.6001); const day = B - D - Math.floor(30.6001 * E) + F; const month = E < 14 ? E - 1 : E - 13; const year = month > 2 ? C - 4716 : C - 4715; const date = new Date(year, month - 1, Math.floor(day)); const hours = (day - Math.floor(day)) * 24; date.setHours(Math.floor(hours), Math.floor((hours - Math.floor(hours)) * 60)); return date; } // 角度转弧度 toRadians(degrees) { return degrees * Math.PI / 180; } // 弧度转角度 toDegrees(radians) { return radians * 180 / Math.PI; } // 获取地理位置的时区信息 getTimezoneInfo(longitude, latitude) { const cacheKey = `${longitude.toFixed(2)},${latitude.toFixed(2)}`; if (this.timezoneCache.has(cacheKey)) { return this.timezoneCache.get(cacheKey); } // 简化的时区计算(实际应该使用更精确的时区数据库) const timezoneOffset = Math.round(longitude / 15); const timezoneInfo = { offset: timezoneOffset, name: `UTC${timezoneOffset >= 0 ? '+' : ''}${timezoneOffset}`, longitude: longitude, latitude: latitude }; this.timezoneCache.set(cacheKey, timezoneInfo); return timezoneInfo; } // 计算两个节气之间的天数 getDaysBetweenTerms(fromTermIndex, toTermIndex, year, longitude = 116.4, latitude = 39.9) { const yearTerms = this.calculateYearSolarTerms(year, longitude, latitude); const fromTerm = yearTerms[fromTermIndex]; const toTerm = yearTerms[toTermIndex]; let timeDiff = toTerm.localTime - fromTerm.localTime; if (timeDiff < 0) { // 跨年情况 const nextYearTerms = this.calculateYearSolarTerms(year + 1, longitude, latitude); timeDiff = nextYearTerms[toTermIndex].localTime - fromTerm.localTime; } return Math.floor(timeDiff / (24 * 60 * 60 * 1000)); } // 获取节气统计信息 getSolarTermStatistics(year, longitude = 116.4, latitude = 39.9) { const yearTerms = this.calculateYearSolarTerms(year, longitude, latitude); const statistics = { year: year, location: { longitude, latitude }, totalTerms: yearTerms.length, seasonalTerms: { spring: yearTerms.slice(0, 6), summer: yearTerms.slice(6, 12), autumn: yearTerms.slice(12, 18), winter: yearTerms.slice(18, 24) }, averageInterval: 0, maxInterval: 0, minInterval: Infinity }; // 计算节气间隔统计 for (let i = 0; i < yearTerms.length - 1; i++) { const interval = (yearTerms[i + 1].localTime - yearTerms[i].localTime) / (24 * 60 * 60 * 1000); statistics.averageInterval += interval; statistics.maxInterval = Math.max(statistics.maxInterval, interval); statistics.minInterval = Math.min(statistics.minInterval, interval); } statistics.averageInterval /= (yearTerms.length - 1); return statistics; } } module.exports = PreciseSolarTerms;