@@ -185,8 +185,8 @@ function M.heatmap(days)
185185
186186 -- Helper: Calculate date range for a week (numeric format: "02-08" or "30-05")
187187 local function get_week_range (week )
188- local first_day = week [1 ].date -- Monday
189- local last_day = week [7 ].date -- Sunday
188+ local first_day = week [1 ].date
189+ local last_day = week [# week ].date -- Last day of week (may be incomplete)
190190
191191 local f_parsed = parse_date (first_day )
192192 local l_parsed = parse_date (last_day )
@@ -202,22 +202,28 @@ function M.heatmap(days)
202202 for j = i , math.min (i + 6 , # days ) do
203203 week [# week + 1 ] = days [j ]
204204 end
205- if # week == 7 then -- Only add complete weeks
205+ -- Accept incomplete weeks (for smart date ranges)
206+ if # week > 0 then
206207 weeks [# weeks + 1 ] = week
207208 end
208209 end
209210
210211 -- Calculate statistics for summary
211212 local max_time = 0
212213 local active_days = 0
214+ local total_non_future_days = 0
213215 for _ , day in ipairs (days ) do
214- if day .time and day .time > 0 then
215- active_days = active_days + 1
216- max_time = math.max (max_time , day .time )
216+ -- Only count non-future days
217+ if day .level ~= - 1 then
218+ total_non_future_days = total_non_future_days + 1
219+ if day .time and day .time > 0 then
220+ active_days = active_days + 1
221+ max_time = math.max (max_time , day .time )
222+ end
217223 end
218224 end
219225
220- local consistency = # days > 0 and math.floor ((active_days / # days ) * 100 ) or 0
226+ local consistency = total_non_future_days > 0 and math.floor ((active_days / total_non_future_days ) * 100 ) or 0
221227
222228 -- Format max time as "4h 10m"
223229 local function format_time (seconds )
@@ -286,7 +292,7 @@ function M.heatmap(days)
286292 { { " 🗓️ Activity Heatmap (Last " .. # weeks .. " Weeks)" , " exgreen" } },
287293 {
288294 { " 📊 " , " exyellow" },
289- { string.format (" %d/%d days active" , active_days , # days ), " normal" },
295+ { string.format (" %d/%d days active" , active_days , total_non_future_days ), " normal" },
290296 { string.format (" (%d%% consistency)" , consistency ), " commentfg" },
291297 { " • Peak: " , " commentfg" },
292298 { format_time (max_time ), " exgreen" },
@@ -313,22 +319,28 @@ function M.heatmap(days)
313319 local line = { { " " .. week_info .label .. " " , week_info .show_year and " exgreen" or " commentfg" } }
314320
315321 -- Day cells
316- for _ , day in ipairs (week_info .week ) do
317- local lvl = math.max (0 , math.min (4 , day .level or 0 ))
318-
319- -- Mark current week's today with *
320- local is_today = (day .date == today )
321- local cell = chars [lvl + 1 ]
322-
323- if day .level == - 1 then
324- -- Future day
325- table.insert (line , { " · " , " commentfg" })
326- else
327- if is_today and is_current then
328- table.insert (line , { cell .. " * " , colors [lvl + 1 ] })
322+ for i = 1 , 7 do
323+ local day = week_info .week [i ]
324+ if day then
325+ local lvl = math.max (0 , math.min (4 , day .level or 0 ))
326+
327+ -- Mark current week's today with *
328+ local is_today = (day .date == today )
329+ local cell = chars [lvl + 1 ]
330+
331+ if day .level == - 1 then
332+ -- Future day
333+ table.insert (line , { " · " , " commentfg" })
329334 else
330- table.insert (line , { cell .. " " , colors [lvl + 1 ] })
335+ if is_today and is_current then
336+ table.insert (line , { cell .. " * " , colors [lvl + 1 ] })
337+ else
338+ table.insert (line , { cell .. " " , colors [lvl + 1 ] })
339+ end
331340 end
341+ else
342+ -- Pad incomplete weeks with empty cells
343+ table.insert (line , { " " , " normal" })
332344 end
333345 end
334346
0 commit comments