diff --git a/src/commands/earnings.zig b/src/commands/earnings.zig index 4207bac..16e2c2b 100644 --- a/src/commands/earnings.zig +++ b/src/commands/earnings.zig @@ -16,6 +16,15 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const }; defer allocator.free(result.data); + // Sort chronologically (oldest first) — providers may return in any order + if (result.data.len > 1) { + std.mem.sort(zfin.EarningsEvent, result.data, {}, struct { + fn f(_: void, a: zfin.EarningsEvent, b: zfin.EarningsEvent) bool { + return a.date.days < b.date.days; + } + }.f); + } + if (result.source == .cached) try cli.stderrPrint("(using cached earnings data)\n"); try display(result.data, symbol, color, out); @@ -64,32 +73,8 @@ pub fn display(events: []const zfin.EarningsEvent, symbol: []const u8, color: bo try out.print("\n{d} earnings event(s)\n\n", .{events.len}); } -pub fn fmtEps(val: f64) [12]u8 { - var buf: [12]u8 = .{' '} ** 12; - _ = std.fmt.bufPrint(&buf, "${d:.2}", .{val}) catch {}; - return buf; -} - // ── Tests ──────────────────────────────────────────────────── -test "fmtEps formats positive value" { - const result = fmtEps(1.25); - const trimmed = std.mem.trimRight(u8, &result, &.{' '}); - try std.testing.expectEqualStrings("$1.25", trimmed); -} - -test "fmtEps formats negative value" { - const result = fmtEps(-0.50); - const trimmed = std.mem.trimRight(u8, &result, &.{' '}); - try std.testing.expect(std.mem.indexOf(u8, trimmed, "0.5") != null); -} - -test "fmtEps formats zero" { - const result = fmtEps(0.0); - const trimmed = std.mem.trimRight(u8, &result, &.{' '}); - try std.testing.expectEqualStrings("$0.00", trimmed); -} - test "display shows earnings with beat" { var buf: [4096]u8 = undefined; var w: std.Io.Writer = .fixed(&buf); diff --git a/src/tui.zig b/src/tui.zig index fd016d8..e634a30 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -1117,29 +1117,7 @@ pub const App = struct { } fn loadEarningsData(self: *App) void { - self.earnings_loaded = true; - self.freeEarnings(); - - const result = self.svc.getEarnings(self.symbol) catch |err| { - switch (err) { - zfin.DataError.NoApiKey => self.setStatus("No API key. Set FINNHUB_API_KEY"), - zfin.DataError.FetchFailed => { - self.earnings_disabled = true; - self.setStatus("No earnings data (ETF/index?)"); - }, - else => self.setStatus("Error loading earnings"), - } - return; - }; - self.earnings_data = result.data; - self.earnings_timestamp = result.timestamp; - - if (result.data.len == 0) { - self.earnings_disabled = true; - self.setStatus("No earnings data available (ETF/index?)"); - return; - } - self.setStatus(if (result.source == .cached) "r/F5 to refresh" else "Fetched | r/F5 to refresh"); + earnings_tab.loadData(self); } fn loadOptionsData(self: *App) void { diff --git a/src/tui/earnings_tab.zig b/src/tui/earnings_tab.zig index 8e53be5..e64307d 100644 --- a/src/tui/earnings_tab.zig +++ b/src/tui/earnings_tab.zig @@ -27,6 +27,15 @@ pub fn loadData(self: *App) void { self.earnings_data = result.data; self.earnings_timestamp = result.timestamp; + // Sort chronologically (oldest first) — providers may return in any order + if (result.data.len > 1) { + std.mem.sort(zfin.EarningsEvent, result.data, {}, struct { + fn f(_: void, a: zfin.EarningsEvent, b: zfin.EarningsEvent) bool { + return a.date.days < b.date.days; + } + }.f); + } + if (result.data.len == 0) { self.earnings_disabled = true; self.setStatus("No earnings data available (ETF/index?)"); @@ -81,15 +90,15 @@ pub fn renderEarningsLines( return lines.toOwnedSlice(arena); } - try lines.append(arena, .{ .text = try std.fmt.allocPrint(arena, " {s:>12} {s:>4} {s:>12} {s:>12} {s:>12} {s:>10}", .{ - "Date", "Q", "EPS Est", "EPS Act", "Surprise", "Surprise %", + try lines.append(arena, .{ .text = try std.fmt.allocPrint(arena, " {s:>12} {s:>4} {s:>12} {s:>12} {s:>12} {s:>10} {s:>5}", .{ + "Date", "Q", "EPS Est", "EPS Act", "Surprise", "Surprise %", "When", }), .style = th.mutedStyle() }); for (ev) |e| { var row_buf: [128]u8 = undefined; const row = fmt.fmtEarningsRow(&row_buf, e); - const text = try std.fmt.allocPrint(arena, " {s}", .{row.text}); + const text = try std.fmt.allocPrint(arena, " {s} {s:>5}", .{ row.text, @tagName(e.report_time) }); const row_style = if (row.is_future) th.mutedStyle() else if (row.is_positive) th.positiveStyle() else th.negativeStyle(); try lines.append(arena, .{ .text = text, .style = row_style });