package strftime import ( "bytes" "fmt" "io" "strconv" "strings" "time" ) // These are all of the standard, POSIX compliant specifications. // Extensions should be in extensions.go var ( fullWeekDayName = StdlibFormat("Monday") abbrvWeekDayName = StdlibFormat("Mon") fullMonthName = StdlibFormat("January") abbrvMonthName = StdlibFormat("Jan") centuryDecimal = AppendFunc(appendCentury) timeAndDate = StdlibFormat("Mon Jan _2 15:04:05 2006") mdy = StdlibFormat("01/02/06") dayOfMonthZeroPad = StdlibFormat("02") dayOfMonthSpacePad = StdlibFormat("_2") ymd = StdlibFormat("2006-01-02") twentyFourHourClockZeroPad = &hourPadded{twelveHour: false, pad: '0'} twelveHourClockZeroPad = &hourPadded{twelveHour: true, pad: '0'} dayOfYear = AppendFunc(appendDayOfYear) twentyFourHourClockSpacePad = &hourPadded{twelveHour: false, pad: ' '} twelveHourClockSpacePad = &hourPadded{twelveHour: true, pad: ' '} minutesZeroPad = StdlibFormat("04") monthNumberZeroPad = StdlibFormat("01") newline = Verbatim("\n") ampm = StdlibFormat("PM") hm = StdlibFormat("15:04") imsp = hmsWAMPM{} secondsNumberZeroPad = StdlibFormat("05") hms = StdlibFormat("15:04:05") tab = Verbatim("\t") weekNumberSundayOrigin = weeknumberOffset(0) // week number of the year, Sunday first weekdayMondayOrigin = weekday(1) // monday as the first day, and 01 as the first value weekNumberMondayOriginOneOrigin = AppendFunc(appendWeekNumber) eby = StdlibFormat("_2-Jan-2006") // monday as the first day, and 00 as the first value weekNumberMondayOrigin = weeknumberOffset(1) // week number of the year, Monday first weekdaySundayOrigin = weekday(0) natReprTime = StdlibFormat("15:04:05") // national representation of the time XXX is this correct? natReprDate = StdlibFormat("01/02/06") // national representation of the date XXX is this correct? year = StdlibFormat("2006") // year with century yearNoCentury = StdlibFormat("06") // year w/o century timezone = StdlibFormat("MST") // time zone name timezoneOffset = StdlibFormat("-0700") // time zone ofset from UTC percent = Verbatim("%") ) // 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. type Appender interface { Append([]byte, time.Time) []byte } // AppendFunc is an utility type to allow users to create a // function-only version of an Appender type AppendFunc func([]byte, time.Time) []byte func (af AppendFunc) Append(b []byte, t time.Time) []byte { return af(b, t) } type appenderList []Appender type dumper interface { dump(io.Writer) } func (l appenderList) dump(out io.Writer) { var buf bytes.Buffer ll := len(l) for i, a := range l { if dumper, ok := a.(dumper); ok { dumper.dump(&buf) } else { fmt.Fprintf(&buf, "%#v", a) } if i < ll-1 { fmt.Fprintf(&buf, ",\n") } } if _, err := buf.WriteTo(out); err != nil { panic(err) } } // does the time.Format thing type stdlibFormat struct { s string } // 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 StdlibFormat(s string) Appender { return &stdlibFormat{s: s} } func (v stdlibFormat) Append(b []byte, t time.Time) []byte { return t.AppendFormat(b, v.s) } func (v stdlibFormat) str() string { return v.s } func (v stdlibFormat) canCombine() bool { return true } func (v stdlibFormat) combine(w combiner) Appender { return StdlibFormat(v.s + w.str()) } func (v stdlibFormat) dump(out io.Writer) { fmt.Fprintf(out, "stdlib: %s", v.s) } type verbatimw struct { s string } // 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 func Verbatim(s string) Appender { return &verbatimw{s: s} } func (v verbatimw) Append(b []byte, _ time.Time) []byte { return append(b, v.s...) } func (v verbatimw) canCombine() bool { return canCombine(v.s) } func (v verbatimw) combine(w combiner) Appender { if _, ok := w.(*stdlibFormat); ok { return StdlibFormat(v.s + w.str()) } return Verbatim(v.s + w.str()) } func (v verbatimw) str() string { return v.s } func (v verbatimw) dump(out io.Writer) { fmt.Fprintf(out, "verbatim: %s", v.s) } // These words below, as well as any decimal character var combineExclusion = []string{ "Mon", "Monday", "Jan", "January", "MST", "PM", "pm", } func canCombine(s string) bool { if strings.ContainsAny(s, "0123456789") { return false } for _, word := range combineExclusion { if strings.Contains(s, word) { return false } } return true } type combiner interface { canCombine() bool combine(combiner) Appender str() string } // this is container for the compiler to keep track of appenders, // and combine them as we parse and compile the pattern type combiningAppend struct { list appenderList prev Appender prevCanCombine bool } func (ca *combiningAppend) Append(w Appender) { if ca.prevCanCombine { if wc, ok := w.(combiner); ok && wc.canCombine() { ca.prev = ca.prev.(combiner).combine(wc) ca.list[len(ca.list)-1] = ca.prev return } } ca.list = append(ca.list, w) ca.prev = w ca.prevCanCombine = false if comb, ok := w.(combiner); ok { if comb.canCombine() { ca.prevCanCombine = true } } } func appendCentury(b []byte, t time.Time) []byte { n := t.Year() / 100 if n < 10 { b = append(b, '0') } return append(b, strconv.Itoa(n)...) } type weekday int func (v weekday) Append(b []byte, t time.Time) []byte { n := int(t.Weekday()) if n < int(v) { n += 7 } return append(b, byte(n+48)) } type weeknumberOffset int func (v weeknumberOffset) Append(b []byte, t time.Time) []byte { yd := t.YearDay() offset := int(t.Weekday()) - int(v) if offset < 0 { offset += 7 } if yd < offset { return append(b, '0', '0') } n := ((yd - offset) / 7) + 1 if n < 10 { b = append(b, '0') } return append(b, strconv.Itoa(n)...) } func appendWeekNumber(b []byte, t time.Time) []byte { _, n := t.ISOWeek() if n < 10 { b = append(b, '0') } return append(b, strconv.Itoa(n)...) } func appendDayOfYear(b []byte, t time.Time) []byte { n := t.YearDay() if n < 10 { b = append(b, '0', '0') } else if n < 100 { b = append(b, '0') } return append(b, strconv.Itoa(n)...) } type hourPadded struct { pad byte twelveHour bool } func (v hourPadded) Append(b []byte, t time.Time) []byte { h := t.Hour() if v.twelveHour && h > 12 { h = h - 12 } if v.twelveHour && h == 0 { h = 12 } if h < 10 { b = append(b, v.pad) b = append(b, byte(h+48)) } else { b = unrollTwoDigits(b, h) } return b } func unrollTwoDigits(b []byte, v int) []byte { b = append(b, byte((v/10)+48)) b = append(b, byte((v%10)+48)) return b } type hmsWAMPM struct{} func (v hmsWAMPM) Append(b []byte, t time.Time) []byte { h := t.Hour() var am bool if h == 0 { b = append(b, '1') b = append(b, '2') am = true } else { switch { case h == 12: // no op case h > 12: h = h - 12 default: am = true } b = unrollTwoDigits(b, h) } b = append(b, ':') b = unrollTwoDigits(b, t.Minute()) b = append(b, ':') b = unrollTwoDigits(b, t.Second()) b = append(b, ' ') if am { b = append(b, 'A') } else { b = append(b, 'P') } b = append(b, 'M') return b }