Skip to content

Commit 72ccf34

Browse files
committed
fix: use local timezone for daily date bucketing instead of UTC
Timestamps in session files are UTC ISO strings. Several code paths extracted the date via .slice(0, 10) which gives the UTC date, while date range filtering uses local-time boundaries. This caused turns between UTC midnight and local midnight to be bucketed under the wrong day -- the menubar showed lower today cost than the TUI because those turns were attributed to tomorrow (UTC) but filtered as today (local). format.ts already had a localDateString fix; this applies the same pattern everywhere via dateKey() in day-aggregator.ts.
1 parent 888030f commit 72ccf34

4 files changed

Lines changed: 14 additions & 11 deletions

File tree

src/cli.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { renderStatusBar } from './format.js'
88
import { type PeriodData, type ProviderCost } from './menubar-json.js'
99
import { buildMenubarPayload } from './menubar-json.js'
1010
import { addNewDays, getDaysInRange, loadDailyCache, saveDailyCache, withDailyCacheLock } from './daily-cache.js'
11-
import { aggregateProjectsIntoDays, buildPeriodDataFromDays } from './day-aggregator.js'
11+
import { aggregateProjectsIntoDays, buildPeriodDataFromDays, dateKey } from './day-aggregator.js'
1212
import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js'
1313
import { renderDashboard } from './dashboard.js'
1414
import { parseDateRangeFlags } from './cli-date.js'
@@ -25,7 +25,7 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000
2525
const BACKFILL_DAYS = 365
2626

2727
function toDateString(date: Date): string {
28-
return date.toISOString().slice(0, 10)
28+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
2929
}
3030

3131
function getDateRange(period: string): { range: DateRange; label: string } {
@@ -35,12 +35,12 @@ function getDateRange(period: string): { range: DateRange; label: string } {
3535
switch (period) {
3636
case 'today': {
3737
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
38-
return { range: { start, end }, label: `Today (${start.toISOString().slice(0, 10)})` }
38+
return { range: { start, end }, label: `Today (${toDateString(start)})` }
3939
}
4040
case 'yesterday': {
4141
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1)
4242
const yesterdayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 23, 59, 59, 999)
43-
return { range: { start, end: yesterdayEnd }, label: `Yesterday (${start.toISOString().slice(0, 10)})` }
43+
return { range: { start, end: yesterdayEnd }, label: `Yesterday (${toDateString(start)})` }
4444
}
4545
case 'week': {
4646
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7)
@@ -123,7 +123,7 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey:
123123
for (const sess of sessions) {
124124
for (const turn of sess.turns) {
125125
if (!turn.timestamp) { continue }
126-
const day = turn.timestamp.slice(0, 10)
126+
const day = dateKey(turn.timestamp)
127127
if (!dailyMap[day]) { dailyMap[day] = { cost: 0, calls: 0 } }
128128
for (const call of turn.assistantCalls) {
129129
dailyMap[day].cost += call.costUSD
@@ -204,7 +204,7 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey:
204204
Object.entries(m).sort(([, a], [, b]) => b - a).map(([name, calls]) => ({ name, calls }))
205205

206206
const topSessions = projects
207-
.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 })))
207+
.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 })))
208208
.sort((a, b) => b.cost - a.cost)
209209
.slice(0, 5)
210210

@@ -545,7 +545,7 @@ program
545545
return
546546
}
547547

548-
const defaultName = `codeburn-${new Date().toISOString().slice(0, 10)}`
548+
const defaultName = `codeburn-${toDateString(new Date())}`
549549
const outputPath = opts.output ?? `${defaultName}.${opts.format}`
550550

551551
let savedPath: string

src/dashboard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { loadPricing } from './models.js'
99
import { getAllProviders } from './providers/index.js'
1010
import { scanAndDetect, type WasteFinding, type WasteAction, type OptimizeResult } from './optimize.js'
1111
import { estimateContextBudget, discoverProjectCwd, type ContextBudget } from './context-budget.js'
12+
import { dateKey } from './day-aggregator.js'
1213
import { join } from 'path'
1314

1415
type Period = 'today' | 'week' | '30days' | 'month' | 'all'
@@ -195,7 +196,7 @@ function DailyActivity({ projects, days = 14, pw, bw }: { projects: ProjectSumma
195196
for (const session of project.sessions) {
196197
for (const turn of session.turns) {
197198
if (!turn.timestamp) continue
198-
const day = turn.timestamp.slice(0, 10)
199+
const day = dateKey(turn.timestamp)
199200
dailyCosts[day] = (dailyCosts[day] ?? 0) + turn.assistantCalls.reduce((s, c) => s + c.costUSD, 0)
200201
dailyCalls[day] = (dailyCalls[day] ?? 0) + turn.assistantCalls.length
201202
}

src/day-aggregator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ function emptyEntry(date: string): DailyEntry {
2020
}
2121
}
2222

23-
function dateKey(iso: string): string {
24-
return iso.slice(0, 10)
23+
export function dateKey(iso: string): string {
24+
const d = new Date(iso)
25+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
2526
}
2627

2728
export function aggregateProjectsIntoDays(projects: ProjectSummary[]): DailyEntry[] {

src/export.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'path'
33

44
import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types.js'
55
import { getCurrency, convertCost } from './currency.js'
6+
import { dateKey } from './day-aggregator.js'
67

78
function escCsv(s: string): string {
89
const sanitized = /^[=+\-@]/.test(s) ? `'${s}` : s
@@ -48,7 +49,7 @@ function buildDailyRows(projects: ProjectSummary[], period: string): Row[] {
4849
for (const session of project.sessions) {
4950
for (const turn of session.turns) {
5051
if (!turn.timestamp) continue
51-
const day = turn.timestamp.slice(0, 10)
52+
const day = dateKey(turn.timestamp)
5253
if (!daily[day]) {
5354
daily[day] = { cost: 0, calls: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, sessions: new Set() }
5455
}

0 commit comments

Comments
 (0)