Trier une slice Go par date : de sort.Interface à go-timesort

Article
🇬🇧 Cet article est aussi disponible en English

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)
})
Note

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()
Conseil

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

CasApproche recommandée
Tri ponctuel, une struct, un critèreslices.SortFunc
Besoins multiples (asc/desc, stable, clone)go-timesort
Plusieurs structs différentes à trier par datego-timesort
Go < 1.21sort.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.SortFuncgo-timesort dès que le pattern se répète.

← Retour aux articles