// Render output formats for all sites.
        renderFormats output.Formats
 
+       // The currently rendered Site.
+       currentSite *Site
+
        *deps.Deps
 
        gitInfo       *gitInfo
 
 
        i := 0
        for _, s := range h.Sites {
+               h.currentSite = s
                for siteOutIdx, renderFormat := range s.renderFormats {
                        siteRenderContext.outIdx = siteOutIdx
                        siteRenderContext.sitesOutIdx = i
 
        return s.s.h.siteInfos()
 }
 
+// Current returns the currently rendered Site.
+func (s *SiteInfo) Current() page.Site {
+       return s.s.h.currentSite.Info
+}
+
 func (s *SiteInfo) String() string {
        return fmt.Sprintf("Site(%q)", s.title)
 }
 
 
        "github.com/pkg/errors"
 
+       "github.com/gohugoio/hugo/common/htime"
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/locales"
        // Used for date formatting etc. We don't want these exported to the
        // templates.
        // TODO(bep) do the same for some of the others.
-       translator locales.Translator
+       translator    locales.Translator
+       timeFormatter htime.TimeFormatter
 
        location *time.Location
 
                Lang:       lang,
                ContentDir: cfg.GetString("contentDir"),
                Cfg:        cfg, LocalCfg: localCfg,
-               Provider:   compositeConfig,
-               params:     params,
-               translator: translator,
+               Provider:      compositeConfig,
+               params:        params,
+               translator:    translator,
+               timeFormatter: htime.NewTimeFormatter(translator),
        }
 
        if err := l.loadLocation(cfg.GetString("timeZone")); err != nil {
 // Internal access to unexported Language fields.
 // This construct is to prevent them from leaking to the templates.
 
+func GetTimeFormatter(l *Language) htime.TimeFormatter {
+       return l.timeFormatter
+}
+
 func GetTranslator(l *Language) locales.Translator {
        return l.translator
 }
 
--- /dev/null
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package page_test
+
+import (
+       "testing"
+
+       "github.com/gohugoio/hugo/hugolib"
+)
+
+func TestGroupByLocalizedDate(t *testing.T) {
+
+       files := `
+-- config.toml --
+defaultContentLanguage = 'en'
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+title = 'My blog'
+weight = 1
+[languages.fr]
+title = 'Mon blogue'
+weight = 2
+[languages.nn]
+title = 'Bloggen min'
+weight = 3
+-- content/p1.md --
+---
+title: "Post 1"
+date: "2020-01-01"
+---
+-- content/p2.md --
+---
+title: "Post 2"
+date: "2020-02-01"
+---
+-- content/p1.fr.md --
+---
+title: "Post 1"
+date: "2020-01-01"
+---
+-- content/p2.fr.md --
+---
+title: "Post 2"
+date: "2020-02-01"
+---
+-- layouts/index.html --
+{{ range $k, $v := site.RegularPages.GroupByDate "January, 2006" }}{{ $k }}|{{ $v.Key }}|{{ $v.Pages }}{{ end }}
+
+       `
+
+       b := hugolib.NewIntegrationTestBuilder(
+               hugolib.IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+                       NeedsOsFS:   true,
+               }).Build()
+
+       b.AssertFileContent("public/en/index.html", "0|February, 2020|Pages(1)1|January, 2020|Pages(1)")
+       b.AssertFileContent("public/fr/index.html", "0|février, 2020|Pages(1)1|janvier, 2020|Pages(1)")
+}
 
 package page
 
 import (
-       "github.com/gohugoio/hugo/common/hugo"
        "path/filepath"
        "testing"
 
+       "github.com/gohugoio/hugo/common/hugo"
+
        qt "github.com/frankban/quicktest"
 )
 
 
        "github.com/gohugoio/hugo/common/collections"
        "github.com/gohugoio/hugo/common/hreflect"
        "github.com/gohugoio/hugo/compare"
+       "github.com/gohugoio/hugo/langs"
 
        "github.com/gohugoio/hugo/resources/resource"
 )
        return r, nil
 }
 
