Skip to content

fix(statusline): respect timezone for today cost#1121

Merged
ryoppippi merged 1 commit into
mainfrom
codex/fix-statusline-timezone
May 22, 2026
Merged

fix(statusline): respect timezone for today cost#1121
ryoppippi merged 1 commit into
mainfrom
codex/fix-statusline-timezone

Conversation

@ryoppippi
Copy link
Copy Markdown
Owner

@ryoppippi ryoppippi commented May 22, 2026

Summary

  • Add `-z, --timezone ` support to `ccusage statusline`.
  • Compute the statusline `today` filter from the selected timezone and pass that timezone into entry loading so it matches daily date grouping.
  • Update config schema/help snapshots and generated config schema output.

Fixes #1114.

Testing

  • `nix develop --command pnpm run format`
  • `nix develop --command pnpm typecheck`
  • `nix develop --command pnpm run test`
  • pre-push hook: cargo test, clippy, typos, oxfmt, gitleaks

Summary by cubic

Statusline “today” cost now respects the selected timezone, matching daily date grouping instead of forcing UTC. Adds a -z, --timezone option and config support for ccusage statusline. Fixes #1114.

  • Bug Fixes
    • Add -z, --timezone <IANA> and timezone in config schema for statusline.
    • Compute since/until from the chosen timezone and pass it to entry loading to align “today” totals with the local day.
    • Update CLI help, generated config schema, and snapshots/tests.

Written for commit 14406a1. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Added timezone support for statusline configuration. Users can now specify an IANA timezone via configuration file or the new -z/--timezone CLI option to customize date grouping behavior.

Review Change Stack

Add a statusline timezone option so the compact today total uses the same date grouping semantics as daily reports instead of forcing UTC.

The statusline today loader now builds its since/until filter from the requested IANA timezone and passes that timezone through to entry loading, keeping the filtered entry dates aligned with the selected local day.

Update CLI help, config schema coverage, and snapshots for the new option. Fixes #1114.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide 14406a1 Commit Preview URL

Branch Preview URL
May 22 2026, 10:23 AM

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 891b55ab-c4b4-4128-b841-470748e45ca8

📥 Commits

Reviewing files that changed from the base of the PR and between 6a49697 and 14406a1.

