Use local time

This commit is contained in:
2026-06-24 20:31:58 -04:00
parent d6e799e956
commit 60f76b06d4
13 changed files with 476 additions and 309 deletions

View File

@@ -1,7 +1,7 @@
import express from 'express';
import {resolve, dirname} from 'path';
import {fileURLToPath} from 'url';
import {cfg} from './config.mjs';
import {cfg, localDateStr, tzOffsetMinutes} from './config.mjs';
import {queryCurrent, queryHourly, queryDaily, getCoords} from './influx.mjs';
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
import {getSpaceWeather} from './space.mjs';
@@ -13,6 +13,16 @@ import {getWeatherCondition} from './openweather.mjs';
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
// ── Uncaught error handlers ───────────────────────────────────────────────────
process.on('unhandledRejection', (reason, promise) => {
console.error('[unhandledRejection]', promise, '\nReason:', reason)
})
process.on('uncaughtException', (err) => {
console.error('[uncaughtException]', err)
})
const app = express();
const DIR = dirname(fileURLToPath(import.meta.url));
const CLIENT_DIST = resolve(DIR, '../public');
@@ -44,14 +54,13 @@ app.get('/api/current', async (req, res) => {
const space = getCelestialCurrent(coords.latitude, coords.longitude)
const condition = getWeatherCondition(Object.assign(sensors, space))
// Merge forecast summary fields — sensor readings take priority
const forecast = await getForecast();
const merged = {
...(forecast?.summary ? {
precipitation_chance: forecast.summary.precipitation_chance,
temperature_max: forecast.summary.temperature_max,
temperature_min: forecast.summary.temperature_min,
uv_index_max: forecast.summary.uv_index,
precipitation_chance: forecast.summary.precipitation_chance,
temperature_max: forecast.summary.temperature_max,
temperature_min: forecast.summary.temperature_min,
uv_index_max: forecast.summary.uv_index,
} : {}),
...condition,
...sensors,
@@ -73,7 +82,6 @@ app.get('/api/hourly', async (req, res) => {
const coords = await getCoords()
// Past → now: InfluxDB
const historyEnd = new Date(Math.min(now, end))
historyEnd.setMinutes(0, 0, 0)
@@ -82,12 +90,10 @@ app.get('/api/hourly', async (req, res) => {
])
const history = historyResult.status === 'fulfilled' ? historyResult.value : []
// Now → +24h: cached physics forecast
const physics = end > now
? lastForecast.slots.filter(s => { const t = new Date(s.time); return t >= now && t <= new Date(Math.min(end, plus24)) })
: []
// +24h → beyond: Open-Meteo
let meteo = []
if (end > plus24) {
const [meteoResult] = await Promise.allSettled([hourlyWeather(coords.latitude, coords.longitude, plus24, end)])
@@ -103,22 +109,32 @@ app.get('/api/hourly', async (req, res) => {
app.get('/api/daily', async (req, res) => {
const { fields } = req.query
const now = new Date()
now.setHours(0, 0, 0, 0)
const next = new Date(now); next.setDate(now.getDate() + 1)
const start = req.query.start ? new Date(req.query.start) : now
const end = req.query.end ? new Date(req.query.end) : next
// Use local midnight as "today" boundary
const offset = tzOffsetMinutes(now)
const localNow = new Date(now.getTime() + offset * 60_000)
localNow.setUTCHours(0, 0, 0, 0)
const todayLocal = new Date(localNow.getTime() - offset * 60_000) // back to UTC
const nextLocal = new Date(todayLocal.getTime() + 24 * 3600_000)
const start = req.query.start ? new Date(req.query.start) : todayLocal
const end = req.query.end ? new Date(req.query.end) : nextLocal
const coords = await getCoords()
const todayStr = now.toISOString().slice(0, 10) // ✅ string for comparison
const usePhysics = lastForecast.summary && start <= now && end > now // ✅ only when today is in window
const todayStr = localDateStr(now)
const usePhysics = lastForecast.summary && start <= now && end > now
const [sensorResult] = await Promise.allSettled([start < now ? queryDaily(start, now) : Promise.resolve([])])
const history = sensorResult.status === 'fulfilled' ? sensorResult.value : []
// ✅ meteo starts from whichever is later: next or start
const meteoFrom = new Date(Math.max(next.getTime(), start.getTime()))
const [meteoResult] = await Promise.allSettled([end > next ? dailyWeather(coords.latitude, coords.longitude, meteoFrom, end) : Promise.resolve([])])
const meteoFrom = new Date(Math.max(nextLocal.getTime(), start.getTime()))
const [meteoResult] = await Promise.allSettled([end > nextLocal ? dailyWeather(coords.latitude, coords.longitude, meteoFrom, end) : Promise.resolve([])])
const meteo = meteoResult.status === 'fulfilled' ? meteoResult.value : []
const todaySlot = usePhysics ? [lastForecast.summary] : []
const meteoClean = meteo.filter(r => String(r.time).slice(0, 10) !== todayStr) // ✅ safe string compare
// Filter meteo using local date string so we never double-count today
const meteoClean = meteo.filter(r => localDateStr(new Date(r.time)) !== todayStr)
const daily = getCelestialForecast(coords.latitude, coords.longitude, [...history, ...todaySlot, ...meteoClean])
res.json(filterArr(daily, fields))
@@ -175,6 +191,7 @@ app.use((err, req, res, next) => {
})
// ── Start ─────────────────────────────────────────────────────────────────────
const c = cfg();
setTimeout(getForecast, 1)