strftime

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 21, 2026 License: MIT Imports: 9 Imported by: 272

README

strftime

Fast strftime for Go

Go Reference

SYNOPSIS

f, err := strftime.New(`.... pattern ...`)
if err := f.Format(buf, time.Now()); err != nil {
    log.Println(err.Error())
}

DESCRIPTION

The goals for this library are

  • Optimized for the same pattern being called repeatedly
  • Be flexible about destination to write the results out
  • Be as complete as possible in terms of conversion specifications

API

Format(string, time.Time) (string, error)

Takes the pattern and the time, and formats it. This function is a utility function that recompiles the pattern every time the function is called. If you know beforehand that you will be formatting the same pattern multiple times, consider using New to create a Strftime object and reuse it.

New(string) (*Strftime, error)

Takes the pattern and creates a new Strftime object.

obj.Pattern() string

Returns the pattern string used to create this Strftime object

obj.Format(io.Writer, time.Time) error

Formats the time according to the pre-compiled pattern, and writes the result to the specified io.Writer

obj.FormatString(time.Time) string

Formats the time according to the pre-compiled pattern, and returns the result string.

SUPPORTED CONVERSION SPECIFICATIONS

pattern description
%A national representation of the full weekday name
%a national representation of the abbreviated weekday
%B national representation of the full month name
%b national representation of the abbreviated month name
%C (year / 100) as decimal number; single digits are preceded by a zero
%c national representation of time and date
%D equivalent to %m/%d/%y
%d day of the month as a decimal number (01-31)
%e the day of the month as a decimal number (1-31); single digits are preceded by a blank
%F equivalent to %Y-%m-%d
%G the ISO week year with century as a decimal number with 4 digits
%g the ISO week year without century as a decimal number (00-99) with 2 digits
%H the hour (24-hour clock) as a decimal number (00-23)
%h same as %b
%I the hour (12-hour clock) as a decimal number (01-12)
%j the day of the year as a decimal number (001-366)
%k the hour (24-hour clock) as a decimal number (0-23); single digits are preceded by a blank
%l the hour (12-hour clock) as a decimal number (1-12); single digits are preceded by a blank
%M the minute as a decimal number (00-59)
%m the month as a decimal number (01-12)
%n a newline
%p national representation of either "ante meridiem" (a.m.) or "post meridiem" (p.m.) as appropriate.
%R equivalent to %H:%M
%r equivalent to %I:%M:%S %p
%S the second as a decimal number (00-60)
%T equivalent to %H:%M:%S
%t a tab
%U the week number of the year (Sunday as the first day of the week) as a decimal number (00-53)
%u the weekday (Monday as the first day of the week) as a decimal number (1-7)
%V the week number of the year (Monday as the first day of the week) as a decimal number (01-53)
%v equivalent to %e-%b-%Y
%W the week number of the year (Monday as the first day of the week) as a decimal number (00-53)
%w the weekday (Sunday as the first day of the week) as a decimal number (0-6)
%X national representation of the time
%x national representation of the date
%Y the year with century as a decimal number
%y the year without century as a decimal number (00-99)
%Z the time zone name
%z the time zone offset from UTC
%% a '%'

NO-PADDING FLAG

A - (glibc) or # (Windows) flag may be placed between the % and the conversion specifier to suppress the leading zero/blank padding on numeric fields. For example, given 2006-01-02 03:04:05:

pattern result
%m 01
%-m 1
%d 02
%-d 2
%H:%M 03:04
%-H:%-M 3:4

The flag has no effect on non-numeric fields (e.g. %-A is identical to %A).

LOCALIZATION

By default the name-producing specifiers (%A, %a, %B, %b, %h, %p) emit English. To localize them, build a Locale with NewLocale and pass it via WithLocale. The library ships no locale data of its own — you supply the names for your language:

french := strftime.NewLocale(
  strftime.WithMonths(strftime.MonthNames{
    "janvier", "février", "mars", "avril", "mai", "juin",
    "juillet", "août", "septembre", "octobre", "novembre", "décembre",
  }),
  strftime.WithWeekdays(strftime.WeekdayNames{
    "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi",
  }),
  // WithShortMonths, WithShortWeekdays, WithMeridiem ... optional
)

s, _ := strftime.New(`%A %d %B %Y`, strftime.WithLocale(french))
// -> "lundi 02 janvier 2006"

MonthNames is indexed by month minus one (January is index 0); WeekdayNames is indexed by time.Weekday (Sunday is index 0). Any name left empty falls back to the English default, so a partial Locale never yields blank output. Numeric specifiers (%d, %m, %Y, ...) are locale-invariant and unaffected.