⛔ Files ignored due to path filters (2)
  • rust/crates/ccusage/src/snapshots/ccusage__cli__tests__statusline_help.snap is excluded by !**/*.snap
  • rust/crates/ccusage/src/snapshots/ccusage__config_schema__tests__snapshots_schema_agent_specific_option_edges.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • apps/ccusage/config-schema.json
  • rust/crates/ccusage/src/cli-help.json
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/commands/mod.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/date_utils.rs
💤 Files with no reviewable changes (1)
  • rust/crates/ccusage/src/date_utils.rs

📝 Walkthrough

Walkthrough

This PR adds timezone support to the statusline command. Previously, the "today" date filter was computed using UTC unconditionally. Now, users can pass -z, --timezone <IANA> to compute the filter in their local timezone, matching the behavior of other commands like daily and monthly.

Changes

Statusline Timezone Support

Layer / File(s) Summary
Timezone Option Declaration
apps/ccusage/config-schema.json, rust/crates/ccusage/src/cli-help.json, rust/crates/ccusage/src/cli.rs, rust/crates/ccusage/src/config_schema.rs
The timezone string option is declared in the JSON schema (both command and defaults), CLI help text, and Rust struct types (StatuslineArgs and StatuslineSpecificOptions), along with test expectations for schema keys.
CLI Parsing and Config Application
rust/crates/ccusage/src/cli.rs, rust/crates/ccusage/src/config_schema.rs, rust/crates/ccusage/src/config.rs
The CLI parser recognizes -z/--timezone and stores the value in StatuslineArgs. The config schema extracts the timezone key from configuration files, and the application layer copies it into the parsed arguments. Tests validate both CLI and config workflows.
Timezone-Aware Date Computation
rust/crates/ccusage/src/commands/mod.rs, rust/crates/ccusage/src/date_utils.rs
A new statusline_today_shared helper computes the "today" date bounds using format_date with the provided timezone instead of UTC. The call in render_statusline is updated to use this helper. The obsolete format_compact_utc_date function is removed. Tests verify the helper produces correct date strings for non-UTC timezones.

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • ryoppippi/ccusage#1058: Both PRs directly modify rust/crates/ccusage/src/config_schema.rs around the typed statusline config schema; this PR adds the timezone field to StatuslineSpecificOptions as part of the fix for the timezone issue.

🐰 Through schema and CLI, a timezone path unfolds,
Where "today" at last respects the local clock's story,
No more UTC's grip—let each place see its glory!
statusline now speaks in your home timezone's tongue.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding timezone support to statusline for correct 'today' cost calculation.
Linked Issues check ✅ Passed The PR successfully implements all requirements from #1114: adds -z/--timezone option to StatuslineArgs, computes 'today' relative to the selected timezone, and updates relevant schemas and tests.
Out of Scope Changes check ✅ Passed All changes directly support timezone handling for statusline 'today' cost: schema updates, CLI option additions, argument parsing, config application, and date computation logic—no unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-statusline-timezone

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai please review this PR.

This fixes #1114 by adding `--timezone` support to `ccusage statusline` and aligning the statusline `today` cost with timezone-aware daily grouping.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details and diff
gh pr view 1121 --repo ryoppippi/ccusage
echo "---DIFF---"
gh pr diff 1121 --repo ryoppippi/ccusage

Length of output: 15542


🏁 Script executed:

#!/bin/bash
# Check how other args handle CLI vs config precedence in apply_config_to_statusline_args
rg -n "apply_config_to_statusline_args" --type rust -A 80 rust/crates/ccusage/src/config.rs | head -120

echo "=== format_date signature ==="
rg -n "pub.*fn format_date" --type rust

echo "=== format_date body ==="
ast-grep --pattern 'pub(crate) fn format_date($$$) { $$$ }'

echo "=== How timezone is parsed in other commands (daily, weekly, etc.) ==="
rg -n '"-z"\s*\|\s*"--timezone"' --type rust

echo "=== CLI arg timezone for daily/weekly/etc ==="
rg -n '"--timezone"' --type rust -B2 -A2

echo "=== Check config precedence pattern for other args ==="
rg -n "if let Some.*options\." --type rust rust/crates/ccusage/src/config.rs | head -50

Length of output: 9828


🏁 Script executed:

#!/bin/bash
echo "=== format_date body ==="
rg -n "fn format_date" --type rust -A 20 rust/crates/ccusage/src/date_utils.rs

echo "=== Where apply_config_to_statusline_args is called ==="
rg -n "apply_config_to_statusline_args" --type rust -B5 -A5

echo "=== run_statusline or statusline entry point in commands ==="
rg -n "run_statusline\|StatuslineArgs" --type rust rust/crates/ccusage/src/commands/mod.rs | head -20

echo "=== main.rs statusline flow ==="
rg -n "statusline\|apply_config" --type rust rust/crates/ccusage/src/main.rs | head -40

echo "=== Check if config is applied before or after args are built ==="
ast-grep --pattern 'fn run_statusline($$$) { $$$ }'

Length of output: 5213


🏁 Script executed:

#!/bin/bash
echo "=== parse_tz function ==="
rg -n "fn parse_tz" --type rust -A 15

echo "=== How apply_config_to_shared handles timezone (for precedence comparison) ==="
rg -n "fn apply_config_to_shared" --type rust -A 30 rust/crates/ccusage/src/config.rs

echo "=== statusline_today_shared - check what fields from shared are propagated ==="
rg -n "statusline_today_shared" --type rust -A 20 rust/crates/ccusage/src/commands/mod.rs

Length of output: 5559


@ryoppippi: The implementation looks correct and well-structured. Here's a detailed walkthrough:


✅ Config precedence is correct

In cli.rs (line 328), apply_config_to_statusline_args is invoked before the CLI flag parse loop, so CLI-provided --timezone correctly overwrites any config-file value — consistent with every other option in the codebase.


✅ Compact-date conversion is sound

format_date(now, args.timezone.as_deref()) returns YYYY-MM-DD via format_date_parts, so .replace('-', "") cleanly produces the YYYYMMDD format expected by the since/until filter. The subsequent entry.date.replace('-', "") == today_shared.since guard on line 420 continues to work correctly.


-z short flag is consistent

The new -z | --timezone in the statusline parser (line 362) mirrors the shared-args parser (line 735), keeping the CLI surface uniform across subcommands.


ℹ️ Silent fallback for invalid IANA timezone (pre-existing, not introduced here)

parse_tz calls JiffTimeZone::get(value).ok() — an unrecognised timezone string silently falls back to JiffTimeZone::system() rather than returning an error. This is the same behaviour for every command in the codebase, so it's out of scope for this PR, but worth tracking as a follow-up if user-facing error reporting is ever desired.


ℹ️ no_offline not forwarded in statusline_today_shared (pre-existing)

Only offline and timezone are forwarded from the parent shared args; no_offline, no_cache, etc. are not. This matches the original code that was replaced and is therefore not a regression.


✅ Test coverage

builds_statusline_today_filter_from_timezone pins a concrete millisecond timestamp (1_779_380_820_000) and asserts the Tokyo-aware date resolves to 20260522 — a solid, unambiguous regression guard.


Overall the fix is clean and complete. All snapshot, schema, and config struct changes are consistent. Happy to approve.

[approve]

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 22, 2026

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ryoppippi/ccusage@1121

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-darwin-arm64@1121

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-darwin-x64@1121

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-linux-arm64@1121

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-linux-x64@1121

@ccusage/ccusage-win32-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-win32-arm64@1121

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-win32-x64@1121

commit: 14406a1

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 14406a1e686d
Base SHA: 6a496975fc5e

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 6a496975fc5e 758.6ms 474.3ms 32.9ms 3
PR pkg.pr.new 14406a1e686d 459.7ms 476.6ms 32.8ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 6a496975fc5e; PR package: 14406a1e686d. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 499.6ms 494.1ms 1.01x 2.02 GiB/s 2.04 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 478.5ms 478.7ms 1.00x 2.10 GiB/s 2.10 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 492.6ms 2.04 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 461.1ms 2.18 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 474.8ms 2.12 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 448.8ms 2.24 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.9ms 4.3ms 6.87x 0.05 MiB/s 0.36 MiB/s
claude session --offline --json 0.00 MiB 29.9ms 4.5ms 6.69x 0.05 MiB/s 0.35 MiB/s
codex daily --offline --json 0.00 MiB 30.1ms 4.2ms 7.17x 0.03 MiB/s 0.20 MiB/s
codex session --offline --json 0.00 MiB 30.5ms 4.1ms 7.38x 0.03 MiB/s 0.21 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 494.5ms 470.2ms 1.05x 2.04 GiB/s 2.14 GiB/s
codex --offline --json 1.01 GiB 471.0ms 456.8ms 1.03x 2.14 GiB/s 2.20 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.23 KiB 14.25 KiB +0.02 KiB 1.00x
installed native package binary 3288.24 KiB 3288.24 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 14406a1e686d
Base SHA: 6a496975fc5e

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 6a496975fc5e 740.8ms 512.8ms 35.8ms 3
PR pkg.pr.new 14406a1e686d 695.3ms 539.6ms 35.8ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 6a496975fc5e; PR package: 14406a1e686d. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 521.0ms 525.5ms 0.99x 1.93 GiB/s 1.92 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 493.2ms 501.0ms 0.98x 2.04 GiB/s 2.01 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 511.0ms 1.97 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 472.6ms 2.13 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 480.7ms 2.09 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 456.6ms 2.20 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 33.4ms 33.4ms 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 33.6ms 34.1ms 0.98x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 33.6ms 33.5ms 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 34.2ms 33.3ms 1.03x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 537.3ms 539.5ms 1.00x 1.87 GiB/s 1.87 GiB/s
codex --offline --json 1.01 GiB 495.4ms 489.0ms 1.01x 2.03 GiB/s 2.06 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.23 KiB 14.25 KiB +0.02 KiB 1.00x
installed native package binary 3288.24 KiB 3288.24 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi ryoppippi merged commit 2908c4c into main May 22, 2026
41 checks passed
@ryoppippi ryoppippi deleted the codex/fix-statusline-timezone branch May 22, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

statusline uses UTC fixed for today, no --timezone option

1 participant