-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathlocale.go
More file actions
222 lines (192 loc) · 6.78 KB
/
Copy pathlocale.go
File metadata and controls
222 lines (192 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package strftime
import (
"fmt"
"io"
"time"
)
// MonthNames holds twelve month names, indexed by time.Month minus one
// (January is index 0, December is index 11). It is used as the argument to
// WithMonths and WithShortMonths.
type MonthNames [12]string
// WeekdayNames holds seven weekday names, indexed by time.Weekday (Sunday is
// index 0, Saturday is index 6). It is used as the argument to WithWeekdays
// and WithShortWeekdays.
type WeekdayNames [7]string
// Locale supplies the locale-dependent strings used by the name-producing
// conversion specifiers (%A, %a, %B, %b, %h, %p). The library ships no locale
// data of its own: build a Locale for your language with NewLocale, or
// implement this interface yourself to back it with any source (a map,
// computed values, an external CLDR dataset, ...).
//
// Months are addressed by time.Month and weekdays by time.Weekday, so an
// implementation never has to worry about index conventions.
//
// Some languages (Russian, Czech, Polish, Greek, ...) inflect a month name
// depending on whether it stands alone or sits next to a day number — e.g.
// Russian "январь" (stand-alone) vs "2 января" (in a date). A Locale returns a
// single form per month, so format each grammatical context with its own
// Strftime object, each compiled with the Locale that holds the matching form.
type Locale interface {
Month(time.Month) string // full month name (%B)
ShortMonth(time.Month) string // abbreviated month name (%b, %h)
Weekday(time.Weekday) string // full weekday name (%A)
ShortWeekday(time.Weekday) string // abbreviated weekday name (%a)
Meridiem(hour int) string // AM/PM marker for the given 0-23 hour (%p)
}
// localeData is the array-backed Locale implementation produced by NewLocale
// and DefaultLocale. Its fields are unexported and never mutated after
// construction, so a Locale is safe to share across goroutines.
type localeData struct {
months [12]string
shortMonths [12]string
weekdays [7]string
shortWeekdays [7]string
meridiem [2]string
}
func (d *localeData) Month(m time.Month) string { return d.months[int(m)-1] }
func (d *localeData) ShortMonth(m time.Month) string { return d.shortMonths[int(m)-1] }
func (d *localeData) Weekday(w time.Weekday) string { return d.weekdays[int(w)] }
func (d *localeData) ShortWeekday(w time.Weekday) string { return d.shortWeekdays[int(w)] }
func (d *localeData) Meridiem(hour int) string {
if hour < 12 {
return d.meridiem[0]
}
return d.meridiem[1]
}
// englishLocaleData builds the English locale entirely from the standard
// library's time package, so no name data is hard-coded here.
func englishLocaleData() *localeData {
d := &localeData{meridiem: [2]string{"AM", "PM"}}
for m := time.January; m <= time.December; m++ {
full := m.String()
d.months[m-1] = full
d.shortMonths[m-1] = full[:3]
}
for w := time.Sunday; w <= time.Saturday; w++ {
full := w.String()
d.weekdays[w] = full
d.shortWeekdays[w] = full[:3]
}
return d
}
// DefaultLocale returns the English locale. It is the fallback for any name a
// custom Locale leaves unset, and a convenient base for NewLocale.
func DefaultLocale() Locale {
return englishLocaleData()
}
// LocaleOption configures a Locale built by NewLocale.
type LocaleOption interface {
configureLocale(*localeData)
}
type localeOptionFunc func(*localeData)
func (f localeOptionFunc) configureLocale(d *localeData) { f(d) }
// NewLocale creates a Locale from the supplied options, starting from the
// English locale. Any name an option leaves as the empty string keeps its
// English default, so a partially-specified Locale never produces blank
// output.
func NewLocale(options ...LocaleOption) Locale {
d := englishLocaleData()
for _, o := range options {
o.configureLocale(d)
}
return d
}
// WithMonths sets the full month names (%B), indexed by time.Month minus one.
func WithMonths(names MonthNames) LocaleOption {
return localeOptionFunc(func(d *localeData) { overlay12(&d.months, names) })
}
// WithShortMonths sets the abbreviated month names (%b, %h).
func WithShortMonths(names MonthNames) LocaleOption {
return localeOptionFunc(func(d *localeData) { overlay12(&d.shortMonths, names) })
}
// WithWeekdays sets the full weekday names (%A), indexed by time.Weekday.
func WithWeekdays(names WeekdayNames) LocaleOption {
return localeOptionFunc(func(d *localeData) { overlay7(&d.weekdays, names) })
}
// WithShortWeekdays sets the abbreviated weekday names (%a).
func WithShortWeekdays(names WeekdayNames) LocaleOption {
return localeOptionFunc(func(d *localeData) { overlay7(&d.shortWeekdays, names) })
}
// WithMeridiem sets the AM/PM markers (%p). An empty string keeps the English
// default for that marker.
func WithMeridiem(am, pm string) LocaleOption {
return localeOptionFunc(func(d *localeData) {
if am != "" {
d.meridiem[0] = am
}
if pm != "" {
d.meridiem[1] = pm
}
})
}
func overlay12(dst *[12]string, src MonthNames) {
for i, v := range src {
if v != "" {
dst[i] = v
}
}
}
func overlay7(dst *[7]string, src WeekdayNames) {
for i, v := range src {
if v != "" {
dst[i] = v
}
}
}
// applyLocale registers the name-producing appenders that read from loc onto
// the specification set. It is invoked before any explicit WithSpecification
// overrides, so a caller can still replace an individual specifier.
func applyLocale(ds SpecificationSet, loc Locale) error {
pairs := []struct {
b byte
a Appender
}{
{'A', weekdayNameAppender{loc: loc}},
{'a', weekdayNameAppender{loc: loc, short: true}},
{'B', monthNameAppender{loc: loc}},
{'b', monthNameAppender{loc: loc, short: true}},
{'h', monthNameAppender{loc: loc, short: true}},
{'p', meridiemAppender{loc: loc}},
}
for _, p := range pairs {
if err := ds.Set(p.b, p.a); err != nil {
return fmt.Errorf("failed to apply locale for %%%c: %w", p.b, err)
}
}
return nil
}
type monthNameAppender struct {
loc Locale
short bool
}
func (v monthNameAppender) Append(b []byte, t time.Time) []byte {
if v.short {
return append(b, v.loc.ShortMonth(t.Month())...)
}
return append(b, v.loc.Month(t.Month())...)
}
func (v monthNameAppender) dump(out io.Writer) {
fmt.Fprintf(out, "monthName(short=%t)", v.short)
}
type weekdayNameAppender struct {
loc Locale
short bool
}
func (v weekdayNameAppender) Append(b []byte, t time.Time) []byte {
if v.short {
return append(b, v.loc.ShortWeekday(t.Weekday())...)
}
return append(b, v.loc.Weekday(t.Weekday())...)
}
func (v weekdayNameAppender) dump(out io.Writer) {
fmt.Fprintf(out, "weekdayName(short=%t)", v.short)
}
type meridiemAppender struct {
loc Locale
}
func (v meridiemAppender) Append(b []byte, t time.Time) []byte {
return append(b, v.loc.Meridiem(t.Hour())...)
}
func (v meridiemAppender) dump(out io.Writer) {
fmt.Fprintf(out, "meridiem")
}