Locale is an interface, so you can also implement it yourself to back the names with a map, computed values, or an external dataset. DefaultLocale() returns the English implementation.

Inflected languages

In some languages (Russian, Czech, Polish, Greek, ...) a month name changes form depending on whether it stands alone or appears next to a day number — e.g. Russian "январь" (stand-alone) vs "2 января" (in a date). Because a single Locale carries one form per name, format each context with its own compiled Strftime:

inDate, _   := strftime.New(`%d %B %Y`, strftime.WithLocale(ruInDate))   // января
header, _   := strftime.New(`%B %Y`,    strftime.WithLocale(ruStandalone)) // январь

EXTENSIONS / CUSTOM SPECIFICATIONS

This library in general tries to be POSIX compliant, but sometimes you just need that extra specification or two that is relatively widely used but is not included in the POSIX specification.

For example, POSIX does not specify how to print out milliseconds, but popular implementations allow %f or %L to achieve this.

For those instances, strftime.Strftime can be configured to use a custom set of specifications:

ss := strftime.NewSpecificationSet()
ss.Set('L', ...) // provide implementation for `%L`

// pass this new specification set to the strftime instance
p, err := strftime.New(`%L`, strftime.WithSpecificationSet(ss))
p.Format(..., time.Now())

The implementation must implement the Appender interface, which is

type Appender interface {
  Append([]byte, time.Time) []byte
}

For commonly used extensions such as the millisecond example and Unix timestamp, we provide a default implementation so the user can do one of the following:

// (1) Pass a specification byte and the Appender
//     This allows you to pass arbitrary Appenders
p, err := strftime.New(
  `%L`,
  strftime.WithSpecification('L', strftime.Milliseconds),
)

// (2) Pass an option that knows to use strftime.Milliseconds
p, err := strftime.New(
  `%L`,
  strftime.WithMilliseconds('L'),
)

Similarly for Unix Timestamp:

// (1) Pass a specification byte and the Appender
//     This allows you to pass arbitrary Appenders
p, err := strftime.New(
  `%s`,
  strftime.WithSpecification('s', strftime.UnixSeconds),
)

// (2) Pass an option that knows to use strftime.UnixSeconds
p, err := strftime.New(
  `%s`,
  strftime.WithUnixSeconds('s'),
)

If a common specification is missing, please feel free to submit a PR (but please be sure to be able to defend how "common" it is)

List of available extensions

PERFORMANCE / OTHER LIBRARIES

The benchmarks live under bench/ and compare this library against several others.

// AMD Ryzen 9 7900X3D, Linux/amd64
// go version go1.26.1 linux/amd64
% go test -benchmem -bench .
goos: linux
goarch: amd64
pkg: github.com/lestrrat-go/strftime/bench
cpu: AMD Ryzen 9 7900X3D 12-Core Processor
BenchmarkTebeka-24                        	  728451	      1458 ns/op	     260 B/op	      20 allocs/op
BenchmarkJehiah-24                        	 1898193	       622.1 ns/op	     256 B/op	      17 allocs/op
BenchmarkFastly-24                        	 1356129	       881.0 ns/op	     168 B/op	       6 allocs/op
BenchmarkNcruces-24                       	 5115555	       230.7 ns/op	      64 B/op	       1 allocs/op
BenchmarkNcrucesAppend-24                 	 6263023	       199.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkLestrrat-24                      	 5860896	       206.4 ns/op	     128 B/op	       2 allocs/op
BenchmarkLestrratCachedString-24          	 6105082	       189.2 ns/op	     128 B/op	       2 allocs/op
BenchmarkLestrratCachedWriter-24          	 6648992	       168.7 ns/op	      64 B/op	       1 allocs/op
BenchmarkLestrratCachedFormatBuffer-24    	 8669540	       136.7 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/lestrrat-go/strftime/bench	13.281s

This library is the fastest of the bunch across every access pattern. The annotated list below ranks the relevant variants from fastest to slowest:

Import Path ns/op allocs Note
github.com/lestrrat-go/strftime 136.7 0 FormatBuffer() into a reused slice (cached)
github.com/lestrrat-go/strftime 168.7 1 Format() to an io.Writer (cached)
github.com/lestrrat-go/strftime 189.2 2 FormatString() (cached)
github.com/ncruces/go-strftime 199.2 0 AppendFormat()
github.com/lestrrat-go/strftime 206.4 2 package-level Format() (compiled patterns are cached)
github.com/ncruces/go-strftime 230.7 1 Format()
github.com/jehiah/go-strftime 622.1 17
github.com/fastly/go-utils/strftime 881.0 6
github.com/tebeka/strftime 1458 20

