Use local time
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// openmeteo.mjs
|
||||
import { createWriteStream, existsSync, mkdirSync } from 'fs'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { pipeline } from 'stream/promises'
|
||||
import { localDateStr } from './config.mjs'
|
||||
|
||||
const DIR = dirname(fileURLToPath(import.meta.url))
|
||||
const ICON_DIR = resolve(DIR, 'public', 'icons')
|
||||
@@ -53,8 +53,6 @@ async function cachedFetch(key, url) {
|
||||
return data
|
||||
}
|
||||
|
||||
// ── Physics helpers (mirror forecast.mjs — no import to keep this self-contained) ──
|
||||
|
||||
function dewPoint(tempC, humidity) {
|
||||
const a = 17.27, b = 237.7
|
||||
const gamma = (a * tempC) / (b + tempC) + Math.log(humidity / 100)
|
||||
@@ -98,16 +96,6 @@ function cloudsLabel(fraction) {
|
||||
return 'Overcast'
|
||||
}
|
||||
|
||||
function airQualityLabel(aqi) {
|
||||
if (!aqi) return null
|
||||
if (aqi <= 50) return 'Good'
|
||||
if (aqi <= 100) return 'Moderate'
|
||||
if (aqi <= 150) return 'Unhealthy for Sensitive'
|
||||
if (aqi <= 200) return 'Unhealthy'
|
||||
if (aqi <= 300) return 'Very Unhealthy'
|
||||
return 'Hazardous'
|
||||
}
|
||||
|
||||
// ── Daily ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
const DAILY_FIELDS = [
|
||||
@@ -133,13 +121,17 @@ export async function dailyWeather(lat, lon, start, end) {
|
||||
const temp = data.daily.temperature_2m_max[i]
|
||||
const humidity = data.daily.relative_humidity_2m_mean[i]
|
||||
const uv = data.daily.uv_index_max[i]
|
||||
const clouds = null // not available in daily
|
||||
|
||||
// Open-Meteo returns local date strings — parse as local midnight using TZ
|
||||
// e.g. "2026-03-01" → local midnight, not UTC midnight
|
||||
const tzOffset = (new Date(new Date().toLocaleString('en-US', { timeZone: require('./config.mjs').cfg().TZ })) - new Date(new Date().toLocaleString('en-US', { timeZone: 'UTC' }))) / 60_000
|
||||
const localMidnight = new Date(new Date(`${time}T00:00:00Z`).getTime() - tzOffset * 60_000)
|
||||
|
||||
return {
|
||||
label: WMO[code] || 'Unknown',
|
||||
icon,
|
||||
code,
|
||||
time: new Date(time + 'T00:00:00Z'),
|
||||
time: localMidnight,
|
||||
temperature: temp,
|
||||
temperature_max: temp,
|
||||
temperature_min: data.daily.temperature_2m_min[i],
|
||||
@@ -156,9 +148,9 @@ export async function dailyWeather(lat, lon, start, end) {
|
||||
uv_index: uv,
|
||||
uv_index_label: uvLabel(uv),
|
||||
solar_wm2: data.daily.shortwave_radiation_sum[i],
|
||||
clouds,
|
||||
sunrise: data.daily.sunrise[i],
|
||||
sunset: data.daily.sunset[i],
|
||||
clouds: null,
|
||||
sunrise: data.daily.sunrise[i] ? new Date(data.daily.sunrise[i]) : null,
|
||||
sunset: data.daily.sunset[i] ? new Date(data.daily.sunset[i]) : null,
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -178,25 +170,34 @@ export async function hourlyWeather(lat, lon, start, end) {
|
||||
const startStr = start instanceof Date ? start.toISOString().slice(0, 10) : start.slice(0, 10)
|
||||
const endStr = end instanceof Date ? end.toISOString().slice(0, 10) : end.slice(0, 10)
|
||||
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}` +
|
||||
`&hourly=${HOURLY_FIELDS}&timezone=auto&start_date=${startStr}&end_date=${endStr}`
|
||||
`&hourly=${HOURLY_FIELDS}&daily=sunrise,sunset&timezone=auto&start_date=${startStr}&end_date=${endStr}`
|
||||
|
||||
const data = await cachedFetch(`hourly_${lat}_${lon}_${startStr}_${endStr}`, url)
|
||||
if (!data?.hourly?.time) return []
|
||||
|
||||
// Build sunrise/sunset map keyed by local date string from Open-Meteo
|
||||
// Open-Meteo returns these as local time ISO strings e.g. "2026-03-01T06:45"
|
||||
const sunMap = {}
|
||||
if (data.daily?.time) {
|
||||
data.daily.time.forEach((date, i) => {
|
||||
sunMap[date] = { sunrise: data.daily.sunrise[i], sunset: data.daily.sunset[i] }
|
||||
sunMap[date] = {
|
||||
sunrise: data.daily.sunrise[i] ? new Date(data.daily.sunrise[i]) : null,
|
||||
sunset: data.daily.sunset[i] ? new Date(data.daily.sunset[i]) : null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.all(data.hourly.time.map(async (time, i) => {
|
||||
const date = time.slice(0, 10)
|
||||
const sun = sunMap[date] ?? {}
|
||||
const t = new Date(time).getTime()
|
||||
const daytime = sun.sunrise && sun.sunset
|
||||
? t >= new Date(sun.sunrise).getTime() && t < new Date(sun.sunset).getTime()
|
||||
// Open-Meteo returns local time strings — treat as UTC-equivalent for Date parsing
|
||||
// then key into sunMap using the local date portion
|
||||
const slotDate = new Date(time) // parsed as local ISO (no Z suffix) — browser/node both treat naive as local
|
||||
const dateKey = localDateStr(slotDate)
|
||||
const sun = sunMap[dateKey] ?? {}
|
||||
const t = slotDate.getTime()
|
||||
const daytime = sun.sunrise && sun.sunset
|
||||
? t >= sun.sunrise.getTime() && t < sun.sunset.getTime()
|
||||
: false
|
||||
|
||||
const code = data.hourly.weathercode[i]
|
||||
const icon = await ensureIcon(code).catch(() => null)
|
||||
const temp = data.hourly.temperature_2m[i]
|
||||
@@ -205,7 +206,7 @@ export async function hourlyWeather(lat, lon, start, end) {
|
||||
const clouds = Math.round(data.hourly.cloudcover[i]) / 100
|
||||
|
||||
return {
|
||||
time: time,
|
||||
time: slotDate,
|
||||
daytime,
|
||||
label: WMO[code] || 'Unknown',
|
||||
icon,
|
||||
@@ -215,8 +216,7 @@ export async function hourlyWeather(lat, lon, start, end) {
|
||||
humidity_abs: absoluteHumidity(temp, humidity),
|
||||
dew_point: dewPoint(temp, humidity),
|
||||
heat_index: heatIndex(temp, humidity),
|
||||
vapor_pressure_deficit: data.hourly.vapour_pressure_deficit[i]
|
||||
?? vaporPressureDeficit(temp, humidity),
|
||||
vapor_pressure_deficit: data.hourly.vapour_pressure_deficit[i] ?? vaporPressureDeficit(temp, humidity),
|
||||
pressure_hpa: data.hourly.surface_pressure[i],
|
||||
precipitation: data.hourly.precipitation[i],
|
||||
precipitation_chance: data.hourly.precipitation_probability[i],
|
||||
@@ -228,7 +228,7 @@ export async function hourlyWeather(lat, lon, start, end) {
|
||||
solar_wm2: data.hourly.shortwave_radiation[i],
|
||||
clouds_label: cloudsLabel(clouds),
|
||||
visibility: data.hourly.visibility[i] != null
|
||||
? Math.round(data.hourly.visibility[i] / 1000 * 10) / 10 // m → km
|
||||
? Math.round(data.hourly.visibility[i] / 1000 * 10) / 10
|
||||
: null,
|
||||
}
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user