diff --git a/src/cli.ts b/src/cli.ts index 494e313..6090495 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,7 +8,7 @@ import { renderStatusBar } from './format.js' import { type PeriodData, type ProviderCost } from './menubar-json.js' import { buildMenubarPayload } from './menubar-json.js' import { addNewDays, getDaysInRange, loadDailyCache, saveDailyCache, withDailyCacheLock } from './daily-cache.js' -import { aggregateProjectsIntoDays, buildPeriodDataFromDays } from './day-aggregator.js' +import { aggregateProjectsIntoDays, buildPeriodDataFromDays, dateKey } from './day-aggregator.js' import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js' import { renderDashboard } from './dashboard.js' import { parseDateRangeFlags } from './cli-date.js' @@ -25,7 +25,7 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000 const BACKFILL_DAYS = 365 function toDateString(date: Date): string { - return date.toISOString().slice(0, 10) + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` } function getDateRange(period: string): { range: DateRange; label: string } { @@ -35,12 +35,12 @@ function getDateRange(period: string): { range: DateRange; label: string } { switch (period) { case 'today': { const start = new Date(now.getFullYear(), now.getMonth(), now.getDate()) - return { range: { start, end }, label: `Today (${start.toISOString().slice(0, 10)})` } + return { range: { start, end }, label: `Today (${toDateString(start)})` } } case 'yesterday': { const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1) const yesterdayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 23, 59, 59, 999) - return { range: { start, end: yesterdayEnd }, label: `Yesterday (${start.toISOString().slice(0, 10)})` } + return { range: { start, end: yesterdayEnd }, label: `Yesterday (${toDateString(start)})` } } case 'week': { const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7) @@ -123,7 +123,7 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey: for (const sess of sessions) { for (const turn of sess.turns) { if (!turn.timestamp) { continue } - const day = turn.timestamp.slice(0, 10) + const day = dateKey(turn.timestamp) if (!dailyMap[day]) { dailyMap[day] = { cost: 0, calls: 0 } } for (const call of turn.assistantCalls) { dailyMap[day].cost += call.costUSD @@ -204,7 +204,7 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey: Object.entries(m).sort(([, a], [, b]) => b - a).map(([name, calls]) => ({ name, calls })) const topSessions = projects - .flatMap(p => p.sessions.map(s => ({ project: p.project, sessionId: s.sessionId, date: s.firstTimestamp?.slice(0, 10) ?? null, cost: convertCost(s.totalCostUSD), calls: s.apiCalls }))) + .flatMap(p => p.sessions.map(s => ({ project: p.project, sessionId: s.sessionId, date: s.firstTimestamp ? dateKey(s.firstTimestamp) : null, cost: convertCost(s.totalCostUSD), calls: s.apiCalls }))) .sort((a, b) => b.cost - a.cost) .slice(0, 5) @@ -545,7 +545,7 @@ program return } - const defaultName = `codeburn-${new Date().toISOString().slice(0, 10)}` + const defaultName = `codeburn-${toDateString(new Date())}` const outputPath = opts.output ?? `${defaultName}.${opts.format}` let savedPath: string diff --git a/src/dashboard.tsx b/src/dashboard.tsx index a4e5cde..99163aa 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -9,6 +9,7 @@ import { loadPricing } from './models.js' import { getAllProviders } from './providers/index.js' import { scanAndDetect, type WasteFinding, type WasteAction, type OptimizeResult } from './optimize.js' import { estimateContextBudget, discoverProjectCwd, type ContextBudget } from './context-budget.js' +import { dateKey } from './day-aggregator.js' import { join } from 'path' type Period = 'today' | 'week' | '30days' | 'month' | 'all' @@ -195,7 +196,7 @@ function DailyActivity({ projects, days = 14, pw, bw }: { projects: ProjectSumma for (const session of project.sessions) { for (const turn of session.turns) { if (!turn.timestamp) continue - const day = turn.timestamp.slice(0, 10) + const day = dateKey(turn.timestamp) dailyCosts[day] = (dailyCosts[day] ?? 0) + turn.assistantCalls.reduce((s, c) => s + c.costUSD, 0) dailyCalls[day] = (dailyCalls[day] ?? 0) + turn.assistantCalls.length } diff --git a/src/day-aggregator.ts b/src/day-aggregator.ts index 5030f8d..bc63fa6 100644 --- a/src/day-aggregator.ts +++ b/src/day-aggregator.ts @@ -20,8 +20,9 @@ function emptyEntry(date: string): DailyEntry { } } -function dateKey(iso: string): string { - return iso.slice(0, 10) +export function dateKey(iso: string): string { + const d = new Date(iso) + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } export function aggregateProjectsIntoDays(projects: ProjectSummary[]): DailyEntry[] { diff --git a/src/export.ts b/src/export.ts index 58f250e..2ef75bd 100644 --- a/src/export.ts +++ b/src/export.ts @@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'path' import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types.js' import { getCurrency, convertCost } from './currency.js' +import { dateKey } from './day-aggregator.js' function escCsv(s: string): string { const sanitized = /^[=+\-@]/.test(s) ? `'${s}` : s @@ -48,7 +49,7 @@ function buildDailyRows(projects: ProjectSummary[], period: string): Row[] { for (const session of project.sessions) { for (const turn of session.turns) { if (!turn.timestamp) continue - const day = turn.timestamp.slice(0, 10) + const day = dateKey(turn.timestamp) if (!daily[day]) { daily[day] = { cost: 0, calls: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, sessions: new Set() } }