service/vendor/github.com/lestrrat-go/strftime/specifications.go

153 lines
3.5 KiB
Go

package strftime
import (
"fmt"
"sync"
"github.com/lestrrat-go/strftime/internal/errors"
)
// because there is no such thing was a sync.RWLocker
type rwLocker interface {
RLock()
RUnlock()
sync.Locker
}
// SpecificationSet is a container for patterns that Strftime uses.
// If you want a custom strftime, you can copy the default
// SpecificationSet and tweak it
type SpecificationSet interface {
Lookup(byte) (Appender, error)
Delete(byte) error
Set(byte, Appender) error
}
type specificationSet struct {
mutable bool
lock rwLocker
store map[byte]Appender
}
// The default specification set does not need any locking as it is never
// accessed from the outside, and is never mutated.
var defaultSpecificationSet SpecificationSet
func init() {
defaultSpecificationSet = newImmutableSpecificationSet()
}
func newImmutableSpecificationSet() SpecificationSet {
// Create a mutable one so that populateDefaultSpecifications work through
// its magic, then copy the associated map
// (NOTE: this is done this way because there used to be
// two struct types for specification set, united under an interface.
// it can now be removed, but we would need to change the entire
// populateDefaultSpecifications method, and I'm currently too lazy
// PRs welcome)
tmp := NewSpecificationSet()
ss := &specificationSet{
mutable: false,
lock: nil, // never used, so intentionally not initialized
store: tmp.(*specificationSet).store,
}
return ss
}
// NewSpecificationSet creates a specification set with the default specifications.
func NewSpecificationSet() SpecificationSet {
ds := &specificationSet{
mutable: true,
lock: &sync.RWMutex{},
store: make(map[byte]Appender),
}
populateDefaultSpecifications(ds)
return ds
}
var defaultSpecifications = map[byte]Appender{
'A': fullWeekDayName,
'a': abbrvWeekDayName,
'B': fullMonthName,
'b': abbrvMonthName,
'C': centuryDecimal,
'c': timeAndDate,
'D': mdy,
'd': dayOfMonthZeroPad,
'e': dayOfMonthSpacePad,
'F': ymd,
'H': twentyFourHourClockZeroPad,
'h': abbrvMonthName,
'I': twelveHourClockZeroPad,
'j': dayOfYear,
'k': twentyFourHourClockSpacePad,
'l': twelveHourClockSpacePad,
'M': minutesZeroPad,
'm': monthNumberZeroPad,
'n': newline,
'p': ampm,
'R': hm,
'r': imsp,
'S': secondsNumberZeroPad,
'T': hms,
't': tab,
'U': weekNumberSundayOrigin,
'u': weekdayMondayOrigin,
'V': weekNumberMondayOriginOneOrigin,
'v': eby,
'W': weekNumberMondayOrigin,
'w': weekdaySundayOrigin,
'X': natReprTime,
'x': natReprDate,
'Y': year,
'y': yearNoCentury,
'Z': timezone,
'z': timezoneOffset,
'%': percent,
}
func populateDefaultSpecifications(ds SpecificationSet) {
for c, handler := range defaultSpecifications {
if err := ds.Set(c, handler); err != nil {
panic(fmt.Sprintf("failed to set default specification for %c: %s", c, err))
}
}
}
func (ds *specificationSet) Lookup(b byte) (Appender, error) {
if ds.mutable {
ds.lock.RLock()
defer ds.lock.RLock()
}
v, ok := ds.store[b]
if !ok {
return nil, errors.Errorf(`lookup failed: '%%%c' was not found in specification set`, b)
}
return v, nil
}
func (ds *specificationSet) Delete(b byte) error {
if !ds.mutable {
return errors.New(`delete failed: this specification set is marked immutable`)
}
ds.lock.Lock()
defer ds.lock.Unlock()
delete(ds.store, b)
return nil
}
func (ds *specificationSet) Set(b byte, a Appender) error {
if !ds.mutable {
return errors.New(`set failed: this specification set is marked immutable`)
}
ds.lock.Lock()
defer ds.lock.Unlock()
ds.store[b] = a
return nil
}