Skip to content

Commit f9b2906

Browse files
committed
Trim menu formatting work
1 parent ac01d73 commit f9b2906

4 files changed

Lines changed: 132 additions & 27 deletions

File tree

Sources/CodexBar/MenuCardView+Costs.swift

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,35 +133,77 @@ extension UsageMenuCardView.Model {
133133
private static func bedrockLatestBillingDay(from entries: [CostUsageDailyReport.Entry])
134134
-> CostUsageDailyReport.Entry?
135135
{
136-
entries.max { lhs, rhs in
137-
let lDate = Self.bedrockBillingDate(from: lhs.date) ?? .distantPast
138-
let rDate = Self.bedrockBillingDate(from: rhs.date) ?? .distantPast
139-
if lDate != rDate { return lDate < rDate }
140-
let lCost = lhs.costUSD ?? -1
141-
let rCost = rhs.costUSD ?? -1
136+
entries.compactMap { entry -> (entry: CostUsageDailyReport.Entry, dayKey: String)? in
137+
guard let dayKey = bedrockBillingDayKey(from: entry.date) else { return nil }
138+
return (entry, dayKey)
139+
}
140+
.max { lhs, rhs in
141+
if lhs.dayKey != rhs.dayKey { return lhs.dayKey < rhs.dayKey }
142+
let lCost = lhs.entry.costUSD ?? -1
143+
let rCost = rhs.entry.costUSD ?? -1
142144
if lCost != rCost { return lCost < rCost }
143-
let lTokens = lhs.totalTokens ?? -1
144-
let rTokens = rhs.totalTokens ?? -1
145+
let lTokens = lhs.entry.totalTokens ?? -1
146+
let rTokens = rhs.entry.totalTokens ?? -1
145147
if lTokens != rTokens { return lTokens < rTokens }
146-
return lhs.date < rhs.date
147-
}
148+
return lhs.entry.date < rhs.entry.date
149+
}?.entry
148150
}
149151

150152
private static func bedrockDisplayDate(from text: String) -> String? {
151-
guard let date = bedrockBillingDate(from: text) else { return nil }
152-
let formatter = DateFormatter()
153-
formatter.locale = Locale(identifier: "en_US_POSIX")
154-
formatter.timeZone = TimeZone(secondsFromGMT: 0)
155-
formatter.dateFormat = "MMM d"
156-
return formatter.string(from: date)
153+
guard let dayKey = bedrockBillingDayKey(from: text) else { return nil }
154+
let monthStart = dayKey.index(dayKey.startIndex, offsetBy: 5)
155+
let monthEnd = dayKey.index(monthStart, offsetBy: 2)
156+
let dayStart = dayKey.index(dayKey.startIndex, offsetBy: 8)
157+
guard
158+
let month = Int(dayKey[monthStart..<monthEnd]),
159+
let day = Int(dayKey[dayStart...]),
160+
(1...Self.bedrockMonthAbbreviations.count).contains(month),
161+
(1...31).contains(day)
162+
else { return nil }
163+
return "\(Self.bedrockMonthAbbreviations[month - 1]) \(day)"
157164
}
158165

159-
private static func bedrockBillingDate(from text: String) -> Date? {
160-
let formatter = DateFormatter()
161-
formatter.locale = Locale(identifier: "en_US_POSIX")
162-
formatter.timeZone = TimeZone(secondsFromGMT: 0)
163-
formatter.dateFormat = "yyyy-MM-dd"
164-
return formatter.date(from: text.trimmingCharacters(in: .whitespacesAndNewlines))
166+
private static let bedrockMonthAbbreviations = [
167+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
168+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
169+
]
170+
171+
private static func bedrockBillingDayKey(from text: String) -> String? {
172+
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
173+
guard trimmed.count == 10 else { return nil }
174+
for (offset, character) in trimmed.enumerated() {
175+
switch offset {
176+
case 4, 7:
177+
guard character == "-" else { return nil }
178+
default:
179+
guard character.isNumber else { return nil }
180+
}
181+
}
182+
let monthStart = trimmed.index(trimmed.startIndex, offsetBy: 5)
183+
let monthEnd = trimmed.index(monthStart, offsetBy: 2)
184+
let dayStart = trimmed.index(trimmed.startIndex, offsetBy: 8)
185+
let yearEnd = trimmed.index(trimmed.startIndex, offsetBy: 4)
186+
guard
187+
let year = Int(trimmed[..<yearEnd]),
188+
let month = Int(trimmed[monthStart..<monthEnd]),
189+
let day = Int(trimmed[dayStart...]),
190+
(1...Self.bedrockMonthAbbreviations.count).contains(month),
191+
(1...Self.daysInBedrockBillingMonth(month, year: year)).contains(day)
192+
else { return nil }
193+
return trimmed
194+
}
195+
196+
private static func daysInBedrockBillingMonth(_ month: Int, year: Int) -> Int {
197+
switch month {
198+
case 2:
199+
if year.isMultiple(of: 400) { return 29 }
200+
if year.isMultiple(of: 100) { return 28 }
201+
return year.isMultiple(of: 4) ? 29 : 28
202+
case 4, 6, 9, 11:
203+
return 30
204+
default:
205+
return 31
206+
}
165207
}
166208

167209
static func providerCostSection(

Sources/CodexBarCore/UsageFormatter.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,7 @@ public enum UsageFormatter {
241241
return "\(sign)\(formatted)\(unit.suffix)"
242242
}
243243

244-
let formatter = NumberFormatter()
245-
formatter.numberStyle = .decimal
246-
formatter.usesGroupingSeparator = true
247-
formatter.locale = Locale(identifier: "en_US_POSIX")
248-
return formatter.string(from: NSNumber(value: value)) ?? "\(value)"
244+
return "\(value)"
249245
}
250246

251247
public static func byteCountString(_ bytes: Int64) -> String {

Tests/CodexBarTests/BedrockMenuCardTests.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,64 @@ struct BedrockMenuCardTests {
5050
#expect(model.tokenUsage?.monthLine == "Last 7 days: $56.78")
5151
#expect(model.tokenUsage?.hintLine == "AWS Cost Explorer billing can lag.")
5252
}
53+
54+
@Test
55+
func `bedrock cost section picks latest valid billing day`() throws {
56+
let now = Date()
57+
let metadata = try #require(ProviderDefaults.metadata[.bedrock])
58+
let tokenSnapshot = CostUsageTokenSnapshot(
59+
sessionTokens: nil,
60+
sessionCostUSD: 12.34,
61+
last30DaysTokens: nil,
62+
last30DaysCostUSD: 56.78,
63+
historyDays: 7,
64+
daily: [
65+
CostUsageDailyReport.Entry(
66+
date: "not-a-day",
67+
inputTokens: nil,
68+
outputTokens: nil,
69+
totalTokens: 10,
70+
costUSD: 99,
71+
modelsUsed: ["Amazon Bedrock"],
72+
modelBreakdowns: nil),
73+
CostUsageDailyReport.Entry(
74+
date: "2026-05-12",
75+
inputTokens: nil,
76+
outputTokens: nil,
77+
totalTokens: 20,
78+
costUSD: 12.34,
79+
modelsUsed: ["Amazon Bedrock"],
80+
modelBreakdowns: nil),
81+
CostUsageDailyReport.Entry(
82+
date: "2026-05-13",
83+
inputTokens: nil,
84+
outputTokens: nil,
85+
totalTokens: 30,
86+
costUSD: 23.45,
87+
modelsUsed: ["Amazon Bedrock"],
88+
modelBreakdowns: nil),
89+
],
90+
updatedAt: now)
91+
let model = UsageMenuCardView.Model.make(.init(
92+
provider: .bedrock,
93+
metadata: metadata,
94+
snapshot: nil,
95+
credits: nil,
96+
creditsError: nil,
97+
dashboard: nil,
98+
dashboardError: nil,
99+
tokenSnapshot: tokenSnapshot,
100+
tokenError: nil,
101+
account: AccountInfo(email: nil, plan: nil),
102+
isRefreshing: false,
103+
lastError: nil,
104+
usageBarsShowUsed: false,
105+
resetTimeDisplayStyle: .countdown,
106+
tokenCostUsageEnabled: true,
107+
showOptionalCreditsAndExtraUsage: true,
108+
hidePersonalInfo: false,
109+
now: now))
110+
111+
#expect(model.tokenUsage?.sessionLine == "Latest billing day (May 13): $12.34")
112+
}
53113
}

Tests/CodexBarTests/UsageFormatterTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ struct UsageFormatterTests {
230230
#expect(UsageFormatter.modelCostDetail("custom-model", costUSD: nil, totalTokens: 987) == "987")
231231
}
232232

233+
@Test
234+
func `token count string formats small values without grouping`() {
235+
#expect(UsageFormatter.tokenCountString(0) == "0")
236+
#expect(UsageFormatter.tokenCountString(987) == "987")
237+
#expect(UsageFormatter.tokenCountString(-42) == "-42")
238+
}
239+
233240
@Test
234241
func `clean plan maps O auth to ollama`() {
235242
#expect(UsageFormatter.cleanPlanName("oauth") == "Ollama")

0 commit comments

Comments
 (0)