timefmt

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

Documentation

Overview

Package timefmt is the single source of truth for how bones renders time values across every surface. It exists because the same instant used to show up in four shapes — UTC RFC3339 in `tasks show`, UTC RFC3339 in `hub.log`, local-with-no-zone in `up.log`, local HH:MM:SS-with-no-zone in `bones tasks watch` and `bones status` — and operators correlating events across them had to translate timezones in their head. The policy is the fix.

Policy

Two surfaces, two helpers, hard split. No knob. No third helper for callsites that don't fit; the answer there is to make the callsite fit one of the two.

  • Logged — UTC RFC3339. Use for every persisted, structured, or machine-readable timestamp: log files (up.log, hub.log, the event log), --json payload fields, structured stream subjects. A future reader on a different machine or in a different zone reads the same instant.

  • Display — local time with explicit zone abbreviation, e.g. "15:04:05 PST" (or "15:04:05 UTC" when the local zone is UTC). Use only for operator-facing live displays where the operator's wall clock is the reference frame: `bones status` "as of" header, `bones tasks watch` bracket prefix. Never persist a Display string — its zone abbreviation is meaningless to a reader on a different system.

  • LoggedShort — UTC HH:MM:SS, "15:04:05Z". Defined for completeness but not used by any callsite as of #324. Adding a new use requires a reviewer's nod. Default to Logged for any new structured timestamp; default to Display for any new live operator surface.

Enforcement

internal/timefmt/enforce_test.go walks the bones source tree and fails CI if any non-test file outside this package calls time.Time.Format directly. Any new timestamp surface must route through Logged or Display.

If your callsite genuinely doesn't fit either helper, that's a signal to discuss in review — not a signal to add a third helper or bypass the enforcement.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Display

func Display(t time.Time) string

Display returns t formatted in the local zone with an explicit zone abbreviation, e.g. "15:04:05 PST" (or "15:04:05 UTC" when the local zone is UTC).

Use for operator-facing live-display surfaces only: `bones status` "as of" header, `bones tasks watch` bracket prefix, and any future terminal-rendered live display. Never use for logs, JSON payloads, or anything that gets persisted — the zone abbreviation has no meaning to a reader on a different system.

The local zone is whatever Go's time.Local resolves to, which honors the operator's TZ environment variable. An operator who wants UTC across all surfaces should set TZ=UTC at the system level rather than asking bones for a knob.

func Logged

func Logged(t time.Time) string

Logged returns t formatted as RFC3339 in UTC.

Use for every log file entry, every JSON payload field, every persisted timestamp, and every structured stream subject. The output is timezone-independent: a future reader on any system reads the same instant.

func LoggedShort

func LoggedShort(t time.Time) string

LoggedShort returns t formatted as HH:MM:SSZ in UTC.

Defined but not used by any callsite as of #324. Reserved for the rare future case where a Logged-style timestamp would be redundant within an already-dated surface (for example, a per-day log file whose filename carries the date). Confirm with a reviewer before adding new uses; default to Logged otherwise.

Types

type LoggedTime

type LoggedTime struct {
	time.Time
}

LoggedTime is a time.Time newtype whose JSON marshal path goes through Logged — UTC RFC3339, no nanoseconds, "Z" suffix. Use it for every time.Time field in a JSON payload struct or in any type that crosses a persisted/structured boundary (event log envelopes, KV records, --json output).

Default time.Time JSON marshaling emits RFC3339Nano in the local zone with an offset suffix (e.g. "2026-01-15T12:30:45.123-08:00"), which violates the Logged policy. The AST-walk enforcement test flags bare time.Time json-tagged fields outside an allowlist; new payload struct authors should reach for LoggedTime by default.

Round-trip: UnmarshalJSON accepts any RFC3339(Nano) input — the helper is strict on output, lenient on input — so existing persisted records with the old shape decode without breakage.

func NewLoggedTime

func NewLoggedTime(t time.Time) LoggedTime

NewLoggedTime wraps t. Convenience for struct-literal use.

func (LoggedTime) JSONSchemaAlias

func (LoggedTime) JSONSchemaAlias() any

JSONSchemaAlias returns time.Time so JSON-schema reflectors (specifically invopop/jsonschema in cmd/bones-schemagen) treat LoggedTime fields as the same date-time string they treated time.Time fields as before #324. Without this, the reflector sees a struct with no exported fields and emits an empty object schema, breaking every payload contract.

Returning time.Time itself (not a *Schema) keeps internal/timefmt free of a jsonschema-library import — the reflector reads the type by reflection.

func (LoggedTime) MarshalJSON

func (l LoggedTime) MarshalJSON() ([]byte, error)

MarshalJSON emits Logged(t) wrapped in JSON quotes. Zero values emit "0001-01-01T00:00:00Z" — matching default time.Time marshaling — so a value-typed LoggedTime field continues to satisfy a required date-time string in the JSON Schema.

For optional timestamp fields, declare them as *LoggedTime with omitempty: the standard json package drops nil pointers cleanly, so a missing timestamp never reaches MarshalJSON at all.

func (*LoggedTime) UnmarshalJSON

func (l *LoggedTime) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts an RFC3339 or RFC3339Nano string (the latter for backwards compatibility with persisted records pre-#324) and a literal JSON null (lenient — decodes to the zero LoggedTime so a partial record from a future schema can be replayed without abort). Any other shape is rejected.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL