123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- package strftime
- import (
- "io"
- "strings"
- "sync"
- "time"
- "github.com/pkg/errors"
- )
- type compileHandler interface {
- handle(Appender)
- }
- // compile, and create an appender list
- type appenderListBuilder struct {
- list *combiningAppend
- }
- func (alb *appenderListBuilder) handle(a Appender) {
- alb.list.Append(a)
- }
- // compile, and execute the appenders on the fly
- type appenderExecutor struct {
- t time.Time
- dst []byte
- }
- func (ae *appenderExecutor) handle(a Appender) {
- ae.dst = a.Append(ae.dst, ae.t)
- }
- func compile(handler compileHandler, p string, ds SpecificationSet) error {
- for l := len(p); l > 0; l = len(p) {
- // This is a really tight loop, so we don't even calls to
- // Verbatim() to cuase extra stuff
- var verbatim verbatimw
- i := strings.IndexByte(p, '%')
- if i < 0 {
- verbatim.s = p
- handler.handle(&verbatim)
- // this is silly, but I don't trust break keywords when there's a
- // possibility of this piece of code being rearranged
- p = p[l:]
- continue
- }
- if i == l-1 {
- return errors.New(`stray % at the end of pattern`)
- }
- // we found a '%'. we need the next byte to decide what to do next
- // we already know that i < l - 1
- // everything up to the i is verbatim
- if i > 0 {
- verbatim.s = p[:i]
- handler.handle(&verbatim)
- p = p[i:]
- }
- specification, err := ds.Lookup(p[1])
- if err != nil {
- return errors.Wrap(err, `pattern compilation failed`)
- }
- handler.handle(specification)
- p = p[2:]
- }
- return nil
- }
- func getSpecificationSetFor(options ...Option) (SpecificationSet, error) {
- var ds SpecificationSet = defaultSpecificationSet
- var extraSpecifications []*optSpecificationPair
- for _, option := range options {
- switch option.Name() {
- case optSpecificationSet:
- ds = option.Value().(SpecificationSet)
- case optSpecification:
- extraSpecifications = append(extraSpecifications, option.Value().(*optSpecificationPair))
- }
- }
- if len(extraSpecifications) > 0 {
- // If ds is immutable, we're going to need to create a new
- // one. oh what a waste!
- if raw, ok := ds.(*specificationSet); ok && !raw.mutable {
- ds = NewSpecificationSet()
- }
- for _, v := range extraSpecifications {
- if err := ds.Set(v.name, v.appender); err != nil {
- return nil, err
- }
- }
- }
- return ds, nil
- }
- var fmtAppendExecutorPool = sync.Pool{
- New: func() interface{} {
- var h appenderExecutor
- h.dst = make([]byte, 0, 32)
- return &h
- },
- }
- func getFmtAppendExecutor() *appenderExecutor {
- return fmtAppendExecutorPool.Get().(*appenderExecutor)
- }
- func releasdeFmtAppendExecutor(v *appenderExecutor) {
- // TODO: should we discard the buffer if it's too long?
- v.dst = v.dst[:0]
- fmtAppendExecutorPool.Put(v)
- }
- // Format takes the format `s` and the time `t` to produce the
- // format date/time. Note that this function re-compiles the
- // pattern every time it is called.
- //
- // If you know beforehand that you will be reusing the pattern
- // within your application, consider creating a `Strftime` object
- // and reusing it.
- func Format(p string, t time.Time, options ...Option) (string, error) {
- // TODO: this may be premature optimization
- ds, err := getSpecificationSetFor(options...)
- if err != nil {
- return "", errors.Wrap(err, `failed to get specification set`)
- }
- h := getFmtAppendExecutor()
- defer releasdeFmtAppendExecutor(h)
- h.t = t
- if err := compile(h, p, ds); err != nil {
- return "", errors.Wrap(err, `failed to compile format`)
- }
- return string(h.dst), nil
- }
- // Strftime is the object that represents a compiled strftime pattern
- type Strftime struct {
- pattern string
- compiled appenderList
- }
- // New creates a new Strftime object. If the compilation fails, then
- // an error is returned in the second argument.
- func New(p string, options ...Option) (*Strftime, error) {
- // TODO: this may be premature optimization
- ds, err := getSpecificationSetFor(options...)
- if err != nil {
- return nil, errors.Wrap(err, `failed to get specification set`)
- }
- var h appenderListBuilder
- h.list = &combiningAppend{}
- if err := compile(&h, p, ds); err != nil {
- return nil, errors.Wrap(err, `failed to compile format`)
- }
- return &Strftime{
- pattern: p,
- compiled: h.list.list,
- }, nil
- }
- // Pattern returns the original pattern string
- func (f *Strftime) Pattern() string {
- return f.pattern
- }
- // Format takes the destination `dst` and time `t`. It formats the date/time
- // using the pre-compiled pattern, and outputs the results to `dst`
- func (f *Strftime) Format(dst io.Writer, t time.Time) error {
- const bufSize = 64
- var b []byte
- max := len(f.pattern) + 10
- if max < bufSize {
- var buf [bufSize]byte
- b = buf[:0]
- } else {
- b = make([]byte, 0, max)
- }
- if _, err := dst.Write(f.format(b, t)); err != nil {
- return err
- }
- return nil
- }
- // FormatBuffer is equivalent to Format, but appends the result directly to
- // supplied slice dst, returning the updated slice. This avoids any internal
- // memory allocation.
- func (f *Strftime) FormatBuffer(dst []byte, t time.Time) []byte {
- return f.format(dst, t)
- }
- // Dump outputs the internal structure of the formatter, for debugging purposes.
- // Please do NOT assume the output format to be fixed: it is expected to change
- // in the future.
- func (f *Strftime) Dump(out io.Writer) {
- f.compiled.dump(out)
- }
- func (f *Strftime) format(b []byte, t time.Time) []byte {
- for _, w := range f.compiled {
- b = w.Append(b, t)
- }
- return b
- }
- // FormatString takes the time `t` and formats it, returning the
- // string containing the formated data.
- func (f *Strftime) FormatString(t time.Time) string {
- const bufSize = 64
- var b []byte
- max := len(f.pattern) + 10
- if max < bufSize {
- var buf [bufSize]byte
- b = buf[:0]
- } else {
- b = make([]byte, 0, max)
- }
- return string(f.format(b, t))
- }
|