The fastest path is reusing a Strftime object and appending into a slice you own (FormatBuffer), which allocates nothing. The package-level Format() caches compiled patterns internally (bounded), so even repeated one-off calls with the same pattern stay fast.

However, depending on your pattern, this speed may vary. If you find a particular pattern that seems sluggish, please send in patches or tests.

Please also note that this benchmark only uses the subset of conversion specifications that are supported by ALL of the libraries compared.

Somethings to consider when making performance comparisons in the future:

  • Can it write to io.Writer?
  • Which %specification does it handle?

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Format

func Format(p string, t time.Time, options ...Option) (string, error)

Format takes the format `s` and the time `t` to produce the format date/time.

When called without options, compiled patterns are cached (up to an internal limit) so that repeated calls with the same pattern avoid recompilation. Calls that pass options always compile on the fly.

If you know beforehand that you will be reusing the pattern within your application, consider creating a `Strftime` object and reusing it.

Types

type AppendFunc

type AppendFunc func([]byte, time.Time) []byte

AppendFunc is an utility type to allow users to create a function-only version of an Appender

func (AppendFunc) Append

func (af AppendFunc) Append(b []byte, t time.Time) []byte

type Appender

type Appender interface {
	Append([]byte, time.Time) []byte
}

Appender is the interface that must be fulfilled by components that implement the translation of specifications to actual time value.

The Append method takes the accumulated byte buffer, and the time to use to generate the textual representation. The resulting byte sequence must be returned by this method, normally by using the append() builtin function.

func Microseconds added in v1.0.2

func Microseconds() Appender

Microseconds returns the Appender suitable for creating a zero-padded, 6-digit microsecond textual representation.

func Milliseconds

func Milliseconds() Appender

Milliseconds returns the Appender suitable for creating a zero-padded, 3-digit millisecond textual representation.

func StdlibFormat

func StdlibFormat(s string) Appender

StdlibFormat returns an Appender that simply goes through `time.Format()` For example, if you know you want to display the abbreviated month name for %b, you can create a StdlibFormat with the pattern `Jan` and register that for specification `b`:

a := StdlibFormat(`Jan`) ss := NewSpecificationSet() ss.Set('b', a) // does %b -> abbreviated month name

func UnixSeconds added in v1.0.2

func UnixSeconds() Appender

UnixSeconds returns the Appender suitable for creating unix timestamp textual representation.

func Verbatim

func Verbatim(s string) Appender

Verbatim returns an Appender suitable for generating static text. For static text, this method is slightly favorable than creating your own appender, as adjacent verbatim blocks will be combined at compile time to produce more efficient Appenders

type Locale added in v1.2.0

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)
}

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.

func DefaultLocale added in v1.2.0

func DefaultLocale() Locale

DefaultLocale returns the English locale. It is the fallback for any name a custom Locale leaves unset, and a convenient base for NewLocale.

func NewLocale added in v1.2.0

func NewLocale(options ...LocaleOption) Locale

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.

type LocaleOption added in v1.2.0

type LocaleOption interface {
	// contains filtered or unexported methods
}

LocaleOption configures a Locale built by NewLocale.

func WithMeridiem added in v1.2.0

func WithMeridiem(am, pm string) LocaleOption

WithMeridiem sets the AM/PM markers (%p). An empty string keeps the English default for that marker.

func WithMonths added in v1.2.0

func WithMonths(names MonthNames) LocaleOption

WithMonths sets the full month names (%B), indexed by time.Month minus one.

func WithShortMonths added in v1.2.0

func WithShortMonths(names MonthNames) LocaleOption

WithShortMonths sets the abbreviated month names (%b, %h).

func WithShortWeekdays added in v1.2.0

func WithShortWeekdays(names WeekdayNames) LocaleOption

WithShortWeekdays sets the abbreviated weekday names (%a).

func WithWeekdays added in v1.2.0

func WithWeekdays(names WeekdayNames) LocaleOption

WithWeekdays sets the full weekday names (%A), indexed by time.Weekday.

type MonthNames added in v1.2.0

type MonthNames [12]string

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 Option

type Option interface {
	Name() string
	Value() any
}

func WithLocale added in v1.2.0

func WithLocale(loc Locale) Option

WithLocale overrides the name-producing conversion specifiers (%A, %a, %B, %b, %h, %p) with the localized names supplied by loc, which is typically built with NewLocale. Numeric specifiers (%d, %m, %Y, ...) are locale-invariant and are unaffected.

