@@ -23,6 +23,7 @@ import (
2323 "reflect"
2424 "sort"
2525 "strconv"
26+ "time"
2627)
2728
2829// Some constants in the form of bytes to avoid string overhead. This mirrors
@@ -98,47 +99,100 @@ func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool)
9899 v = unsafeReflectValue (v )
99100 }
100101
102+ defer catchPanic (w , v )
103+ handled , continued := handleErrorOrStringer (cs , w , v )
104+ if handled {
105+ // short-circuit: if we can handle directly without trying to convert to a pointer receiver, we're done.
106+ // This allows avoiding to call unsafeReflectValue() or retrieving the value's address when not necessary.
107+ return true
108+ }
109+ if continued {
110+ // printed, and wants to return now to continue digging
111+ return false
112+ }
113+
101114 // Choose whether or not to do error and Stringer interface lookups against
102115 // the base type or a pointer to the base type depending on settings.
116+ //
103117 // Technically calling one of these methods with a pointer receiver can
104- // mutate the value, however, types which choose to satisify an error or
118+ // mutate the value, however, types which choose to satisfy an error or
105119 // Stringer interface with a pointer receiver should not be mutating their
106120 // state inside these interface methods.
107121 if ! cs .DisablePointerMethods && ! UnsafeDisabled && ! v .CanAddr () {
122+ // since this is unsafe, there are a few edge cases where it doesn't work well
108123 v = unsafeReflectValue (v )
109124 }
125+
110126 if v .CanAddr () {
111127 v = v .Addr ()
112128 }
113129
130+ handled , _ = handleErrorOrStringer (cs , w , v )
131+
132+ return handled
133+ }
134+
135+ // handleErrorOrString is only called when v.CanInterface().
136+ //
137+ // NOTE: we should prove that unsafReflectValue doesn't alter this property.
138+ func handleErrorOrStringer (cs * ConfigState , w io.Writer , v reflect.Value ) (handled , continued bool ) {
114139 // Is it an error or Stringer?
115140 switch iface := v .Interface ().(type ) {
116141 case error :
117- defer catchPanic (w , v )
118142 if cs .ContinueOnMethod {
119143 w .Write (openParenBytes )
120144 w .Write ([]byte (iface .Error ()))
121145 w .Write (closeParenBytes )
122146 w .Write (spaceBytes )
123- return false
147+ return false , true
124148 }
125149
126150 w .Write ([]byte (iface .Error ()))
127- return true
151+ return true , false
128152
129153 case fmt.Stringer :
130- defer catchPanic (w , v )
131154 if cs .ContinueOnMethod {
132155 w .Write (openParenBytes )
133156 w .Write ([]byte (iface .String ()))
134157 w .Write (closeParenBytes )
135158 w .Write (spaceBytes )
136- return false
159+ return false , true
137160 }
161+
138162 w .Write ([]byte (iface .String ()))
139- return true
163+ return true , false
164+
165+ default :
166+ // is it convertible to time.Time (or *time.Time)?
167+ converted , ok := isConvertibleToTime (v )
168+ if ! ok {
169+ // can't handle this value
170+ return false , false
171+ }
172+
173+ // rendering time.Time
174+ if ! converted .CanInterface () {
175+ return false , false // safeguard: should never be the case
176+ }
177+
178+ timeIface := converted .Interface ()
179+ stringer , ok := timeIface .(fmt.Stringer )
180+ if ! ok {
181+ return false , false // safeguard: should never be the case
182+ }
183+
184+ if cs .ContinueOnMethod {
185+ _ , _ = w .Write (openParenBytes )
186+ _ , _ = w .Write ([]byte (stringer .String ()))
187+ _ , _ = w .Write (closeParenBytes )
188+ _ , _ = w .Write (spaceBytes )
189+ return false , true
190+ }
191+
192+ _ , _ = w .Write ([]byte (stringer .String ()))
193+
194+ return true , false
140195 }
141- return false
142196}
143197
144198// printBool outputs a boolean value as true or false to Writer w.
@@ -226,12 +280,21 @@ type valuesSorter struct {
226280// newValuesSorter initializes a valuesSorter instance, which holds a set of
227281// surrogate keys on which the data should be sorted. It uses flags in
228282// ConfigState to decide if and how to populate those surrogate keys.
283+ //
284+ // Values that are convertible to a time.Time are compared using [time.Time.Compare()].
285+ // This is safer than sorting their string representation, because the [time.Location]
286+ // may differ.
287+ //
288+ // NOTE: if all values are not of the same underlying type,
289+ // comparison will resort to the default reflect.Value.String() representation.
229290func newValuesSorter (values []reflect.Value , cs * ConfigState ) sort.Interface {
230291 vs := & valuesSorter {values : values , cs : cs }
231- if canSortSimply (vs .values [0 ].Kind ()) {
292+ v0 := vs .values [0 ]
293+ if canSortSimply (v0 .Kind ()) || isTime (v0 ) {
232294 return vs
233295 }
234- if ! cs .DisableMethods {
296+
297+ if ! vs .cs .DisableMethods {
235298 vs .strings = make ([]string , len (values ))
236299 for i := range vs .values {
237300 b := bytes.Buffer {}
@@ -242,12 +305,14 @@ func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
242305 vs .strings [i ] = b .String ()
243306 }
244307 }
308+
245309 if vs .strings == nil && cs .SpewKeys {
246310 vs .strings = make ([]string , len (values ))
247311 for i := range vs .values {
248312 vs .strings [i ] = Sprintf ("%#v" , vs .values [i ].Interface ())
249313 }
250314 }
315+
251316 return vs
252317}
253318
@@ -291,6 +356,15 @@ func (s *valuesSorter) Swap(i, j int) {
291356 }
292357}
293358
359+ // Less returns whether the value at index i should sort before the
360+ // value at index j. It is part of the sort.Interface implementation.
361+ func (s * valuesSorter ) Less (i , j int ) bool {
362+ if s .strings == nil {
363+ return valueSortLess (s .values [i ], s .values [j ])
364+ }
365+ return s .strings [i ] < s .strings [j ]
366+ }
367+
294368// valueSortLess returns whether the first value should sort before the second
295369// value. It is used by valueSorter.Less as part of the sort.Interface
296370// implementation.
@@ -327,17 +401,64 @@ func valueSortLess(a, b reflect.Value) bool {
327401 }
328402 fallthrough
329403 default :
330- return a .String () < b .String ()
404+ isTimeA := isTime (a )
405+ isTimeB := isTime (b )
406+ if ! isTimeA || ! isTimeB {
407+ return a .String () < b .String ()
408+ }
409+
410+ return timeLess (a , b )
331411 }
332412}
333413
334- // Less returns whether the value at index i should sort before the
335- // value at index j. It is part of the sort.Interface implementation.
336- func (s * valuesSorter ) Less (i , j int ) bool {
337- if s .strings == nil {
338- return valueSortLess (s .values [i ], s .values [j ])
414+ // timeLess compare two values that convert to a time.Time.
415+ //
416+ // This handles nil values gracefully.
417+ func timeLess (a , b reflect.Value ) bool {
418+ convertedA , okForA := isConvertibleToTime (a )
419+ convertedB , okForB := isConvertibleToTime (b )
420+ if ! okForA || ! okForB {
421+ // types can convert, but not values: this means we have at least one nil
422+ if ! okForA { // a =< b because nil =< (any value)
423+ return true
424+ }
425+
426+ return false // b > a
339427 }
340- return s .strings [i ] < s .strings [j ]
428+
429+ // time comparison: we need to retrieve the time.Time values from a chain of pointers
430+ for convertedA .Kind () == reflect .Pointer && convertedA .IsValid () {
431+ convertedA = reflect .Indirect (convertedA )
432+ }
433+ for convertedB .Kind () == reflect .Pointer && convertedB .IsValid () {
434+ convertedB = reflect .Indirect (convertedB )
435+ }
436+
437+ // handle the case when some values are nil, after resolving pointers
438+ switch {
439+ case ! convertedA .IsValid (): // nil <= (any value)
440+ return true
441+ case ! convertedB .IsValid (): // (valid) > nil
442+ return false
443+ }
444+
445+ okIfaceA := convertedA .CanInterface ()
446+ okIfaceB := convertedB .CanInterface ()
447+
448+ if ! okIfaceA || ! okIfaceB {
449+ // defensive safeguard (should never get there, since we have successfully converted)
450+ return a .String () < b .String ()
451+ }
452+
453+ tA , okTimeA := convertedA .Interface ().(time.Time )
454+ tB , okTimeB := convertedB .Interface ().(time.Time )
455+
456+ if ! okTimeA || ! okTimeB {
457+ // defensive safeguard (should never get there, since we have successfully indirected and converted)
458+ return a .String () < b .String ()
459+ }
460+
461+ return tA .Compare (tB ) <= 0
341462}
342463
343464// sortValues is a sort function that handles both native types and any type that
0 commit comments