New method to format date-time in locale and LC_TIME sensitive way

pull/206/head
Simon Roberts 3 years ago
parent 1cf12fd173
commit 9e910402f5
No known key found for this signature in database
GPG Key ID: 0F30F99E6B771FD4

@ -9,10 +9,13 @@ import (
"time"
"github.com/goodsign/monday"
"github.com/jeandeaual/go-locale"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
var cachedSystemLocale = ""
// Numericf produces a string from of the given number with give fixed precision
// in base 10 with thousands separators after every three orders of magnitude
// using thousands and decimal separator according to LC_NUMERIC; defaulting "en".
@ -31,58 +34,59 @@ func Monetaryf(value float64, precision int) string {
return f(value, precision, "LC_MONETARY", false)
}
// Attempt to determine the locale from the current environment. If usage is provided, treat it as the name of a LC_xxx environment variable.
// LANGUAGE
// LC_ALL Will override the setting of all other LC_* variables.
// LC_MONETARY Sets the locale for the LC_MONETARY category.
// LC_NUMERIC Sets the locale for the LC_NUMERIC category.
// LC_TIME Sets the locale for the LC_TIME category.
// LANG Used as a substitute for any unset LC_* variable. If LANG is unset, it will act as if set to "C"
// Local is language[_territory][.codeset] [@modifier]
func DetectLocale(usage string) string {
if lc, ok := os.LookupEnv("LANGUAGE"); ok {
return lc
}
if lc, ok := os.LookupEnv("LC_ALL"); ok {
return lc
}
if usage != "" {
if lc, ok := os.LookupEnv(strings.ToUpper(usage)); ok {
return lc
}
// borrowed from go-locale/util.go
func splitLocale(locale string) (string, string) {
// Remove the encoding, if present
formattedLocale := strings.Split(locale, ".")[0]
// Normalize by replacing the hyphens with underscores
formattedLocale = strings.Replace(formattedLocale, "-", "_", -1)
// Split at the underscore
split := strings.Split(formattedLocale, "_")
language := split[0]
territory := ""
if len(split) > 1 {
territory = split[1]
}
if lc, ok := os.LookupEnv("LANG"); ok {
return lc
}
return "C"
}
// func DetectLanguage(usage string) language.Tag {
// lc := DetectLocale(usage)
// if lc == "C" {
// lc = "en"
// }
// return language.Make(lc)
// }
return language, territory
}
// Locale is language[_territory][.codeset] [@modifier]
func FormatTime(time time.Time, layout string) string {
// Attempt to use the environment to determine monday.Locale
xxx := strings.Split(DetectLocale("LC_TIME"), ".")[0]
bits := strings.Split(xxx, "_")
// Look for a supported Locale with default to en_US
var locale monday.Locale = monday.LocaleEnUS // default
if len(bits) == 2 {
lookFor := monday.Locale(strings.ToLower(bits[0]) + "_" + strings.ToUpper(bits[1]))
for _, v := range monday.ListLocales() {
if v == lookFor {
locale = v
// GetLocale returns the current locale as defined in IETF BCP 47 (e.g. "en-US").
// The envvar is provided this is checked first, before the platform-specific defaults.
func getLocale(envvar string) string {
userLocale := "en-US" // default language-REGION
// First try looking up envar directly
envlang, ok := os.LookupEnv(envvar)
if ok {
language, region := splitLocale(envlang)
userLocale = language
if len(region) > 0 {
userLocale = strings.Join([]string{language, region}, "-")
}
} else {
// Then use (cached) system-specific locale
if cachedSystemLocale == "" {
if loc, err := locale.GetLocale(); err == nil {
userLocale = loc
cachedSystemLocale = loc
}
} else {
userLocale = cachedSystemLocale
}
}
return userLocale
}
// formatTimeExplicit formats the given time using the prescribed layout with the provided userLocale
func formatTimeExplicit(time time.Time, layout string, userLocale string) string {
mondayLocale := monday.Locale(strings.Replace(userLocale, "-", "_", 1))
return monday.Format(time, layout, mondayLocale)
}
return monday.Format(time, layout, locale)
// FormatTime is a dropin replacement time.Format(layout) that uses system locale + LC_TIME
func FormatTime(time time.Time, layout string) string {
return formatTimeExplicit(time, layout, getLocale("LC_TIME"))
}
// f formats given value, with precision decimal places using thousands and decimal

@ -54,9 +54,41 @@ func TestScaleNumeric(t *testing.T) {
}
func TestFormatTime(t *testing.T) {
s := FormatTime(time.Now(), "Jan 2006")
t.Logf("First: %s", s)
if Monetaryf(834142.3256, 2) != "834,142.3256" {
t.FailNow()
testData := map[string]map[string]string{
"en_GB": {
"Monday 2 January 2006": "Wednesday 12 March 2014",
"Jan 2006": "Mar 2014",
"02 Jan 2006": "12 Mar 2014",
"02/01/2006": "12/03/2014",
},
"en_US": {
"Monday 2 January 2006": "Wednesday 12 March 2014",
"Jan 2006": "Mar 2014",
"02 Jan 2006": "12 Mar 2014",
"02/01/2006": "12/03/2014", // ??
},
"fr_FR": {
"Monday 2 January 2006": "mercredi 12 mars 2014",
"Jan 2006": "mars 2014",
"02 Jan 2006": "12 mars 2014",
"02/01/2006": "12/03/2014",
},
"de_DE": {
"Monday 2 January 2006": "Mittwoch 12 März 2014",
"Jan 2006": "Mär 2014",
"02 Jan 2006": "12 Mär 2014",
"02/01/2006": "12/03/2014",
},
}
testTime := time.Date(2014, 3, 12, 0, 0, 0, 0, time.Local)
for locale, tests := range testData {
for layout, result := range tests {
s := formatTimeExplicit(testTime, layout, locale)
if s != result {
t.Fatalf("Expected layout '%s' in locale %s to render '%s' but got '%s'", layout, locale, result, s)
}
}
}
}

Loading…
Cancel
Save