feat: render API timestamps as RFC3339 via self-describing types#20
Merged
Conversation
Add `Timestamp` (Unix seconds) and `TimestampMilli` (Unix milliseconds) types whose MarshalJSON emits RFC3339 in the local timezone, so structured output is human- and LLM-readable instead of opaque integers. The zero value marshals to 0 (an unset sentinel, never a 1970 date) and is dropped by `omitempty`; UnmarshalJSON accepts a numeric epoch or an RFC3339 string. TimestampMilli uses RFC3339Nano so sub-second precision survives a round-trip. Re-type every absolute-instant field in RESPONSE structs to these (59 seconds, 4 millis). Durations, cyclic-window offsets, counts, ids, and all request-input fields stay int64 — the unit/instant knowledge now lives on the field type, so consumers (CLI, MCP) get readable timestamps with no downstream guessing. Type the two previously-`any` create methods: CreateIncident -> *CreateIncidentOutput, CreateStatusIncident -> *CreateStatusIncidentOutput, retiring the last untyped return in the SDK. Add CLAUDE.md (+ AGENTS.md symlink) documenting the conventions. BREAKING (pre-1.0): response time fields change from int64 to Timestamp/TimestampMilli; the two create methods now return typed structs.
toon-go renders named scalar types via fmt.Stringer (its normalize() has no case for reflect.Int64 on a named type, so a bare Timestamp errors with "unsupported value of type"). Add String() to both types returning the same RFC3339 representation MarshalJSON uses, so the CLI/MCP TOON output path — a primary LLM format — renders readable timestamps instead of erroring. MarshalJSON now reuses String() for the non-zero case (JSON still goes through MarshalJSON, never Stringer, so JSON output is unchanged).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Flashduty API responses encode time as Unix integers — opaque to an LLM (and a human) reading CLI / MCP output. This puts the unit/instant knowledge on the field type so structured output renders RFC3339 with zero downstream guessing.
Timestamp(Unix seconds) andTimestampMilli(Unix milliseconds) types.MarshalJSON→ RFC3339 in the local timezone; zero →0(unset sentinel, never a 1970 date;omitemptystill drops it);UnmarshalJSONaccepts epoch or RFC3339 (symmetric round-trip).TimestampMilliuses RFC3339Nano so sub-second precision survives.AuditLogRecord.CreatedAt,RawTimelineItem.CreatedAt/UpdatedAt,TimelineEvent.Timestamp).int64(NOT instants): durations / cyclic-window offsets / counts —ScheduleLayer.{RotationDuration,RotationValue,HandoffTime,RestrictStart,RestrictEnd},ScheduleNotify.AdvanceInTime— and all request-input fields (the LLM never reads request bodies).anycreate methods →*CreateIncidentOutput/*CreateStatusIncidentOutput, retiring the last untyped return.CLAUDE.md(+AGENTS.mdsymlink) with the conventions.Response time fields change from
int64→Timestamp/TimestampMilli; the two create methods now return typed structs instead ofany. Consumers do.Time()/.Unix()/int64(...). The two first-party consumers (CLI, MCP) are updated in follow-up PRs that pin this branch.Reviewer attention
AccountInfo.CreatedAt,MemberInfo.CreatedAt,StatusChange.CreatedAt/UpdatedAt,ChangeTimeline.At): the OpenAPI lacks these read schemas, but they arecreated_at/updated_at/atand the existing CLI already renders them as seconds — corroborating. Flagging in case you know otherwise.created_atclassified seconds per docs (创建时间戳(秒)); the old CLI helper hedged with a magnitude guess. Confirm against a live response if convenient.Classification source
Every ambiguous schedule field resolved against
flashduty-docson-call OpenAPI (zh, source of truth): migrate-bucket fields sayUnix 秒; kept fields say偏移/周期/提前通知时间(no "Unix").Tests / gate
go build ./... && go vet ./... && go test ./... && gofmt -l .— all green. New tests cover both types (marshal/unmarshal/zero/omitempty/round-trip incl. fractional-ms), a migration golden (sec + ms render RFC3339; trap fields stay numeric), and both create-method decodes.