11package duration
22
33import (
4- "encoding/json"
5- "fmt"
64 "net/url"
75 "strings"
86 "time"
7+
8+ "google.golang.org/protobuf/encoding/protojson"
9+ "google.golang.org/protobuf/types/known/durationpb"
910)
1011
1112// Duration is a wrapper for time.Duration to provide custom marshaling
@@ -20,78 +21,48 @@ import (
2021// customDur := durationpb.New(30 * time.Second)
2122// goDur := customDur.AsDuration() // Access the underlying time.Duration
2223type Duration struct {
23- time .Duration
24+ internal * durationpb .Duration
2425}
2526
2627// New creates a custom Duration from a standard time.Duration.
2728func New (d time.Duration ) * Duration {
28- return & Duration {Duration : d }
29+ return & Duration {internal : durationpb . New ( d ) }
2930}
3031
3132// AsDuration returns the underlying time.Duration value.
3233func (x * Duration ) AsDuration () time.Duration {
3334 if x == nil {
3435 return 0
3536 }
36- return x .Duration
37+ return x .internal . AsDuration ()
3738}
3839
39- // MarshalJSON implements the [json.Marshaler] interface by formatting the
40- // duration as a string according to Google Well Known Type .
40+ // MarshalJSON implements the [json.Marshaler] interface
41+ // by marshalling the duration as a protobuf Duration .
4142func (d Duration ) MarshalJSON () ([]byte , error ) {
42- return json .Marshal (d .toWireFormat ())
43- }
44-
45- // toWireFormat returns a string representation of Duration
46- // which follows the wire format from Google Well Known Type.
47- // a String that ends in s to indicate seconds and is preceded by
48- // the number of seconds, with nanoseconds expressed as fractional seconds.
49- //
50- // https://protobuf.dev/reference/protobuf/google.protobuf/#duration
51- func (d Duration ) toWireFormat () string {
52- // We do not use the standard time.Duration.String() and d.Duration.Seconds()
53- // method because they use float64 which loses precision.
54-
55- // Get the total nanoseconds as a precise integer.
56- sign := ""
57- if d .Duration < 0 {
58- sign = "-"
59- d .Duration = - d .Duration
60- }
61-
62- sec := d .Duration / time .Second
63- nsec := d .Duration % time .Second
64-
65- if nsec == 0 {
66- return fmt .Sprintf ("%s%ds" , sign , sec )
67- }
68-
69- frac := strings .TrimRight (fmt .Sprintf ("%09d" , nsec ), "0" )
70- return fmt .Sprintf ("%s%d.%ss" , sign , sec , frac )
43+ return protojson .Marshal (d .internal )
7144}
7245
7346// EncodeValues implements the [query.Encoder] interface by encoding the
7447// duration as a string, like "3.3s".
7548func (d Duration ) EncodeValues (key string , v * url.Values ) error {
76- v .Set (key , d .toWireFormat ())
49+ res , err := protojson .Marshal (d .internal )
50+ if err != nil {
51+ return err
52+ }
53+ // remove the quotes from the string
54+ queryValue := strings .Trim (string (res ), "\" " )
55+ v .Set (key , queryValue )
7756 return nil
7857}
7958
8059// UnmarshalJSON implements the [json.Unmarshaler] interface. It can parse a
81- // duration from the Google well-known type format (e.g., "3.123s") .
60+ // duration from the protobuf Duration .
8261func (d * Duration ) UnmarshalJSON (b []byte ) error {
83- if d == nil {
84- return fmt .Errorf ("json.Unmarshal on nil pointer" )
85- }
86- // Remove the quotes from the string.
87- var s string
88- if err := json .Unmarshal (b , & s ); err != nil {
89- return err
90- }
91- dur , err := time .ParseDuration (s )
92- if err != nil {
62+ var pb durationpb.Duration
63+ if err := protojson .Unmarshal (b , & pb ); err != nil {
9364 return err
9465 }
95- * d = * New (dur )
66+ * d = * New (pb . AsDuration () )
9667 return nil
9768}
0 commit comments