summaryrefslogtreecommitdiffstats
path: root/common/htime
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2021-07-26 18:28:57 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2021-07-27 19:02:48 +0200
commita57dda854b5efd3429af5f0b1564fc9d9d5439b9 (patch)
tree8e01442c7c43cc5bef5c9d5dbfdb3e0132736efe /common/htime
parentf9afba933579de07d2d2e36a457895ec5f1b7f01 (diff)
Localize time.Format
Fixes #8797
Diffstat (limited to 'common/htime')
-rw-r--r--common/htime/time.go127
-rw-r--r--common/htime/time_test.go111
2 files changed, 238 insertions, 0 deletions
diff --git a/common/htime/time.go b/common/htime/time.go
new file mode 100644
index 000000000..cd294b3a2
--- /dev/null
+++ b/common/htime/time.go
@@ -0,0 +1,127 @@
+// 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 htime
+
+import (
+ "strings"
+ "time"
+
+ "github.com/go-playground/locales"
+)
+
+var (
+ longDayNames = []string{
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ }
+
+ shortDayNames = []string{
+ "Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ }
+
+ shortMonthNames = []string{
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ }
+
+ longMonthNames = []string{
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ }
+)
+
+func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
+ if ltr == nil {
+ panic("must provide a locales.Translator")
+ }
+ return TimeFormatter{
+ ltr: ltr,
+ }
+}
+
+// TimeFormatter is locale aware.
+type TimeFormatter struct {
+ ltr locales.Translator
+}
+
+func (f TimeFormatter) Format(t time.Time, layout string) string {
+ if layout == "" {
+ return ""
+ }
+
+ if layout[0] == ':' {
+ // It may be one of Hugo's custom layouts.
+ switch strings.ToLower(layout[1:]) {
+ case "date_full":
+ return f.ltr.FmtDateFull(t)
+ case "date_long":
+ return f.ltr.FmtDateLong(t)
+ case "date_medium":
+ return f.ltr.FmtDateMedium(t)
+ case "date_short":
+ return f.ltr.FmtDateShort(t)
+ case "time_full":
+ return f.ltr.FmtTimeFull(t)
+ case "time_long":
+ return f.ltr.FmtTimeLong(t)
+ case "time_medium":
+ return f.ltr.FmtTimeMedium(t)
+ case "time_short":
+ return f.ltr.FmtTimeShort(t)
+ }
+ }
+
+ s := t.Format(layout)
+
+ monthIdx := t.Month() - 1 // Month() starts at 1.
+ dayIdx := t.Weekday()
+
+ s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthWide(t.Month()))
+ s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
+ s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdayWide(t.Weekday()))
+ s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
+
+ return s
+}
diff --git a/common/htime/time_test.go b/common/htime/time_test.go
new file mode 100644
index 000000000..383027755
--- /dev/null
+++ b/common/htime/time_test.go
@@ -0,0 +1,111 @@
+// 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 htime
+
+import (
+ "testing"
+ "time"
+
+ translators "github.com/bep/gotranslators"
+ qt "github.com/frankban/quicktest"
+)
+
+func TestTimeFormatter(t *testing.T) {
+ c := qt.New(t)
+
+ june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
+ june06 = june06.Add(7777 * time.Second)
+
+ c.Run("Norsk nynorsk", func(c *qt.C) {
+ f := NewTimeFormatter(translators.Get("nn"))
+
+ c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "onsdag juni 6 2018")
+ c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "on. juni 6 2018")
+ c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "on. on.")
+ })
+
+ c.Run("Custom layouts Norsk nynorsk", func(c *qt.C) {
+ f := NewTimeFormatter(translators.Get("nn"))
+
+ c.Assert(f.Format(june06, ":date_full"), qt.Equals, "onsdag 6. juni 2018")
+ c.Assert(f.Format(june06, ":date_long"), qt.Equals, "6. juni 2018")
+ c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "6. juni 2018")
+ c.Assert(f.Format(june06, ":date_short"), qt.Equals, "06.06.2018")
+
+ c.Assert(f.Format(june06, ":time_full"), qt.Equals, "kl. 02:09:37 UTC")
+ c.Assert(f.Format(june06, ":time_long"), qt.Equals, "02:09:37 UTC")
+ c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "02:09:37")
+ c.Assert(f.Format(june06, ":time_short"), qt.Equals, "02:09")
+
+ })
+
+ c.Run("Custom layouts English", func(c *qt.C) {
+ f := NewTimeFormatter(translators.Get("en"))
+
+ c.Assert(f.Format(june06, ":date_full"), qt.Equals, "Wednesday, June 6, 2018")
+ c.Assert(f.Format(june06, ":date_long"), qt.Equals, "June 6, 2018")
+ c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "Jun 6, 2018")
+ c.Assert(f.Format(june06, ":date_short"), qt.Equals, "6/6/18")
+
+ c.Assert(f.Format(june06, ":time_full"), qt.Equals, "2:09:37 am UTC")
+ c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37 am UTC")
+ c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37 am")
+ c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09 am")
+
+ })
+
+ c.Run("English", func(c *qt.C) {
+ f := NewTimeFormatter(translators.Get("en"))
+
+ c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "Wednesday Jun 6 2018")
+ c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "Wed June 6 2018")
+ c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "Wed Wed")
+ })
+
+}
+
+func BenchmarkTimeFormatter(b *testing.B) {
+ june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
+
+ b.Run("Native", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ got := june06.Format("Monday Jan 2 2006")
+ if got != "Wednesday Jun 6 2018" {
+ b.Fatalf("invalid format, got %q", got)
+ }
+ }
+ })
+
+ b.Run("Localized", func(b *testing.B) {
+ f := NewTimeFormatter(translators.Get("nn"))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ got := f.Format(june06, "Monday Jan 2 2006")
+ if got != "onsdag juni 6 2018" {
+ b.Fatalf("invalid format, got %q", got)
+ }
+ }
+ })
+
+ b.Run("Localized Custom", func(b *testing.B) {
+ f := NewTimeFormatter(translators.Get("nn"))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ got := f.Format(june06, ":date_medium")
+ if got != "6. juni 2018" {
+ b.Fatalf("invalid format, got %q", got)
+ }
+ }
+ })
+}