Trier une slice Go par date : de sort.Interface à go-timesort
Tu as une slice de structs avec un champ time.Time. Tu veux les trier chronologiquement. Il y a trois façons de faire en Go aujourd’hui — et elles ne sont pas équivalentes.
sort.Interface : l’approche classique
L’approche canonique pré-Go 1.21 : implémenter sort.Interface sur un type wrapper.
type Event struct {
Name string
CreatedAt time.Time
}
type ByDate []Event
func (a ByDate) Len() int { return len(a) }
func (a ByDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByDate) Less(i, j int) bool { return a[i].CreatedAt.Before(a[j].CreatedAt) }
// Utilisation
sort.Sort(ByDate(events))
Ça marche. C’est lisible. Mais tu dois répéter ce pattern pour chaque struct que tu veux trier par date — ByDateUser, ByDateOrder, ByDateInvoice… le boilerplate s’accumule vite.
Go 1.21 : slices.SortFunc, l’approche moderne
Depuis Go 1.21, le package slices de la bibliothèque standard fournit slices.SortFunc. La signature attend une fonction de comparaison qui retourne un entier négatif, zéro, ou positif — exactement ce que time.Time.Compare() retourne.
import "slices"
slices.SortFunc(events, func(a, b Event) int {
return a.CreatedAt.Compare(b.CreatedAt)
})
C’est tout. Pas de type wrapper, pas de méthodes à implémenter. Pour l’ordre descendant, on inverse :
slices.SortFunc(events, func(a, b Event) int {
return b.CreatedAt.Compare(a.CreatedAt)
})
time.Time.Compare() est disponible depuis Go 1.20. Avant ça, tu pouvais utiliser a.CreatedAt.Before(b.CreatedAt) avec un ternaire, mais Compare() s’intègre directement dans slices.SortFunc sans acrobaties.
Pour la grande majorité des cas, slices.SortFunc est la bonne réponse. Pas besoin de lib externe, pas de boilerplate.
Quand ça ne suffit plus
slices.SortFunc couvre les cas simples — un critère de tri, une struct, usage ponctuel. Le problème apparaît quand tu te retrouves à écrire le même pattern en boucle :
- plusieurs structs différentes, chacune avec son champ date
- besoin de tri stable (préserver l’ordre des éléments à date égale)
- opérations répétées sur une même slice (tri asc puis desc selon le contexte)
- besoin de cloner la slice avant de trier sans toucher l’original
À ce stade, soit tu encapsules slices.SortFunc dans des helpers maison, soit tu cherches une abstraction générique.
go-timesort : l’API générique
go-timesort est une lib légère que j’ai écrite pour encapsuler exactement ce pattern. Elle utilise les génériques Go (1.18+) et n’a aucune dépendance externe.
go get github.com/azrod/go-timesort
L’idée centrale : tu crées un objet trieur en lui passant ta slice et une fonction qui extrait le time.Time depuis chaque élément.
import gts "github.com/azrod/go-timesort"
type User struct {
Name string
CreatedAt time.Time
}
users := []User{
{Name: "Alice", CreatedAt: time.Date(2022, 5, 1, 0, 0, 0, 0, time.UTC)},
{Name: "Bob", CreatedAt: time.Date(2021, 8, 15, 0, 0, 0, 0, time.UTC)},
{Name: "Carol", CreatedAt: time.Date(2023, 1, 20, 0, 0, 0, 0, time.UTC)},
}
sorter := gts.New(users, func(u User) time.Time {
return u.CreatedAt
})
sorter.SortAsc()
fmt.Println(sorter.Items()) // Bob, Alice, Carol
sorter.SortDesc()
fmt.Println(sorter.Items()) // Carol, Alice, Bob
Le tri stable et le clonage sont disponibles via SortStableAsc(), SortStableDesc(), et Clone() :
// Tri stable : préserve l'ordre des éléments à date identique
sorter.SortStableAsc()
// Clone : retourne une nouvelle slice triée sans modifier l'originale
sorted := sorter.Clone().SortAsc().Items()
Le tri stable est utile quand ta slice peut contenir des éléments à dates identiques et que leur ordre relatif a une signification métier (logs d’audit, événements transactionnels).
Quel outil pour quel cas
| Cas | Approche recommandée |
|---|---|
| Tri ponctuel, une struct, un critère | slices.SortFunc |
| Besoins multiples (asc/desc, stable, clone) | go-timesort |
| Plusieurs structs différentes à trier par date | go-timesort |
| Go < 1.21 | sort.Interface ou go-timesort |
sort.Interface reste pertinent sur une codebase pré-1.21, ou si ta logique de comparaison dépasse un simple champ date. Pour le reste : slices.SortFunc — go-timesort dès que le pattern se répète.