You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2026/02/05 08:17:42 UTC

(answer) 01/01: fix: enhance bracket handling in formatting and add concurrency test for internationalization

This is an automated email from the ASF dual-hosted git repository.

linkinstar pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/answer.git

commit 638fb082c56431397a3c63d00f3fc57c6b57f08d
Author: LinkinStars <li...@foxmail.com>
AuthorDate: Thu Feb 5 16:17:32 2026 +0800

    fix: enhance bracket handling in formatting and add concurrency test for internationalization
---
 pkg/day/day.go      | 21 +++++++++++++--
 pkg/day/day_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 92 insertions(+), 7 deletions(-)

diff --git a/pkg/day/day.go b/pkg/day/day.go
index 2fd86a6c..797da68b 100644
--- a/pkg/day/day.go
+++ b/pkg/day/day.go
@@ -174,13 +174,30 @@ func nextStdChunk(from []rune) (to, suffix []rune) {
 	case "a":
 		old = "a"
 	case "[":
-		if len(from) >= 4 && string(from[:4]) == "[at]" {
-			old = "[at]"
+		// treat anything inside [] as literal text, e.g. [at], [a las], [o]
+		end := -1
+		for i := 1; i < len(from); i++ {
+			if from[i] == ']' {
+				end = i
+				break
+			}
+		}
+		if end != -1 {
+			to = []rune(string(from[1:end]))
+			suffix = from[end+1:]
+			return
 		}
+		// no closing bracket, fall back to literal "["
+		old = "["
 	default:
 		old = s
 	}
 
+	if old == "" {
+		// safety: always consume at least one rune
+		old = s
+	}
+
 	tos, ok := placeholder[old]
 	if !ok {
 		to = []rune(old)
diff --git a/pkg/day/day_test.go b/pkg/day/day_test.go
index 4f945d88..1ff75766 100644
--- a/pkg/day/day_test.go
+++ b/pkg/day/day_test.go
@@ -20,17 +20,85 @@
 package day
 
 import (
+	"os"
+	"path/filepath"
+	"runtime"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"gopkg.in/yaml.v3"
 )
 
 func TestFormat(t *testing.T) {
-	sec := time.Now().Unix()
-	tz := "Asia/Shanghai"
-	actual := Format(sec, "YYYY-MM-DD HH:mm:ss", tz)
-	_, _ = time.LoadLocation(tz)
-	expected := time.Unix(sec, 0).Format("2006-01-02 15:04:05")
+	sec := int64(1700000000)
+	tz := "UTC"
+
+	actual := Format(sec, "MMM D, YYYY [a las] HH:mm", tz)
+	expected := time.Unix(sec, 0).Format("Jan 2, 2006") + " a las " + time.Unix(sec, 0).Format("15:04")
 	assert.Equal(t, expected, actual)
 }
+
+func TestFormat_AllLanguagesNoHang(t *testing.T) {
+	_, currentFile, _, ok := runtime.Caller(0)
+	if !ok {
+		t.Fatalf("unable to determine test file path")
+	}
+	i18nDir := filepath.Clean(filepath.Join(filepath.Dir(currentFile), "..", "..", "i18n"))
+	entries, err := os.ReadDir(i18nDir)
+	if err != nil {
+		t.Fatalf("read i18n dir: %v", err)
+	}
+
+	type datesConfig struct {
+		LongDate         string `yaml:"long_date"`
+		LongDateWithYear string `yaml:"long_date_with_year"`
+		LongDateWithTime string `yaml:"long_date_with_time"`
+	}
+	type uiConfig struct {
+		Dates datesConfig `yaml:"dates"`
+	}
+	type fileConfig struct {
+		UI uiConfig `yaml:"ui"`
+	}
+
+	sec := int64(1700000000)
+	tz := "UTC"
+
+	for _, entry := range entries {
+		name := entry.Name()
+		if entry.IsDir() || filepath.Ext(name) != ".yaml" || name == "i18n.yaml" {
+			continue
+		}
+		data, err := os.ReadFile(filepath.Join(i18nDir, name))
+		if err != nil {
+			t.Fatalf("read %s: %v", name, err)
+		}
+		var cfg fileConfig
+		if err := yaml.Unmarshal(data, &cfg); err != nil {
+			t.Fatalf("parse %s: %v", name, err)
+		}
+
+		formats := []string{
+			cfg.UI.Dates.LongDate,
+			cfg.UI.Dates.LongDateWithYear,
+			cfg.UI.Dates.LongDateWithTime,
+		}
+		for _, format := range formats {
+			if format == "" {
+				continue
+			}
+			done := make(chan struct{})
+			go func(f string) {
+				_ = Format(sec, f, tz)
+				close(done)
+			}(format)
+
+			select {
+			case <-done:
+			case <-time.After(200 * time.Millisecond):
+				t.Fatalf("format hang in %s: %q", name, format)
+			}
+		}
+	}
+}