-func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p Page) string, order ...string) (PagesGroup, error) {
+func (p Pages) groupByDateField(format string, sorter func(p Pages) Pages, getDate func(p Page) time.Time, order ...string) (PagesGroup, error) {
        if len(p) < 1 {
                return nil, nil
        }
                return nil, nil
        }
 
-       date := formatter(sp[0].(Page))
+       firstPage := sp[0].(Page)
+       date := getDate(firstPage)
+
+       // Pages may be a mix of multiple languages, so we need to use the language
+       // for the currently rendered Site.
+       currentSite := firstPage.Site().Current()
+       formatter := langs.GetTimeFormatter(currentSite.Language())
+       formatted := formatter.Format(date, format)
        var r []PageGroup
-       r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)})
+       r = append(r, PageGroup{Key: formatted, Pages: make(Pages, 0)})
        r[0].Pages = append(r[0].Pages, sp[0])
 
        i := 0
        for _, e := range sp[1:] {
-               date = formatter(e.(Page))
-               if r[i].Key.(string) != date {
-                       r = append(r, PageGroup{Key: date})
+               date = getDate(e.(Page))
+               formatted := formatter.Format(date, format)
+               if r[i].Key.(string) != formatted {
+                       r = append(r, PageGroup{Key: formatted})
                        i++
                }
                r[i].Pages = append(r[i].Pages, e)
        sorter := func(p Pages) Pages {
                return p.ByDate()
        }
-       formatter := func(p Page) string {
-               return p.Date().Format(format)
+       getDate := func(p Page) time.Time {
+               return p.Date()
        }
-       return p.groupByDateField(sorter, formatter, order...)
+       return p.groupByDateField(format, sorter, getDate, order...)
 }
 
 // GroupByPublishDate groups by the given page's PublishDate value in
        sorter := func(p Pages) Pages {
                return p.ByPublishDate()
        }
-       formatter := func(p Page) string {
-               return p.PublishDate().Format(format)
+       getDate := func(p Page) time.Time {
+               return p.PublishDate()
        }
-       return p.groupByDateField(sorter, formatter, order...)
+       return p.groupByDateField(format, sorter, getDate, order...)
 }
 
 // GroupByExpiryDate groups by the given page's ExpireDate value in
        sorter := func(p Pages) Pages {
                return p.ByExpiryDate()
        }
-       formatter := func(p Page) string {
-               return p.ExpiryDate().Format(format)
+       getDate := func(p Page) time.Time {
+               return p.ExpiryDate()
        }
-       return p.groupByDateField(sorter, formatter, order...)
+       return p.groupByDateField(format, sorter, getDate, order...)
 }
 
 // GroupByLastmod groups by the given page's Lastmod value in
        sorter := func(p Pages) Pages {
                return p.ByLastmod()
        }
-       formatter := func(p Page) string {
-               return p.Lastmod().Format(format)
+       getDate := func(p Page) time.Time {
+               return p.Lastmod()
        }
-       return p.groupByDateField(sorter, formatter, order...)
+       return p.groupByDateField(format, sorter, getDate, order...)
 }
 
 // GroupByParamDate groups by a date set as a param on the page in
                pageBy(pdate).Sort(r)
                return r
        }
-       formatter := func(p Page) string {
-               return dates[p].Format(format)
+       getDate := func(p Page) time.Time {
+               return dates[p]
        }
-       return p.groupByDateField(sorter, formatter, order...)
+       return p.groupByDateField(format, sorter, getDate, order...)
 }
 
 // ProbablyEq wraps compare.ProbablyEqer
 
        "testing"
        "time"
 
-       "github.com/gohugoio/hugo/htesting/hqt"
-       "github.com/gohugoio/hugo/source"
-
        "github.com/gohugoio/hugo/resources/resource"
+       "github.com/google/go-cmp/cmp"
 
        qt "github.com/frankban/quicktest"
 )
 
-var eq = qt.CmpEquals(hqt.DeepAllowUnexported(
-       &testPage{},
-       &source.FileInfo{},
-))
+var eq = qt.CmpEquals(
+       cmp.Comparer(func(p1, p2 testPage) bool {
+               return p1.path == p2.path && p1.weight == p2.weight
+       }),
+)
 
 func TestDefaultSort(t *testing.T) {
        t.Parallel()
 
        ServerPort() int
        Title() string
        Sites() Sites
+       Current() Site
        Hugo() hugo.Info
        BaseURL() template.URL
        Taxonomies() any
        return nil
 }
 
+func (t testSite) Current() Site {
+       return t
+}
+
 func (t testSite) IsServer() bool {
        return false
 }
 
                currentSection: &testPage{
                        sectionEntries: []string{"a", "b", "c"},
                },
+               site: testSite{l: langs.NewDefaultLanguage(config.New())},
        }
 }
 
 
                if d.Language == nil {
                        panic("Language must be set")
                }
-               ctx := New(langs.GetTranslator(d.Language), langs.GetLocation(d.Language))
+               ctx := New(langs.GetTimeFormatter(d.Language), langs.GetLocation(d.Language))
 
                ns := &internal.TemplateFuncsNamespace{
                        Name: name,
 
 
        "github.com/gohugoio/hugo/common/htime"
 
-       "github.com/gohugoio/locales"
-
        "github.com/spf13/cast"
 )
 
 // New returns a new instance of the time-namespaced template functions.
-func New(translator locales.Translator, location *time.Location) *Namespace {
+func New(timeFormatter htime.TimeFormatter, location *time.Location) *Namespace {
        return &Namespace{
-               timeFormatter: htime.NewTimeFormatter(translator),
+               timeFormatter: timeFormatter,
                location:      location,
        }
 }
 
 
        qt "github.com/frankban/quicktest"
 
+       "github.com/gohugoio/hugo/common/htime"
        translators "github.com/gohugoio/localescompressed"
 )
 
        t.Parallel()
 
        loc, _ := time.LoadLocation("America/Antigua")
-       ns := New(translators.GetTranslator("en"), loc)
+       ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), loc)
 
        for i, test := range []struct {
                name     string
 
        c.Run("UTC", func(c *qt.C) {
                c.Parallel()
-               ns := New(translators.GetTranslator("en"), time.UTC)
+               ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC)
 
                for i, test := range []struct {
                        layout string
 
                loc, err := time.LoadLocation("America/Los_Angeles")
                c.Assert(err, qt.IsNil)
-               ns := New(translators.GetTranslator("en"), loc)
+               ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), loc)
 
                d, err := ns.Format(":time_full", "2020-03-09T11:00:00")
 
 func TestDuration(t *testing.T) {
        t.Parallel()
 
-       ns := New(translators.GetTranslator("en"), time.UTC)
+       ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC)
 
        for i, test := range []struct {
                unit   any