function parseWeatherStatus(raw) { try { var data = JSON.parse(String(raw || "{}")) return { label: data.text || "", klass: data.class || "" } } catch (e) { return { label: "", klass: "" } } } function isFutureForecastDate(dateString, todayString) { if (!dateString) return false return String(dateString).slice(0, 10) > String(todayString || "") } function roundedTemp(value) { if (value === undefined || value === null || value === "") return "" var n = parseFloat(String(value)) return isNaN(n) ? "" : String(Math.round(n)) } function celsiusToFahrenheit(value) { if (value === undefined || value === null || value === "") return "" var n = parseFloat(String(value)) return isNaN(n) ? "" : (n * 9 / 5) + 32 } function formatTemp(value, useImperial) { if (value === undefined || value === null || value === "") return "" return value + "°" + (useImperial ? "F" : "C") } function dayName(dateString, formatter) { if (!dateString) return "" var d = new Date(dateString + "T12:00:00") if (isNaN(d.getTime())) return "" if (formatter) return formatter(d) return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][d.getDay()] } function openMeteoForecastDays(dailyForecastReport, todayString) { var daily = dailyForecastReport && dailyForecastReport.daily ? dailyForecastReport.daily : null if (!daily || !daily.time) return [] var result = [] for (var i = 0; i < daily.time.length && result.length < 3; ++i) { var date = daily.time[i] if (!isFutureForecastDate(date, todayString)) continue var maxC = daily.temperature_2m_max ? daily.temperature_2m_max[i] : "" var minC = daily.temperature_2m_min ? daily.temperature_2m_min[i] : "" result.push({ date: date, maxtempC: roundedTemp(maxC), mintempC: roundedTemp(minC), maxtempF: roundedTemp(celsiusToFahrenheit(maxC)), mintempF: roundedTemp(celsiusToFahrenheit(minC)), openMeteoWeatherCode: daily.weather_code ? daily.weather_code[i] : null }) } return result } function wttrNextForecastDays(report, todayString) { var days = report && report.weather ? report.weather : [] var result = [] for (var i = 0; i < days.length && result.length < 3; ++i) { if (isFutureForecastDate(days[i].date, todayString)) result.push(days[i]) } return result } function buildForecastDays(report, dailyForecastReport, todayString) { var days = openMeteoForecastDays(dailyForecastReport, todayString) return days.length > 0 ? days : wttrNextForecastDays(report, todayString) } function bareTempForDay(day, kind, useImperial) { if (!day) return "" var v = useImperial ? (kind === "max" ? day.maxtempF : day.mintempF) : (kind === "max" ? day.maxtempC : day.mintempC) if (v === undefined || v === null || v === "") return "" return v + "°" } function dayIcon(day) { if (!day) return "" if (day.openMeteoWeatherCode !== undefined && day.openMeteoWeatherCode !== null) return iconForOpenMeteoCode(day.openMeteoWeatherCode) if (!day.hourly || day.hourly.length === 0) return "" var best = day.hourly[0] var bestDist = 9999 for (var i = 0; i < day.hourly.length; ++i) { var t = parseInt(String(day.hourly[i].time || "0"), 10) var dist = Math.abs(t - 1200) if (dist < bestDist) { bestDist = dist best = day.hourly[i] } } return iconForCode(best.weatherCode, false) } function iconForOpenMeteoCode(code) { var c = parseInt(String(code || "0"), 10) if (c === 0) return iconForCode(113, false) if (c === 1 || c === 2) return iconForCode(116, false) if (c === 3) return iconForCode(119, false) if (c === 45 || c === 48) return iconForCode(143, false) if (c === 51 || c === 53 || c === 55 || c === 56 || c === 57 || c === 61) return iconForCode(266, false) if (c === 63 || c === 65 || c === 66 || c === 67 || c === 80 || c === 81 || c === 82) return iconForCode(308, false) if (c === 71 || c === 73 || c === 75 || c === 77 || c === 85 || c === 86) return iconForCode(338, false) if (c === 95 || c === 96 || c === 99) return iconForCode(389, false) return iconForCode(119, false) } function iconForCode(code, night) { var c = parseInt(String(code || "0"), 10) switch (c) { case 113: return night ? "" : "" case 116: return night ? "" : "" case 119: case 122: return "" case 143: case 248: case 260: return "" case 176: case 263: case 353: return night ? "" : "" case 179: case 227: case 230: case 323: case 326: case 368: return night ? "" : "" case 182: case 185: case 281: case 284: case 311: case 314: case 317: case 320: case 350: case 362: case 365: case 374: case 377: return "" case 200: case 386: case 389: case 392: case 395: return "" case 266: case 293: case 296: case 299: case 302: case 305: case 308: case 356: case 359: return "" case 329: case 332: case 335: case 338: case 371: return "" default: return "" } } if (typeof module !== "undefined") { module.exports = { parseWeatherStatus: parseWeatherStatus, isFutureForecastDate: isFutureForecastDate, roundedTemp: roundedTemp, celsiusToFahrenheit: celsiusToFahrenheit, formatTemp: formatTemp, dayName: dayName, openMeteoForecastDays: openMeteoForecastDays, wttrNextForecastDays: wttrNextForecastDays, buildForecastDays: buildForecastDays, bareTempForDay: bareTempForDay, dayIcon: dayIcon, iconForOpenMeteoCode: iconForOpenMeteoCode, iconForCode: iconForCode } }