pax_global_header00006660000000000000000000000064146306147310014517gustar00rootroot0000000000000052 comment=1ce0db37c11c462f17f4f7c1c6f7571f5d92f880 golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/000077500000000000000000000000001463061473100230465ustar00rootroot00000000000000golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/LICENSE000066400000000000000000000020501463061473100240500ustar00rootroot00000000000000MIT License Copyright (c) 2022 delthas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/README.md000066400000000000000000000016201463061473100243240ustar00rootroot00000000000000# go-localeinfo [![GoDoc](https://godocs.io/github.com/delthas/go-localeinfo?status.svg)](https://godocs.io/github.com/delthas/go-localeinfo) A cross-platform library for extracting locale information. Rather than extracting the current locale name (e.g. en_US), this library enables clients to extract monetary/numeric/time formatting information. This library is basically a wrapper over different platform-specific calls: - on Linux: nl_langinfo - on Windows: GetLocaleInfoEx - on other platforms: a stub implementation that returns empty values ## Usage The API is well-documented in its [![GoDoc](https://godocs.io/github.com/delthas/go-localeinfo?status.svg)](https://godocs.io/github.com/delthas/go-localeinfo) ``` l := localeinfo.Default() fmt.Printf("123%s456\n", l.ThousandSeparator()) ``` ## Status Used in small-scale production environments. - [ ] Add darwin - [ ] Add web ## License MIT golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/go.mod000066400000000000000000000000611463061473100241510ustar00rootroot00000000000000module github.com/delthas/go-localeinfo go 1.18 golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/localeinfo.go000066400000000000000000000064671463061473100255250ustar00rootroot00000000000000// Package localeinfo offers platform-agnostic information about the current locale. // The main entrypoints are Default and NewLocale, which return a Locale. // Most calls of interest are the methods of Locale. package localeinfo import ( "time" ) // Locale represents a particular locale, instantiated from Default or NewLocale. // Locale is an interface over platform-specific locale implementations. // Most calls are best-effort, as not all platforms support the same locale information. type Locale interface { // Encoding returns the name of the character encoding used in the selected locale, such as "UTF-8", "ISO-8859-1", or "ANSI_X3.4-1968" (better known as US-ASCII). Encoding() string // DateTimeFormat returns a format string to represent time and date in a locale-specific way (corresponds to strftime's %c). DateTimeFormat() string // DateFormat returns a format string to represent a date in a locale-specific way (corresponds to strftime's %x). DateFormat() string // TimeFormat returns a format string to represent a time in a locale-specific way (corresponds to strftime's %X). TimeFormat() string // AM returns the affix for ante meridiem time (corresponds to strftime's %p). AM() string // PM returns the affix for post meridiem time (corresponds to strftime's %p). PM() string // TimeAMPMFormat returns a format string to represent a time in AM/PM notation in a locale-specific way (corresponds to strftime's %r). TimeAMPMFormat() string // Day returns the name of the day of the week (corresponds to strftime's %A). Day(day time.Weekday) string // ShortDay returns the abbreviated name of the day of the week (corresponds to strftime's %a). ShortDay(day time.Weekday) string // Month returns the name of the month (corresponds to strftime's %B). Month(month time.Month) string // ShortMonth returns the abbreviated name of the month (corresponds to strftime's %b). ShortMonth(month time.Month) string // Radix returns the decimal separator between the integer and fractional parts of a decimal number. Radix() string // ThousandSeparator returns the separator character for thousands. ThousandSeparator() string // Currency returns the currency symbol and where the character should be placed relative to a monetary value. Currency() (symbol string, position CurrencyFormat) } // Default returns the current locale. func Default() Locale { l, err := NewLocale("") if err != nil { panic(err) } return l } // CurrencyFormat describes how the currency symbol should be formatted relative to a monetary value. type CurrencyFormat int const ( // BeforeValue means prefix, no separation, for example, $1.1 BeforeValue CurrencyFormat = iota // AfterValue means suffix, no separation, for example, 1.1$ AfterValue // BeforeValueSpace means prefix, 1-character separation, for example, $ 1.1 BeforeValueSpace // AfterValueSpace means suffix, 1-character separation, for example, 1.1 $ AfterValueSpace // ReplaceRadix means the currency symbol replaces the radix, for example, 1$1 ReplaceRadix ) func (c CurrencyFormat) String() string { switch c { case BeforeValue: return "BeforeValue" case AfterValue: return "AfterValue" case BeforeValueSpace: return "BeforeValueSpace" case AfterValueSpace: return "AfterValueSpace" case ReplaceRadix: return "ReplaceRadix" default: return "" } } golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/localeinfo_linux.go000066400000000000000000000066011463061473100267320ustar00rootroot00000000000000//go:build cgo package localeinfo /* #include #include #include */ import "C" import ( "runtime" "sync" "time" "unsafe" ) var _ Locale = &linuxLocale{} type linuxLocale struct { l sync.Mutex locale C.locale_t } // NewLocale tries creating a new Locale from the specified locale name. func NewLocale(name string) (Locale, error) { clLocale := C.CString(name) defer C.free(unsafe.Pointer(clLocale)) l, err := C.newlocale(C.LC_ALL_MASK, clLocale, nil) if l == nil { return nil, err } ll := &linuxLocale{ locale: l, } runtime.SetFinalizer(ll, func(l *linuxLocale) { C.freelocale(l.locale) }) return ll, nil } func (l *linuxLocale) Encoding() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.CODESET, l.locale) return C.GoString(p) } func (l *linuxLocale) DateTimeFormat() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.D_T_FMT, l.locale) return C.GoString(p) } func (l *linuxLocale) DateFormat() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.D_FMT, l.locale) return C.GoString(p) } func (l *linuxLocale) TimeFormat() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.T_FMT, l.locale) return C.GoString(p) } func (l *linuxLocale) AM() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.AM_STR, l.locale) return C.GoString(p) } func (l *linuxLocale) PM() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.PM_STR, l.locale) return C.GoString(p) } func (l *linuxLocale) TimeAMPMFormat() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.T_FMT_AMPM, l.locale) return C.GoString(p) } var days = []C.nl_item{C.DAY_1, C.DAY_2, C.DAY_3, C.DAY_4, C.DAY_5, C.DAY_6, C.DAY_7} func (l *linuxLocale) Day(day time.Weekday) string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(days[day], l.locale) return C.GoString(p) } var shortDays = []C.nl_item{C.ABDAY_1, C.ABDAY_2, C.ABDAY_3, C.ABDAY_4, C.ABDAY_5, C.ABDAY_6, C.ABDAY_7} func (l *linuxLocale) ShortDay(day time.Weekday) string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(shortDays[day], l.locale) return C.GoString(p) } var months = []C.nl_item{C.MON_1, C.MON_2, C.MON_3, C.MON_4, C.MON_5, C.MON_6, C.MON_7, C.MON_8, C.MON_9, C.MON_10, C.MON_11, C.MON_12} func (l *linuxLocale) Month(month time.Month) string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(months[month-1], l.locale) return C.GoString(p) } var shortMonths = []C.nl_item{C.ABMON_1, C.ABMON_2, C.ABMON_3, C.ABMON_4, C.ABMON_5, C.ABMON_6, C.ABMON_7, C.ABMON_8, C.ABMON_9, C.ABMON_10, C.ABMON_11, C.ABMON_12} func (l *linuxLocale) ShortMonth(month time.Month) string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(shortMonths[month-1], l.locale) return C.GoString(p) } func (l *linuxLocale) Radix() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.RADIXCHAR, l.locale) return C.GoString(p) } func (l *linuxLocale) ThousandSeparator() string { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.THOUSEP, l.locale) return C.GoString(p) } func (l *linuxLocale) Currency() (symbol string, position CurrencyFormat) { l.l.Lock() defer l.l.Unlock() p := C.nl_langinfo_l(C.CRNCYSTR, l.locale) v := C.GoString(p) if len(v) > 0 { switch v[0] { case '-': position = BeforeValue case '+': position = AfterValue case '.': position = ReplaceRadix } symbol = v[1:] } return } golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/localeinfo_stub.go000066400000000000000000000020731463061473100265470ustar00rootroot00000000000000//go:build !cgo || (!linux && !windows) package localeinfo import ( "time" ) var _ Locale = &stubLocale{} type stubLocale struct { } func NewLocale(name string) (Locale, error) { return &stubLocale{}, nil } func (l *stubLocale) Encoding() string { return "" } func (l *stubLocale) DateTimeFormat() string { return "" } func (l *stubLocale) DateFormat() string { return "" } func (l *stubLocale) TimeFormat() string { return "" } func (l *stubLocale) AM() string { return "" } func (l *stubLocale) PM() string { return "" } func (l *stubLocale) TimeAMPMFormat() string { return "" } func (l *stubLocale) Day(day time.Weekday) string { return "" } func (l *stubLocale) ShortDay(day time.Weekday) string { return "" } func (l *stubLocale) Month(month time.Month) string { return "" } func (l *stubLocale) ShortMonth(month time.Month) string { return "" } func (l *stubLocale) Radix() string { return "" } func (l *stubLocale) ThousandSeparator() string { return "" } func (l *stubLocale) Currency() (symbol string, position CurrencyFormat) { return } golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/localeinfo_test.go000066400000000000000000000020401463061473100265430ustar00rootroot00000000000000package localeinfo import ( "testing" "time" ) func assert(t *testing.T, got string, want string, fmt string) { if want == got { return } t.Errorf(fmt+": want: %v: got: %v", want, got) t.FailNow() } func TestDateFmt(t *testing.T) { l, err := NewLocale("C.UTF-8") if err != nil { t.Errorf("NewLocale: %v", err) return } assert(t, l.DateFormat(), "%m/%d/%y", "DateFormat") assert(t, l.DateTimeFormat(), "%a %b %e %H:%M:%S %Y", "DateTimeFormat") assert(t, l.Month(time.February), "February", "Month") assert(t, l.Day(time.Friday), "Friday", "Day") assert(t, l.AM(), "AM", "AM") assert(t, l.PM(), "PM", "PM") s, _ := l.Currency() assert(t, s, "", "Currency") assert(t, l.Encoding(), "UTF-8", "Encoding") assert(t, l.Radix(), ".", "Radix") assert(t, l.ThousandSeparator(), "", "ThousandSeparator") assert(t, l.TimeFormat(), "%H:%M:%S", "TimeFormat") assert(t, l.TimeAMPMFormat(), "%I:%M:%S %p", "TimeAMPMFormat") assert(t, l.ShortDay(time.Wednesday), "Wed", "ShortDay") assert(t, l.ShortMonth(time.July), "Jul", "ShortMonth") } golang-github-delthas-go-localeinfo-0.0~git20240607.b2e834f/localeinfo_windows.go000066400000000000000000000141321463061473100272630ustar00rootroot00000000000000//go:build cgo package localeinfo /* #define WIN32_LEAN_AND_MEAN #include */ import "C" import ( "fmt" "reflect" "runtime" "strings" "sync" "time" "unicode/utf16" "unsafe" ) var _ Locale = &windowsLocale{} type windowsLocale struct { l sync.Mutex buf *uint16 locale *C.ushort } const bufSize = 1024 func NewLocale(name string) (Locale, error) { var cLocale *uint16 if name == "" { // LOCALE_NAME_USER_DEFAULT is NULL cLocale = nil } else { nameUTF16 := utf16.Encode([]rune(name + "\x00")) cLocale = &nameUTF16[0] } buf := make([]uint16, bufSize) return &windowsLocale{ buf: &buf[0], locale: (*C.ushort)(unsafe.Pointer(cLocale)), }, nil } func (l *windowsLocale) decode(n int) string { if n == 0 { return "" } var data []uint16 sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) sh.Data = uintptr(unsafe.Pointer(l.buf)) sh.Len = n - 1 sh.Cap = n - 1 s := string(utf16.Decode(data)) runtime.KeepAlive(l.buf) return s } func (l *windowsLocale) localeInfo(t C.ulong) string { l.l.Lock() defer l.l.Unlock() n, err := C.GetLocaleInfoEx(l.locale, t, (*C.ushort)(unsafe.Pointer(l.buf)), bufSize) if err != nil { return "" } return l.decode(int(n)) } func (l *windowsLocale) localeInfoInt(t C.ulong, def int) int { var n int32 _, err := C.GetLocaleInfoEx(l.locale, t|C.LOCALE_RETURN_NUMBER, (*C.ushort)(unsafe.Pointer(&n)), 2) if err != nil { return def } return int(n) } func convertDateTimeFormat(fmt string) string { var sb strings.Builder sb.Grow(len(fmt)) var current = '\x00' var count = 0 escape := -1 for i, c := range fmt + "\x00" { if escape >= 0 && c != '\x00' { if c == '\'' { if escape == i-1 { escape = -1 sb.WriteRune(c) continue } escape = -1 continue } if c == '%' { sb.WriteRune('%') } sb.WriteRune(c) continue } if count > 0 && c == current { count++ continue } if count > 0 { if current == 'h' { sb.WriteString("%I") } else if current == 'H' { sb.WriteString("%H") } else if current == 'm' { sb.WriteString("%M") } else if current == 's' { sb.WriteString("%S") } else if current == 't' { sb.WriteString("%p") } else if current == 'd' && count == 1 { // zero-padding is better than space-padding when we want no padding // otherwise we would get eg 01/ 1 sb.WriteString("%d") } else if current == 'd' && count == 2 { sb.WriteString("%d") } else if current == 'd' && count == 3 { sb.WriteString("%a") } else if current == 'd' && count == 4 { sb.WriteString("%A") } else if current == 'M' && count <= 2 { sb.WriteString("%m") } else if current == 'M' && count == 3 { sb.WriteString("%b") } else if current == 'M' && count == 4 { sb.WriteString("%B") } else if current == 'y' && count <= 2 { sb.WriteString("%y") } else if current == 'y' && count >= 4 { sb.WriteString("%Y") } count = 0 } switch c { case '\x00': return sb.String() case '\'': escape = i case 'd', 'M', 'y', 'h', 'H', 'm', 's', 't': current = c count = 1 case '%': sb.WriteString("%%") default: sb.WriteRune(c) } } panic("unreachable") } func (l *windowsLocale) Encoding() string { return l.localeInfo(C.LOCALE_SNAME) } func (l *windowsLocale) DateTimeFormat() string { // Best-effort, because there is no equivalent of %c. // ReactOS outputs date and time separated by a space, let's do that too. d := l.DateFormat() t := l.TimeFormat() if d == "" || t == "" { return "" } return fmt.Sprintf("%s %s", d, t) } func (l *windowsLocale) DateFormat() string { // https://learn.microsoft.com/en-us/windows/win32/intl/day--month--year--and-era-format-pictures return convertDateTimeFormat(l.localeInfo(C.LOCALE_SSHORTDATE)) } func (l *windowsLocale) TimeFormat() string { // https://learn.microsoft.com/en-us/windows/win32/intl/hour--minute--and-second-format-pictures return convertDateTimeFormat(l.localeInfo(C.LOCALE_STIMEFORMAT)) } func (l *windowsLocale) AM() string { return l.localeInfo(C.LOCALE_S1159) } func (l *windowsLocale) PM() string { return l.localeInfo(C.LOCALE_S2359) } func (l *windowsLocale) TimeAMPMFormat() string { // Best-effort: use TimeFormat return l.TimeFormat() } var days = []C.ulong{C.LOCALE_SDAYNAME7, C.LOCALE_SDAYNAME1, C.LOCALE_SDAYNAME2, C.LOCALE_SDAYNAME3, C.LOCALE_SDAYNAME4, C.LOCALE_SDAYNAME5, C.LOCALE_SDAYNAME6} func (l *windowsLocale) Day(day time.Weekday) string { return l.localeInfo(days[day]) } var shortDays = []C.ulong{C.LOCALE_SABBREVDAYNAME7, C.LOCALE_SABBREVDAYNAME1, C.LOCALE_SABBREVDAYNAME2, C.LOCALE_SABBREVDAYNAME3, C.LOCALE_SABBREVDAYNAME4, C.LOCALE_SABBREVDAYNAME5, C.LOCALE_SABBREVDAYNAME6} func (l *windowsLocale) ShortDay(day time.Weekday) string { return l.localeInfo(shortDays[day]) } var months = []C.ulong{C.LOCALE_SMONTHNAME1, C.LOCALE_SMONTHNAME2, C.LOCALE_SMONTHNAME3, C.LOCALE_SMONTHNAME4, C.LOCALE_SMONTHNAME5, C.LOCALE_SMONTHNAME6, C.LOCALE_SMONTHNAME7, C.LOCALE_SMONTHNAME8, C.LOCALE_SMONTHNAME9, C.LOCALE_SMONTHNAME10, C.LOCALE_SMONTHNAME11, C.LOCALE_SMONTHNAME12} func (l *windowsLocale) Month(month time.Month) string { return l.localeInfo(months[month-1]) } var shortMonths = []C.ulong{C.LOCALE_SABBREVMONTHNAME1, C.LOCALE_SABBREVMONTHNAME2, C.LOCALE_SABBREVMONTHNAME3, C.LOCALE_SABBREVMONTHNAME4, C.LOCALE_SABBREVMONTHNAME5, C.LOCALE_SABBREVMONTHNAME6, C.LOCALE_SABBREVMONTHNAME7, C.LOCALE_SABBREVMONTHNAME8, C.LOCALE_SABBREVMONTHNAME9, C.LOCALE_SABBREVMONTHNAME10, C.LOCALE_SABBREVMONTHNAME11, C.LOCALE_SABBREVMONTHNAME12} func (l *windowsLocale) ShortMonth(month time.Month) string { return l.localeInfo(shortMonths[month-1]) } func (l *windowsLocale) Radix() string { return l.localeInfo(C.LOCALE_SDECIMAL) } func (l *windowsLocale) ThousandSeparator() string { return l.localeInfo(C.LOCALE_STHOUSAND) } func (l *windowsLocale) Currency() (symbol string, position CurrencyFormat) { symbol = l.localeInfo(C.LOCALE_SCURRENCY) switch l.localeInfoInt(C.LOCALE_ICURRENCY, 0) { case 0: position = BeforeValue case 1: position = AfterValue case 2: position = BeforeValueSpace case 3: position = AfterValueSpace } return }