strftime.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package strftime
  2. import (
  3. "io"
  4. "strings"
  5. "sync"
  6. "time"
  7. "github.com/pkg/errors"
  8. )
  9. type compileHandler interface {
  10. handle(Appender)
  11. }
  12. // compile, and create an appender list
  13. type appenderListBuilder struct {
  14. list *combiningAppend
  15. }
  16. func (alb *appenderListBuilder) handle(a Appender) {
  17. alb.list.Append(a)
  18. }
  19. // compile, and execute the appenders on the fly
  20. type appenderExecutor struct {
  21. t time.Time
  22. dst []byte
  23. }
  24. func (ae *appenderExecutor) handle(a Appender) {
  25. ae.dst = a.Append(ae.dst, ae.t)
  26. }
  27. func compile(handler compileHandler, p string, ds SpecificationSet) error {
  28. for l := len(p); l > 0; l = len(p) {
  29. // This is a really tight loop, so we don't even calls to
  30. // Verbatim() to cuase extra stuff
  31. var verbatim verbatimw
  32. i := strings.IndexByte(p, '%')
  33. if i < 0 {
  34. verbatim.s = p
  35. handler.handle(&verbatim)
  36. // this is silly, but I don't trust break keywords when there's a
  37. // possibility of this piece of code being rearranged
  38. p = p[l:]
  39. continue
  40. }
  41. if i == l-1 {
  42. return errors.New(`stray % at the end of pattern`)
  43. }
  44. // we found a '%'. we need the next byte to decide what to do next
  45. // we already know that i < l - 1
  46. // everything up to the i is verbatim
  47. if i > 0 {
  48. verbatim.s = p[:i]
  49. handler.handle(&verbatim)
  50. p = p[i:]
  51. }
  52. specification, err := ds.Lookup(p[1])
  53. if err != nil {
  54. return errors.Wrap(err, `pattern compilation failed`)
  55. }
  56. handler.handle(specification)
  57. p = p[2:]
  58. }
  59. return nil
  60. }
  61. func getSpecificationSetFor(options ...Option) (SpecificationSet, error) {
  62. var ds SpecificationSet = defaultSpecificationSet
  63. var extraSpecifications []*optSpecificationPair
  64. for _, option := range options {
  65. switch option.Name() {
  66. case optSpecificationSet:
  67. ds = option.Value().(SpecificationSet)
  68. case optSpecification:
  69. extraSpecifications = append(extraSpecifications, option.Value().(*optSpecificationPair))
  70. }
  71. }
  72. if len(extraSpecifications) > 0 {
  73. // If ds is immutable, we're going to need to create a new
  74. // one. oh what a waste!
  75. if raw, ok := ds.(*specificationSet); ok && !raw.mutable {
  76. ds = NewSpecificationSet()
  77. }
  78. for _, v := range extraSpecifications {
  79. if err := ds.Set(v.name, v.appender); err != nil {
  80. return nil, err
  81. }
  82. }
  83. }
  84. return ds, nil
  85. }
  86. var fmtAppendExecutorPool = sync.Pool{
  87. New: func() interface{} {
  88. var h appenderExecutor
  89. h.dst = make([]byte, 0, 32)
  90. return &h
  91. },
  92. }
  93. func getFmtAppendExecutor() *appenderExecutor {
  94. return fmtAppendExecutorPool.Get().(*appenderExecutor)
  95. }
  96. func releasdeFmtAppendExecutor(v *appenderExecutor) {
  97. // TODO: should we discard the buffer if it's too long?
  98. v.dst = v.dst[:0]
  99. fmtAppendExecutorPool.Put(v)
  100. }
  101. // Format takes the format `s` and the time `t` to produce the
  102. // format date/time. Note that this function re-compiles the
  103. // pattern every time it is called.
  104. //
  105. // If you know beforehand that you will be reusing the pattern
  106. // within your application, consider creating a `Strftime` object
  107. // and reusing it.
  108. func Format(p string, t time.Time, options ...Option) (string, error) {
  109. // TODO: this may be premature optimization
  110. ds, err := getSpecificationSetFor(options...)
  111. if err != nil {
  112. return "", errors.Wrap(err, `failed to get specification set`)
  113. }
  114. h := getFmtAppendExecutor()
  115. defer releasdeFmtAppendExecutor(h)
  116. h.t = t
  117. if err := compile(h, p, ds); err != nil {
  118. return "", errors.Wrap(err, `failed to compile format`)
  119. }
  120. return string(h.dst), nil
  121. }
  122. // Strftime is the object that represents a compiled strftime pattern
  123. type Strftime struct {
  124. pattern string
  125. compiled appenderList
  126. }
  127. // New creates a new Strftime object. If the compilation fails, then
  128. // an error is returned in the second argument.
  129. func New(p string, options ...Option) (*Strftime, error) {
  130. // TODO: this may be premature optimization
  131. ds, err := getSpecificationSetFor(options...)
  132. if err != nil {
  133. return nil, errors.Wrap(err, `failed to get specification set`)
  134. }
  135. var h appenderListBuilder
  136. h.list = &combiningAppend{}
  137. if err := compile(&h, p, ds); err != nil {
  138. return nil, errors.Wrap(err, `failed to compile format`)
  139. }
  140. return &Strftime{
  141. pattern: p,
  142. compiled: h.list.list,
  143. }, nil
  144. }
  145. // Pattern returns the original pattern string
  146. func (f *Strftime) Pattern() string {
  147. return f.pattern
  148. }
  149. // Format takes the destination `dst` and time `t`. It formats the date/time
  150. // using the pre-compiled pattern, and outputs the results to `dst`
  151. func (f *Strftime) Format(dst io.Writer, t time.Time) error {
  152. const bufSize = 64
  153. var b []byte
  154. max := len(f.pattern) + 10
  155. if max < bufSize {
  156. var buf [bufSize]byte
  157. b = buf[:0]
  158. } else {
  159. b = make([]byte, 0, max)
  160. }
  161. if _, err := dst.Write(f.format(b, t)); err != nil {
  162. return err
  163. }
  164. return nil
  165. }
  166. // FormatBuffer is equivalent to Format, but appends the result directly to
  167. // supplied slice dst, returning the updated slice. This avoids any internal
  168. // memory allocation.
  169. func (f *Strftime) FormatBuffer(dst []byte, t time.Time) []byte {
  170. return f.format(dst, t)
  171. }
  172. // Dump outputs the internal structure of the formatter, for debugging purposes.
  173. // Please do NOT assume the output format to be fixed: it is expected to change
  174. // in the future.
  175. func (f *Strftime) Dump(out io.Writer) {
  176. f.compiled.dump(out)
  177. }
  178. func (f *Strftime) format(b []byte, t time.Time) []byte {
  179. for _, w := range f.compiled {
  180. b = w.Append(b, t)
  181. }
  182. return b
  183. }
  184. // FormatString takes the time `t` and formats it, returning the
  185. // string containing the formated data.
  186. func (f *Strftime) FormatString(t time.Time) string {
  187. const bufSize = 64
  188. var b []byte
  189. max := len(f.pattern) + 10
  190. if max < bufSize {
  191. var buf [bufSize]byte
  192. b = buf[:0]
  193. } else {
  194. b = make([]byte, 0, max)
  195. }
  196. return string(f.format(b, t))
  197. }