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}
Comment From: gabyhelp
Related Issues
- proposal: encoding/json: support struct tag for time.Format in JSON Marshaller/Unmarshaller #21990
- proposal: time: direct support for Unix millis and micros #18935 (closed)
- proposal: time: add UnixMilli and UnixMicro #44412 (closed)
- proposal: encoding/json/v2: use "72h3m0.5s" as the default representation for time.Duration #71631
- proposal: time: add constants for DatetimeMilli, DatetimeMicro, and DatetimeNano Formats #62090
- encoding/json: custom format for time.Duration #4712 (closed)
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: dsnet
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.
Comment From: dsnet
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.
Comment From: lmittmann
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 https://github.com/golang/go/issues/71664. This way one could change the behaviour of e.g. the unix
format.
Comment From: feeeei
I still believe the format:unix
family should return an int
/int64
type, not float
.
Relying on existing production usage of this behavior shouldn’t justify the design. Even if fractional seconds are preserved, the default precision remains contentious: some systems log 3 decimal places, others use 6, while Unix timestamps natively support 9 (nanoseconds).
Additionally, returning float
creates inconsistency with Go’s own time.Unix()
family of methods. Switching between float
and int
in everyday code would feel disjointed and harm developer ergonomics.
My suggestion:
Default to returning int64
for Unix timestamps, while providing an optional mechanism to control fractional precision.
@dsnet