aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/weather.lua27
-rw-r--r--test/test_weather.lua13
2 files changed, 30 insertions, 10 deletions
diff --git a/lib/weather.lua b/lib/weather.lua
index aea630e..d4b1460 100644
--- a/lib/weather.lua
+++ b/lib/weather.lua
@@ -76,22 +76,25 @@ function M.describe(code)
return DESCRIBE[code] or 'Unknown'
end
-local function round(v) return math.floor(v + 0.5) end
+-- Nearest integer, or nil when v is NaN, infinite or too big for an integer:
+-- %d and utf8.char raise on those, and a raise is a blank dashboard.
+local function round(v) return math.tointeger(math.floor(v + 0.5)) end
-- A temperature as the cards print it. Missing is '--', never '0': zero is a
--- legitimate reading. The '-0' a value just under zero would round to is
--- folded into '0'.
+-- legitimate reading.
function M.deg(v)
if type(v) ~= 'number' then return '--' end
local r = round(v)
- if r == 0 then r = 0 end
+ if not r then return '--' end
return string.format('%d\u{00B0}', r)
end
-- A rounded number with an optional suffix, or '--'.
function M.num(v, suffix)
if type(v) ~= 'number' then return '--' end
- return string.format('%d', round(v)) .. (suffix or '')
+ local r = round(v)
+ if not r then return '--' end
+ return string.format('%d', r) .. (suffix or '')
end
-- The fetcher asks Open-Meteo for km/h or mph to match UNITS.
@@ -101,8 +104,11 @@ end
-- Seconds as '10h 28m'.
function M.duration(s)
- if type(s) ~= 'number' or s < 0 then return '--' end
- return string.format('%dh %02dm', math.floor(s / 3600), math.floor(s % 3600 / 60))
+ -- ponytail: anything over 1e9 s (about 31 years) is bad data, not a duration.
+ if type(s) ~= 'number' or not (s >= 0 and s < 1e9) then return '--' end
+ local h, m = math.tointeger(math.floor(s / 3600)), math.tointeger(math.floor(s % 3600 / 60))
+ if not (h and m) then return '--' end
+ return string.format('%dh %02dm', h, m)
end
-- Threshold role names, which index the palette (colors.ok and so on).
@@ -136,7 +142,9 @@ end
-- One of the 28 Nerd Font moon glyphs, U+E38D (new) to U+E3A8, in phase order.
function M.moon_icon(phase)
if type(phase) ~= 'number' then return M.ICON.unknown end
- return utf8.char(0xE38D + round(phase * 28) % 28)
+ local r = round(phase * 28)
+ if not r then return M.ICON.unknown end
+ return utf8.char(0xE38D + r % 28)
end
-- Eight compass points. The half-step offset is what makes north straddle
@@ -160,7 +168,7 @@ function M.sun_t(now, sunrise, sunset)
local span = sunset - sunrise
if span <= 0 then return 0 end -- polar day/night or bad data: no division
local t = (now - sunrise) / span
- if t < 0 then return 0 elseif t > 1 then return 1 end
+ if not (t >= 0) then return 0 elseif t > 1 then return 1 end
return t
end
@@ -228,6 +236,7 @@ end
function M.age_str(dt, now)
if type(dt) ~= 'number' or type(now) ~= 'number' then return '?' end
local s = now - dt
+ if s ~= s then return '?' end
if s < 0 then s = 0 end
if s < 3600 then return math.floor(s / 60) .. 'm' end
if s < 86400 then return math.floor(s / 3600) .. 'h' end
diff --git a/test/test_weather.lua b/test/test_weather.lua
index cea4645..e7d7614 100644
--- a/test/test_weather.lua
+++ b/test/test_weather.lua
@@ -64,9 +64,20 @@ assert(weather.describe(nil) == 'Unknown', 'nil code words')
assert(weather.deg(13.2) == '13\u{00B0}', 'rounds down')
assert(weather.deg(13.5) == '14\u{00B0}', 'rounds half up')
assert(weather.deg(-0.4) == '0\u{00B0}', 'just below zero reads 0')
-assert(weather.deg(-2.6) == '-3\u{00B0}', 'negative rounds away from zero')
+assert(weather.deg(-2.6) == '-3\u{00B0}', 'negative rounds to nearest')
assert(weather.deg(nil) == '--', 'missing temperature is --, never 0')
+-- Non-finite input must not raise (1e999 parses to inf from a cache line).
+local NAN, INF = 0/0, math.huge
+for _, bad in ipairs({ NAN, INF, -INF, 1e300, math.maxinteger }) do
+ assert(weather.deg(bad) == '--', 'deg non-finite ' .. tostring(bad))
+ assert(weather.num(bad) == '--', 'num non-finite ' .. tostring(bad))
+ assert(weather.duration(bad) == '--', 'duration non-finite ' .. tostring(bad))
+ assert(weather.moon_icon(bad) == weather.ICON.unknown, 'moon_icon non-finite ' .. tostring(bad))
+end
+assert(weather.sun_t(NAN, 1000, 2000) == 0, 'NaN now parks at 0')
+assert(weather.age_str(NAN, 0) == '?', 'NaN age is ?')
+
assert(weather.num(77) == '77', 'plain number')
assert(weather.num(39.6, '%') == '40%', 'rounded with suffix')
assert(weather.num(0, '%') == '0%', 'zero is a real reading')