mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-04-28 06:59:37 +00:00
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.
This commit is contained in:
parent
888030fce3
commit
72ccf34a5a
4 changed files with 14 additions and 11 deletions
14
src/cli.ts
14
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[] {
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue