|
4 | 4 | "encoding/json" |
5 | 5 | "fmt" |
6 | 6 | "net/url" |
| 7 | + "strings" |
7 | 8 | "time" |
8 | 9 | ) |
9 | 10 |
|
@@ -35,54 +36,54 @@ func (x *Duration) AsDuration() time.Duration { |
35 | 36 | return x.Duration |
36 | 37 | } |
37 | 38 |
|
38 | | -// MarshalJSON implements the json.Marshaler interface by formatting the |
39 | | -// duration as a string according to Google Well Known Type |
| 39 | +// MarshalJSON implements the [json.Marshaler] interface by formatting the |
| 40 | +// duration as a string according to Google Well Known Type. |
40 | 41 | func (d Duration) MarshalJSON() ([]byte, error) { |
41 | | - return []byte(fmt.Sprintf(`"%s"`, d.ToWireFormat())), nil |
| 42 | + return json.Marshal(d.toWireFormat()) |
42 | 43 | } |
43 | 44 |
|
44 | | -// String returns a string representation of Duration |
45 | | -// which follows the wire format from Google Well Known Type: |
| 45 | +// toWireFormat returns a string representation of Duration |
| 46 | +// which follows the wire format from Google Well Known Type. |
46 | 47 | // a String that ends in s to indicate seconds and is preceded by |
47 | 48 | // the number of seconds, with nanoseconds expressed as fractional seconds. |
| 49 | +// |
48 | 50 | // https://protobuf.dev/reference/protobuf/google.protobuf/#duration |
49 | | -func (d Duration) ToWireFormat() string { |
| 51 | +func (d Duration) toWireFormat() string { |
50 | 52 | // We do not use the standard time.Duration.String() and d.Duration.Seconds() |
51 | 53 | // method because they use float64 which loses precision. |
52 | 54 |
|
53 | 55 | // Get the total nanoseconds as a precise integer. |
54 | | - nanos := d.Duration.Nanoseconds() |
| 56 | + sign := "" |
| 57 | + if d.Duration < 0 { |
| 58 | + sign = "-" |
| 59 | + d.Duration = -d.Duration |
| 60 | + } |
55 | 61 |
|
56 | | - // Calculate seconds and fractional nanoseconds. |
57 | | - secs := nanos / 1_000_000_000 |
58 | | - fracNanos := nanos % 1_000_000_000 |
| 62 | + sec := d.Duration / time.Second |
| 63 | + nsec := d.Duration % time.Second |
59 | 64 |
|
60 | | - // Format the string, ensuring the fractional part is zero-padded to 9 digits. |
61 | | - // This correctly handles both positive and negative durations. |
62 | | - if nanos < 0 { |
63 | | - // For negative values, both parts should be negative. |
64 | | - secs = -(-nanos / 1_000_000_000) |
65 | | - fracNanos = -(-nanos % 1_000_000_000) |
66 | | - return fmt.Sprintf(`%d.%09ds`, secs, -fracNanos) |
| 65 | + if nsec == 0 { |
| 66 | + return fmt.Sprintf("%s%ds", sign, sec) |
67 | 67 | } |
68 | 68 |
|
69 | | - return fmt.Sprintf(`%d.%09ds`, secs, fracNanos) |
| 69 | + frac := strings.TrimRight(fmt.Sprintf("%09d", nsec), "0") |
| 70 | + return fmt.Sprintf("%s%d.%ss", sign, sec, frac) |
70 | 71 | } |
71 | 72 |
|
72 | | -// EncodeValues implements the query.Encoder interface by formatting the |
| 73 | +// EncodeValues implements the [query.Encoder] interface by encoding the |
73 | 74 | // duration as a string, like "3.3s". |
74 | 75 | func (d Duration) EncodeValues(key string, v *url.Values) error { |
75 | | - v.Set(key, d.ToWireFormat()) |
| 76 | + v.Set(key, d.toWireFormat()) |
76 | 77 | return nil |
77 | 78 | } |
78 | 79 |
|
79 | | -// UnmarshalJSON implements the json.Unmarshaler interface. It can parse a |
| 80 | +// UnmarshalJSON implements the [json.Unmarshaler] interface. It can parse a |
80 | 81 | // duration from the Google well-known type format (e.g., "3.123s"). |
81 | 82 | func (d *Duration) UnmarshalJSON(b []byte) error { |
82 | 83 | if d == nil { |
83 | 84 | return fmt.Errorf("json.Unmarshal on nil pointer") |
84 | 85 | } |
85 | | - // Remove the quotes from the string |
| 86 | + // Remove the quotes from the string. |
86 | 87 | var s string |
87 | 88 | if err := json.Unmarshal(b, &s); err != nil { |
88 | 89 | return err |
|
0 commit comments