specifications.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package strftime
  2. import (
  3. "fmt"
  4. "sync"
  5. "github.com/pkg/errors"
  6. )
  7. // because there is no such thing was a sync.RWLocker
  8. type rwLocker interface {
  9. RLock()
  10. RUnlock()
  11. sync.Locker
  12. }
  13. // SpecificationSet is a container for patterns that Strftime uses.
  14. // If you want a custom strftime, you can copy the default
  15. // SpecificationSet and tweak it
  16. type SpecificationSet interface {
  17. Lookup(byte) (Appender, error)
  18. Delete(byte) error
  19. Set(byte, Appender) error
  20. }
  21. type specificationSet struct {
  22. mutable bool
  23. lock rwLocker
  24. store map[byte]Appender
  25. }
  26. // The default specification set does not need any locking as it is never
  27. // accessed from the outside, and is never mutated.
  28. var defaultSpecificationSet SpecificationSet
  29. func init() {
  30. defaultSpecificationSet = newImmutableSpecificationSet()
  31. }
  32. func newImmutableSpecificationSet() SpecificationSet {
  33. // Create a mutable one so that populateDefaultSpecifications work through
  34. // its magic, then copy the associated map
  35. // (NOTE: this is done this way because there used to be
  36. // two struct types for specification set, united under an interface.
  37. // it can now be removed, but we would need to change the entire
  38. // populateDefaultSpecifications method, and I'm currently too lazy
  39. // PRs welcome)
  40. tmp := NewSpecificationSet()
  41. ss := &specificationSet{
  42. mutable: false,
  43. lock: nil, // never used, so intentionally not initialized
  44. store: tmp.(*specificationSet).store,
  45. }
  46. return ss
  47. }
  48. // NewSpecificationSet creates a specification set with the default specifications.
  49. func NewSpecificationSet() SpecificationSet {
  50. ds := &specificationSet{
  51. mutable: true,
  52. lock: &sync.RWMutex{},
  53. store: make(map[byte]Appender),
  54. }
  55. populateDefaultSpecifications(ds)
  56. return ds
  57. }
  58. var defaultSpecifications = map[byte]Appender{
  59. 'A': fullWeekDayName,
  60. 'a': abbrvWeekDayName,
  61. 'B': fullMonthName,
  62. 'b': abbrvMonthName,
  63. 'C': centuryDecimal,
  64. 'c': timeAndDate,
  65. 'D': mdy,
  66. 'd': dayOfMonthZeroPad,
  67. 'e': dayOfMonthSpacePad,
  68. 'F': ymd,
  69. 'H': twentyFourHourClockZeroPad,
  70. 'h': abbrvMonthName,
  71. 'I': twelveHourClockZeroPad,
  72. 'j': dayOfYear,
  73. 'k': twentyFourHourClockSpacePad,
  74. 'l': twelveHourClockSpacePad,
  75. 'M': minutesZeroPad,
  76. 'm': monthNumberZeroPad,
  77. 'n': newline,
  78. 'p': ampm,
  79. 'R': hm,
  80. 'r': imsp,
  81. 'S': secondsNumberZeroPad,
  82. 'T': hms,
  83. 't': tab,
  84. 'U': weekNumberSundayOrigin,
  85. 'u': weekdayMondayOrigin,
  86. 'V': weekNumberMondayOriginOneOrigin,
  87. 'v': eby,
  88. 'W': weekNumberMondayOrigin,
  89. 'w': weekdaySundayOrigin,
  90. 'X': natReprTime,
  91. 'x': natReprDate,
  92. 'Y': year,
  93. 'y': yearNoCentury,
  94. 'Z': timezone,
  95. 'z': timezoneOffset,
  96. '%': percent,
  97. }
  98. func populateDefaultSpecifications(ds SpecificationSet) {
  99. for c, handler := range defaultSpecifications {
  100. if err := ds.Set(c, handler); err != nil {
  101. panic(fmt.Sprintf("failed to set default specification for %c: %s", c, err))
  102. }
  103. }
  104. }
  105. func (ds *specificationSet) Lookup(b byte) (Appender, error) {
  106. if ds.mutable {
  107. ds.lock.RLock()
  108. defer ds.lock.RLock()
  109. }
  110. v, ok := ds.store[b]
  111. if !ok {
  112. return nil, errors.Errorf(`lookup failed: '%%%c' was not found in specification set`, b)
  113. }
  114. return v, nil
  115. }
  116. func (ds *specificationSet) Delete(b byte) error {
  117. if !ds.mutable {
  118. return errors.New(`delete failed: this specification set is marked immutable`)
  119. }
  120. ds.lock.Lock()
  121. defer ds.lock.Unlock()
  122. delete(ds.store, b)
  123. return nil
  124. }
  125. func (ds *specificationSet) Set(b byte, a Appender) error {
  126. if !ds.mutable {
  127. return errors.New(`set failed: this specification set is marked immutable`)
  128. }
  129. ds.lock.Lock()
  130. defer ds.lock.Unlock()
  131. ds.store[b] = a
  132. return nil
  133. }