Skip to content

proposal: encoding/json/v2: encode time with format:unix as integer instead of float #73486

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
lmittmann opened this issue Apr 24, 2025 · 4 comments
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool Proposal
Milestone

Comments

@lmittmann
Copy link

lmittmann commented Apr 24, 2025

Proposal Details

The new package json/v2 has format options unix, unixmilli, unixmicro, and unixnano for time.Time, that encode the time as float of seconds, milliseconds, microseconds, and nanoseconds since epoch. The encoded float has nanosecond precision for each unit.

I propose that we change the format options unix, unixmilli, unixmicro, and unixnano to emit integer timestamps, rather than floats.

Motivation

Many APIs that use epoch timestamps use integers epoch timestamps. High precision floats are not commonly used due to rounding issues in clients.

The time package has the time.Time-methods Unix, UnixMilli, UnixMicro, and UnixNano that return integer values. Equally named json/v2 format options should have the same logic to avoid confusion.

time.Time json/v2 format
Unix() int64 format:unix
UnixMilli() int64 format:unixmilli
UnixMicro() int64 format:unixmicro
UnixNano() int64 format:unixnano

Example of Current Behavior (Playground)

type times struct {
	Time  time.Time `json:"time,format:unix"`
	Time2 time.Time `json:"time2,format:unixmilli"`
	Time3 time.Time `json:"time3,format:unixmicro"`
	Time4 time.Time `json:"time4,format:unixnano"`
}

enc, _ := json.Marshal(times{
	Time:  time.Unix(123, 456),
	Time2: time.Unix(123, 456),
	Time3: time.Unix(123, 456),
	Time4: time.Unix(123, 456),
})
fmt.Print(string(enc))
// Output:
// {"time":123.000000456,"time2":123000.000456,"time3":123000000.456,"time4":123000000456}
@gabyhelp gabyhelp added the LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool label Apr 24, 2025
@dsnet
Copy link
Member

dsnet commented Apr 24, 2025

Here are the pros and cons of this.

Benefits

  • unix, unixmilli, unixmicro, unixnano matches the precision of the existing Unix, UnixMilli, UnixMicro, and UnixNano methods.

Detriments

  • You cannot express a Unix timestamp in seconds with sub-second precision. A number of JSON APIs actually accept Unix timestamps with sub-second precision.
  • Timestamps do not roundtrip through JSON marshal and unmarshal because you lose precision. Though this is already true for other formats such as RFC3339 (which is only second resolution and RFC3339Nano is with sub-second resolution).

Note that the behavior today (having fractional precision by default) can be turned into a non-fractional integer by always calling time.Time.Round on the Go struct field prior to marshaling. However, if we remove the encoding of fractional component, there is no way for someone to maintain precision when they need it. I feel strongly that we still need a way to preserve precise timestamps for those who need (and we are already depending on precise timestamps of this in production).

If we change this, we would also need something like unixprecise, unixmilliprecise, unixmicroprecise, unixnano, or something.

@dsnet
Copy link
Member

dsnet commented Apr 24, 2025

Perhaps this proposal is too narrow in scope. There are often times where you want to adjust a time.Time before serializing. Another reasonable alteration is change the location.

One could consider the ability to specify something like format:unix,round:1s (format as Unix seconds, but round to seconds, which would accomplish what this proposal desires) or format:RFC3339Nano,location:UTC (format as RFC 3339, but always encode as UTC to avoid leaking our general location). This would provide greater flexibility beyond what this particular proposal is suggesting.

@lmittmann
Copy link
Author

One could consider the ability to specify something like format:unix,round:1s (format as Unix seconds, but round to seconds, which would accomplish what this proposal desires) or format:RFC3339Nano,location:UTC (format as RFC 3339, but always encode as UTC to avoid leaking our general location). This would provide greater flexibility beyond what this particular proposal is suggesting.

This seems like a lot of complexity. A potentially more straight forward solution might be to support customizable formatting behavior via #71664. This way one could change the behaviour of e.g. the unix format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool Proposal
Projects
None yet
Development

No branches or pull requests

4 participants