all: migrate x/exp/{maps,slices} uses to maps and slices in std lib

Also migrate to their updated API. This makes it possible to update to
the latest version of the golang.org/x/exp module without running into
errors.

Change-Id: Ie23cff86130ac767d0a5972293784cc70affab79
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/697175
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Neal Patel <nealpatel@google.com>
diff --git a/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go b/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
index 77db317..dffa1ef 100644
--- a/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
+++ b/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
@@ -8,10 +8,10 @@
 	"bytes"
 	"path/filepath"
 	"runtime"
+	"slices"
 	"strings"
 	"testing"
 
-	"golang.org/x/exp/slices"
 	"golang.org/x/pkgsite-metrics/internal/buildtest"
 	"golang.org/x/pkgsite-metrics/internal/govulncheck"
 	"golang.org/x/pkgsite-metrics/internal/govulncheckapi"
diff --git a/go.mod b/go.mod
index dab67b7..9135500 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,6 @@
 	go.opencensus.io v0.24.0
 	go.opentelemetry.io/otel v1.11.2
 	go.opentelemetry.io/otel/sdk v1.4.0
-	golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
 	golang.org/x/exp/event v0.0.0-20220218215828-6cf2b201936e
 	golang.org/x/mod v0.26.0
 	golang.org/x/net v0.42.0
@@ -72,6 +71,7 @@
 	go.opentelemetry.io/otel/sdk/metric v0.26.0 // indirect
 	go.opentelemetry.io/otel/trace v1.11.2 // indirect
 	golang.org/x/crypto v0.40.0 // indirect
+	golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20250711185948-6ae5c78190dc // indirect
 	golang.org/x/sys v0.34.0 // indirect
 	golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b // indirect
diff --git a/internal/analysis/analysis.go b/internal/analysis/analysis.go
index d67004e..3dcb183 100644
--- a/internal/analysis/analysis.go
+++ b/internal/analysis/analysis.go
@@ -10,13 +10,13 @@
 	"context"
 	"encoding/json"
 	"fmt"
+	"maps"
 	"net/http"
-	"sort"
+	"slices"
 	"strings"
 	"time"
 
 	bq "cloud.google.com/go/bigquery"
-	"golang.org/x/exp/maps"
 	"golang.org/x/pkgsite-metrics/internal/bigquery"
 	"golang.org/x/pkgsite-metrics/internal/derrors"
 	"golang.org/x/pkgsite-metrics/internal/queue"
@@ -247,12 +247,10 @@
 func JSONTreeToDiagnostics(jsonTree JSONTree) []*Diagnostic {
 	var diags []*Diagnostic
 	// Sort for determinism.
-	pkgIDs := maps.Keys(jsonTree)
-	sort.Strings(pkgIDs)
+	pkgIDs := slices.Sorted(maps.Keys(jsonTree))
 	for _, pkgID := range pkgIDs {
 		amap := jsonTree[pkgID]
-		aNames := maps.Keys(amap)
-		sort.Strings(aNames)
+		aNames := slices.Sorted(maps.Keys(amap))
 		for _, aName := range aNames {
 			diagsOrErr := amap[aName]
 			if diagsOrErr.Error != nil {
diff --git a/internal/vulndbreqs/bq_test.go b/internal/vulndbreqs/bq_test.go
index 48baf69..b3826df 100644
--- a/internal/vulndbreqs/bq_test.go
+++ b/internal/vulndbreqs/bq_test.go
@@ -6,13 +6,13 @@
 
 import (
 	"context"
+	"slices"
 	"testing"
 	"time"
 
 	"cloud.google.com/go/civil"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	"golang.org/x/exp/slices"
 	"golang.org/x/pkgsite-metrics/internal/bigquery"
 	test "golang.org/x/pkgsite-metrics/internal/testing"
 )
@@ -58,8 +58,23 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	slices.SortFunc(want, func(c1, c2 *RequestCount) bool { return c1.Date.After(c2.Date) })
+	slices.SortFunc(want, func(c1, c2 *RequestCount) int { return -1 * compareDate(c1.Date, c2.Date) })
 	if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(RequestCount{}, "CreatedAt")); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%s", diff)
 	}
 }
+
+// compareDate compares d1 and d2. If d1 is before d2, it returns -1;
+// if d1 is after d2, it returns +1; otherwise it returns 0.
+//
+// TODO(go.dev/issue/74596): Delete and replace with
+// https://pkg.go.dev/cloud.google.com/go/civil#Date.Compare
+// after updating that module to v0.114.0 or higher.
+func compareDate(d1, d2 civil.Date) int {
+	if d1.Before(d2) {
+		return -1
+	} else if d1.After(d2) {
+		return +1
+	}
+	return 0
+}
diff --git a/internal/vulndbreqs/compute_test.go b/internal/vulndbreqs/compute_test.go
index 1361aba..96ab888 100644
--- a/internal/vulndbreqs/compute_test.go
+++ b/internal/vulndbreqs/compute_test.go
@@ -5,9 +5,12 @@
 package vulndbreqs
 
 import (
+	cmppkg "cmp"
 	"context"
+	"maps"
 	"os"
 	"path/filepath"
+	"slices"
 	"strings"
 	"testing"
 	"time"
@@ -16,8 +19,6 @@
 	"cloud.google.com/go/storage"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	"golang.org/x/exp/maps"
-	"golang.org/x/exp/slices"
 	test "golang.org/x/pkgsite-metrics/internal/testing"
 )
 
@@ -56,8 +57,8 @@
 		t.Fatal(err)
 	}
 	// The returned slice comes from a map, so sort for determinism.
-	slices.SortFunc(got, func(r1, r2 *IPRequestCount) bool {
-		return r1.Count < r2.Count
+	slices.SortFunc(got, func(r1, r2 *IPRequestCount) int {
+		return cmppkg.Compare(r1.Count, r2.Count)
 	})
 	want := []*IPRequestCount{
 		{Date: testDate, Count: 30},
diff --git a/internal/worker/jobs_test.go b/internal/worker/jobs_test.go
index 6432a30..62ae55f 100644
--- a/internal/worker/jobs_test.go
+++ b/internal/worker/jobs_test.go
@@ -9,13 +9,13 @@
 	"context"
 	"encoding/json"
 	"fmt"
+	"maps"
+	"slices"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/google/go-cmp/cmp"
-	"golang.org/x/exp/maps"
-	"golang.org/x/exp/slices"
 	"golang.org/x/pkgsite-metrics/internal/derrors"
 	"golang.org/x/pkgsite-metrics/internal/jobs"
 )
@@ -107,12 +107,11 @@
 }
 
 func (d *testJobDB) ListJobs(ctx context.Context, f func(*jobs.Job, time.Time) error) error {
-	jobslice := maps.Values(d.jobs)
 	// Sort by StartedAt descending.
-	slices.SortFunc(jobslice, func(j1, j2 *jobs.Job) bool {
-		return j1.StartedAt.After(j2.StartedAt)
+	sortedJobs := slices.SortedFunc(maps.Values(d.jobs), func(j1, j2 *jobs.Job) int {
+		return -1 * j1.StartedAt.Compare(j2.StartedAt)
 	})
-	for _, j := range jobslice {
+	for _, j := range sortedJobs {
 		if err := f(j, time.Time{}); err != nil {
 			return err
 		}