Because a Locale supplies one form per name, format a context that needs inflected month names (e.g. "%d %B" vs "%B %Y" in Slavic languages) by compiling a separate Strftime object per context, each with the Locale holding the appropriate form. See the Locale documentation for details.

func WithMicroseconds added in v1.0.2

func WithMicroseconds(b byte) Option

WithMicroseconds is similar to WithSpecification, and specifies that the Strftime object should interpret the pattern `%b` (where b is the byte that you specify as the argument) as the zero-padded, 3 letter microseconds of the time.

func WithMilliseconds

func WithMilliseconds(b byte) Option

WithMilliseconds is similar to WithSpecification, and specifies that the Strftime object should interpret the pattern `%b` (where b is the byte that you specify as the argument) as the zero-padded, 3 letter milliseconds of the time.

func WithSpecification

func WithSpecification(b byte, a Appender) Option

WithSpecification allows you to create a new specification set on the fly, to be used only for that invocation.

func WithSpecificationSet

func WithSpecificationSet(ds SpecificationSet) Option

WithSpecificationSet allows you to specify a custom specification set

func WithUnixSeconds added in v1.0.2

func WithUnixSeconds(b byte) Option

WithUnixSeconds is similar to WithSpecification, and specifies that the Strftime object should interpret the pattern `%b` (where b is the byte that you specify as the argument) as the unix timestamp in seconds

type SpecificationSet

type SpecificationSet interface {
	Lookup(byte) (Appender, error)
	Delete(byte) error
	Set(byte, Appender) error
}

SpecificationSet is a container for patterns that Strftime uses. If you want a custom strftime, you can copy the default SpecificationSet and tweak it

Example
package main

import (
	"fmt"
	"os"
	"time"

	"github.com/lestrrat-go/strftime"
)

var ref = time.Unix(1136239445, 123456789).UTC()

func main() {
	{
		// I want %L as milliseconds!
		p, err := strftime.New(`%L`, strftime.WithMilliseconds('L'))
		if err != nil {
			fmt.Println(err)
			return
		}
		p.Format(os.Stdout, ref)
		os.Stdout.Write([]byte{'\n'})
	}

	{
		// I want %f as milliseconds!
		p, err := strftime.New(`%f`, strftime.WithMilliseconds('f'))
		if err != nil {
			fmt.Println(err)
			return
		}
		p.Format(os.Stdout, ref)
		os.Stdout.Write([]byte{'\n'})
	}

	{
		// I want %X to print out my name!
		a := strftime.Verbatim(`Daisuke Maki`)
		p, err := strftime.New(`%X`, strftime.WithSpecification('X', a))
		if err != nil {
			fmt.Println(err)
			return
		}
		p.Format(os.Stdout, ref)
		os.Stdout.Write([]byte{'\n'})
	}

	{
		// I want a completely new specification set, and I want %X to print out my name!
		a := strftime.Verbatim(`Daisuke Maki`)

		ds := strftime.NewSpecificationSet()
		ds.Set('X', a)
		p, err := strftime.New(`%X`, strftime.WithSpecificationSet(ds))
		if err != nil {
			fmt.Println(err)
			return
		}
		p.Format(os.Stdout, ref)
		os.Stdout.Write([]byte{'\n'})
	}

	{
		// I want %s as unix timestamp!
		p, err := strftime.New(`%s`, strftime.WithUnixSeconds('s'))
		if err != nil {
			fmt.Println(err)
			return
		}
		p.Format(os.Stdout, ref)
		os.Stdout.Write([]byte{'\n'})
	}

}
Output:
123
123
Daisuke Maki
Daisuke Maki
1136239445

func NewSpecificationSet

func NewSpecificationSet() SpecificationSet

NewSpecificationSet creates a specification set with the default specifications.

type Strftime

type Strftime struct {
	// contains filtered or unexported fields
}

Strftime is the object that represents a compiled strftime pattern

func New

func New(p string, options ...Option) (*Strftime, error)

New creates a new Strftime object. If the compilation fails, then an error is returned in the second argument.

func (*Strftime) Dump added in v1.0.1

func (f *Strftime) Dump(out io.Writer)

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 (*Strftime) Format

func (f *Strftime) Format(dst io.Writer, t time.Time) error

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 (*Strftime) FormatBuffer added in v1.0.5

func (f *Strftime) FormatBuffer(dst []byte, t time.Time) []byte

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 (*Strftime) FormatString

func (f *Strftime) FormatString(t time.Time) string

FormatString takes the time `t` and formats it, returning the string containing the formated data.

func (*Strftime) Pattern

func (f *Strftime) Pattern() string

Pattern returns the original pattern string

type WeekdayNames added in v1.2.0

type WeekdayNames [7]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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL