true-solar-time-calculator
Version:
228 lines (227 loc) • 10.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TrueSolarTimeCalculator = void 0;
class AstroUtils {
static degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
static radiansToDegrees(radians) {
return radians * (180 / Math.PI);
}
static normalizeDegrees(degrees) {
let normalized = degrees % 360;
if (normalized < 0) {
normalized += 360;
}
return normalized;
}
}
class TrueSolarTimeCalculator {
/**
* Creates an instance of the calculator.
* @param inputDate The input Date object. Assumed to represent a moment in UTC.
* @param longitude Observer's longitude in degrees (-180 to 180, positive East).
* @param latitude Observer's latitude in degrees (-90 to 90, positive North).
*/
constructor(inputDate, longitude, latitude) {
this._julianDay = null;
this._equationOfTimeSeconds = null;
this._trueSolarTime = null;
if (longitude < -180 || longitude > 180) {
throw new Error('Longitude must be between -180 and 180 degrees.');
}
if (latitude < -90 || latitude > 90) {
throw new Error('Latitude must be between -90 and 90 degrees.');
}
const timeValue = inputDate?.getTime();
if (timeValue === null || timeValue === undefined || isNaN(timeValue)) {
throw new Error('Invalid input Date object provided.');
}
this._initialUtcDate = new Date(timeValue);
this._longitude = longitude;
this._latitude = latitude;
}
/** Gets a copy of the initial UTC Date used for calculations. */
get utcDate() {
return new Date(this._initialUtcDate.getTime());
}
/** Gets the observer's longitude. */
get longitude() {
return this._longitude;
}
/** Gets the observer's latitude. */
get latitude() {
return this._latitude;
}
/**
* Calculates and returns the Julian Day (JD) based on the initial UTC date.
* JD is the number of days since noon Universal Time (UT) on January 1, 4713 BCE.
*/
get julianDay() {
if (this._julianDay === null) {
const msPerDay = 86400000; // 24 * 60 * 60 * 1000
const unixEpochJD = 2440587.5; // JD for 1970-01-01 00:00:00 UTC
// getTime() returns milliseconds since Unix epoch (based on UTC)
const timeValue = this._initialUtcDate.getTime();
// Although constructor validates, double-check for safety (shouldn't happen)
if (isNaN(timeValue)) {
throw new Error('Cannot calculate Julian Day from an invalid Date object.');
}
this._julianDay = timeValue / msPerDay + unixEpochJD;
}
return this._julianDay;
}
/**
* Calculates and returns the Equation of Time (EoT) in seconds.
* EoT is the difference between apparent solar time and mean solar time.
* Positive EoT means the Sun is "fast" (transits before mean noon).
* Negative EoT means the Sun is "slow" (transits after mean noon).
* This uses a common approximation formula.
*/
get equationOfTimeSeconds() {
if (this._equationOfTimeSeconds === null) {
const jd = this.julianDay;
if (isNaN(jd)) {
// Should not happen if julianDay getter throws, but defensively:
throw new Error('Cannot calculate Equation of Time: Julian Day is invalid.');
}
const B_rad = AstroUtils.degreesToRadians((360 / 365) * (this.dayOfYear - 81)); // Simplified angular day
const eotMinutesApprox = 9.87 * Math.sin(2 * B_rad) -
7.53 * Math.cos(B_rad) -
1.5 * Math.sin(B_rad);
this._equationOfTimeSeconds = eotMinutesApprox * 60;
}
if (isNaN(this._equationOfTimeSeconds)) {
console.warn('Equation of Time calculation resulted in NaN. Check input date and formulas.');
}
return this._equationOfTimeSeconds ?? NaN; // Return NaN if null (though should be calculated or throw)
}
/**
* Gets the day of the year (1-366) for the initial UTC date.
*/
get dayOfYear() {
const startOfYear = new Date(Date.UTC(this._initialUtcDate.getUTCFullYear(), 0, 1, 0, 0, 0, 0) // Ensure start is exactly midnight
);
const diffMillis = this._initialUtcDate.getTime() - startOfYear.getTime();
// Be careful with DST transitions if not using UTC consistently, but here we are.
return Math.floor(diffMillis / 86400000) + 1;
}
/**
* Calculates the True Solar Time (TST) and stores it internally.
* Returns the calculator instance for method chaining.
*/
calculateTrueSolarTime() {
if (this._trueSolarTime === null) {
const eotSeconds = this.equationOfTimeSeconds;
if (isNaN(eotSeconds)) {
throw new Error('Cannot calculate True Solar Time: Equation of Time is invalid.');
}
// Longitude correction: Difference between Local Mean Time (LMT) and UTC.
// 1 degree longitude = 4 minutes = 240 seconds.
// Positive longitude (East) means LMT is ahead of UTC.
const longitudeCorrectionSeconds = this._longitude * 240;
// Get UTC time components
const utcHours = this._initialUtcDate.getUTCHours();
const utcMinutes = this._initialUtcDate.getUTCMinutes();
const utcSeconds = this._initialUtcDate.getUTCSeconds();
const utcMilliseconds = this._initialUtcDate.getUTCMilliseconds();
// Total seconds past UTC midnight
const totalUtcSeconds = utcHours * 3600 + utcMinutes * 60 + utcSeconds;
// Local Mean Time (LMT) in seconds past LMT midnight
// LMT = UTC + Longitude Correction
const meanSolarTimeSeconds = totalUtcSeconds + longitudeCorrectionSeconds;
// True Solar Time (TST) in seconds past TST midnight
// TST = LMT + EoT
let tstTotalSeconds = meanSolarTimeSeconds + eotSeconds;
// Normalize TST seconds to a value within a 0 to 86400 range (representing HH:MM:SS of TST)
// Handles crossing midnight forward or backward.
tstTotalSeconds = ((tstTotalSeconds % 86400) + 86400) % 86400;
// Extract TST hours, minutes, seconds
const tstHours = Math.floor(tstTotalSeconds / 3600);
const remainingSecondsAfterHours = tstTotalSeconds % 3600;
const tstMinutes = Math.floor(remainingSecondsAfterHours / 60);
const tstSeconds = Math.floor(remainingSecondsAfterHours % 60);
// Note: Milliseconds are carried over from the original UTC time. TST usually doesn't need ms precision.
// Create the Date object representing TST.
// IMPORTANT: Use the *original UTC date's* Year, Month, Day,
// but the *calculated TST* Hour, Minute, Second. Store this as a UTC date.
this._trueSolarTime = new Date(Date.UTC(this._initialUtcDate.getUTCFullYear(), this._initialUtcDate.getUTCMonth(), this._initialUtcDate.getUTCDate(), tstHours, tstMinutes, tstSeconds, utcMilliseconds // Preserve original milliseconds
));
}
return this;
}
/**
* Returns the calculated True Solar Time as a new Date object.
* Ensures TST is calculated if it hasn't been already.
*/
getDate() {
if (this._trueSolarTime === null) {
this.calculateTrueSolarTime();
}
// Return a *copy* to prevent external modification of the internal state
if (this._trueSolarTime === null || isNaN(this._trueSolarTime.getTime())) {
throw new Error('Failed to calculate a valid True Solar Time date.');
}
return new Date(this._trueSolarTime.getTime());
}
/**
* Formats the calculated True Solar Time Date object into a string.
* @param formatString A format string (e.g., 'YYYY-MM-DD HH:mm:ss'). Default is 'YYYY-MM-DD HH:mm:ss'.
* Supports YYYY, MM, DD, HH, mm, ss placeholders.
* @returns The formatted date string.
*/
format(formatString = 'YYYY-MM-DD HH:mm:ss') {
const date = this.getDate(); // This ensures calculation and gets a valid date or throws
// Extract components using UTC methods, as the internal _trueSolarTime is stored as UTC
const year = date.getUTCFullYear();
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
const day = date.getUTCDate().toString().padStart(2, '0');
const hours = date.getUTCHours().toString().padStart(2, '0');
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
const seconds = date.getUTCSeconds().toString().padStart(2, '0');
// Basic substitution - for more complex formatting, consider a library like date-fns or moment
return formatString
.replace('YYYY', year.toString())
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
}
/**
* Returns the Equation of Time in a human-readable format (e.g., "X分Y秒").
*/
getHumanReadableEOT() {
const eotSeconds = this.equationOfTimeSeconds;
if (isNaN(eotSeconds)) {
return '无效EOT';
}
const totalSeconds = Math.abs(eotSeconds);
const minutes = Math.floor(totalSeconds / 60);
const seconds = Math.floor(totalSeconds % 60);
const parts = [];
// Show minutes only if > 0 or if seconds are also 0
if (minutes > 0) {
parts.push(`${minutes}分`);
}
// Show seconds if > 0 or if it's exactly 0 minutes
if (seconds > 0 || minutes === 0) {
// Add padding zero for seconds if minutes are shown? Optional.
// const secStr = (minutes > 0 && seconds < 10) ? `0${seconds}` : `${seconds}`;
// parts.push(`${secStr}秒`);
parts.push(`${seconds}秒`);
}
return `${parts.join('')}`.trim();
}
/**
* Returns the calculated True Solar Time as a string in HH:mm:ss format.
*/
getTrueSolarTimeString() {
const tstDate = this.getDate(); // Ensures calculation and validity
const h = tstDate.getUTCHours().toString().padStart(2, '0');
const m = tstDate.getUTCMinutes().toString().padStart(2, '0');
const s = tstDate.getUTCSeconds().toString().padStart(2, '0');
return `${h}:${m}:${s}`;
}
}
exports.TrueSolarTimeCalculator = TrueSolarTimeCalculator;