Skip to content

Add configurable time format support #1888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,13 @@ export namespace Config {
.optional(),
})
.optional(),
time_format: z
.enum(["detect", "12h", "24h"])
.optional()
.default("detect")
.describe(
"Time format preference: 'detect' auto-detects system preference (macOS only), '12h' forces 12-hour format, '24h' forces 24-hour format",
),
})
.optional(),
})
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/go/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ type Config struct {
Snapshot bool `json:"snapshot"`
// Theme name to use for the interface
Theme string `json:"theme"`
// Time format preference: 'detect' auto-detects system preference (macOS only), '12h' forces 12-hour format, '24h' forces 24-hour format
TimeFormat string `json:"time_format"`
// Custom username to display in conversations instead of system username
Username string `json:"username"`
JSON configJSON `json:"-"`
Expand Down Expand Up @@ -108,6 +110,7 @@ type configJSON struct {
SmallModel apijson.Field
Snapshot apijson.Field
Theme apijson.Field
TimeFormat apijson.Field
Username apijson.Field
raw string
ExtraFields map[string]apijson.Field
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/js/src/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,10 @@ export type Config = {
}>
}
}
/**
* Time format preference: 'detect' auto-detects system preference (macOS only), '12h' forces 12-hour format, '24h' forces 24-hour format
*/
time_format: "detect" | "12h" | "24h"
}

export type KeybindsConfig = {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/stainless/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ console.log("=== Generating Stainless SDK ===")
console.log(process.cwd())

await $`rm -rf go`
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
await $`bun run --conditions=development ../../opencode/src/index.ts generate > openapi.json`
await $`stl builds create --branch dev --pull --allow-empty --+target go`

await $`rm -rf ../go`
Expand Down
27 changes: 14 additions & 13 deletions packages/tui/internal/app/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,20 @@ type AgentModel struct {
}

type State struct {
Theme string `toml:"theme"`
ScrollSpeed *int `toml:"scroll_speed"`
AgentModel map[string]AgentModel `toml:"agent_model"`
Provider string `toml:"provider"`
Model string `toml:"model"`
Agent string `toml:"agent"`
RecentlyUsedModels []ModelUsage `toml:"recently_used_models"`
RecentlyUsedAgents []AgentUsage `toml:"recently_used_agents"`
MessagesRight bool `toml:"messages_right"`
SplitDiff bool `toml:"split_diff"`
MessageHistory []Prompt `toml:"message_history"`
ShowToolDetails *bool `toml:"show_tool_details"`
ShowThinkingBlocks *bool `toml:"show_thinking_blocks"`
Theme string `toml:"theme"`
ScrollSpeed *int `toml:"scroll_speed"`
AgentModel map[string]AgentModel `toml:"agent_model"`
Provider string `toml:"provider"`
Model string `toml:"model"`
Agent string `toml:"agent"`
RecentlyUsedModels []ModelUsage `toml:"recently_used_models"`
RecentlyUsedAgents []AgentUsage `toml:"recently_used_agents"`
MessagesRight bool `toml:"messages_right"`
SplitDiff bool `toml:"split_diff"`
MessageHistory []Prompt `toml:"message_history"`
ShowToolDetails *bool `toml:"show_tool_details"`
ShowThinkingBlocks *bool `toml:"show_thinking_blocks"`
DetectedTimeFormat24h *bool `toml:"detected_time_format_24h"`
}

func NewState() *State {
Expand Down
48 changes: 45 additions & 3 deletions packages/tui/internal/components/chat/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"maps"
"os/exec"
"runtime"
"slices"
"strings"
"time"
Expand All @@ -22,6 +24,41 @@ import (
"golang.org/x/text/language"
)

// shouldUse24HourFormat determines if 24-hour time format should be used
// based on config and lazy system detection
func shouldUse24HourFormat(config *opencode.Config, state *app.State) bool {
switch config.TimeFormat {
case "24h":
return true
case "12h":
return false
default:
// Lazy evaluation: detect and cache on first use
if state.DetectedTimeFormat24h == nil {
detected := detectSystemTimeFormat()
state.DetectedTimeFormat24h = &detected
}
return *state.DetectedTimeFormat24h
}
}

// detectSystemTimeFormat detects if the system prefers 24-hour format
func detectSystemTimeFormat() bool {
// Only try detection on macOS
if runtime.GOOS == "darwin" {
cmd := exec.Command("date", "+%X")
output, err := cmd.Output()
if err != nil {
return false
}

sysTime := strings.TrimSpace(string(output))
// If system time contains AM/PM, it's 12-hour format
return !strings.Contains(sysTime, "AM") && !strings.Contains(sysTime, "PM")
}
return false // fallback to 12h on other platforms
}

type blockRenderer struct {
textColor compat.AdaptiveColor
backgroundColor compat.AdaptiveColor
Expand Down Expand Up @@ -331,9 +368,14 @@ func renderText(
content = base.Width(width - 6).Render(wrappedText)
}

timestamp := ts.
Local().
Format("02 Jan 2006 03:04 PM")
var timeFormat string
if shouldUse24HourFormat(app.Config, app.State) {
timeFormat = "02 Jan 2006 15:04"
} else {
timeFormat = "02 Jan 2006 03:04 PM"
}

timestamp := ts.Local().Format(timeFormat)
if time.Now().Format("02 Jan 2006") == timestamp[:11] {
timestamp = timestamp[12:]
}
Expand Down