appenders.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. package strftime
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strconv"
  7. "strings"
  8. "time"
  9. )
  10. // These are all of the standard, POSIX compliant specifications.
  11. // Extensions should be in extensions.go
  12. var (
  13. fullWeekDayName = StdlibFormat("Monday")
  14. abbrvWeekDayName = StdlibFormat("Mon")
  15. fullMonthName = StdlibFormat("January")
  16. abbrvMonthName = StdlibFormat("Jan")
  17. centuryDecimal = AppendFunc(appendCentury)
  18. timeAndDate = StdlibFormat("Mon Jan _2 15:04:05 2006")
  19. mdy = StdlibFormat("01/02/06")
  20. dayOfMonthZeroPad = StdlibFormat("02")
  21. dayOfMonthSpacePad = StdlibFormat("_2")
  22. ymd = StdlibFormat("2006-01-02")
  23. twentyFourHourClockZeroPad = &hourPadded{twelveHour: false, pad: '0'}
  24. twelveHourClockZeroPad = &hourPadded{twelveHour: true, pad: '0'}
  25. dayOfYear = AppendFunc(appendDayOfYear)
  26. twentyFourHourClockSpacePad = &hourPadded{twelveHour: false, pad: ' '}
  27. twelveHourClockSpacePad = &hourPadded{twelveHour: true, pad: ' '}
  28. minutesZeroPad = StdlibFormat("04")
  29. monthNumberZeroPad = StdlibFormat("01")
  30. newline = Verbatim("\n")
  31. ampm = StdlibFormat("PM")
  32. hm = StdlibFormat("15:04")
  33. imsp = hmsWAMPM{}
  34. secondsNumberZeroPad = StdlibFormat("05")
  35. hms = StdlibFormat("15:04:05")
  36. tab = Verbatim("\t")
  37. weekNumberSundayOrigin = weeknumberOffset(0) // week number of the year, Sunday first
  38. weekdayMondayOrigin = weekday(1)
  39. // monday as the first day, and 01 as the first value
  40. weekNumberMondayOriginOneOrigin = AppendFunc(appendWeekNumber)
  41. eby = StdlibFormat("_2-Jan-2006")
  42. // monday as the first day, and 00 as the first value
  43. weekNumberMondayOrigin = weeknumberOffset(1) // week number of the year, Monday first
  44. weekdaySundayOrigin = weekday(0)
  45. natReprTime = StdlibFormat("15:04:05") // national representation of the time XXX is this correct?
  46. natReprDate = StdlibFormat("01/02/06") // national representation of the date XXX is this correct?
  47. year = StdlibFormat("2006") // year with century
  48. yearNoCentury = StdlibFormat("06") // year w/o century
  49. timezone = StdlibFormat("MST") // time zone name
  50. timezoneOffset = StdlibFormat("-0700") // time zone ofset from UTC
  51. percent = Verbatim("%")
  52. )
  53. // Appender is the interface that must be fulfilled by components that
  54. // implement the translation of specifications to actual time value.
  55. //
  56. // The Append method takes the accumulated byte buffer, and the time to
  57. // use to generate the textual representation. The resulting byte
  58. // sequence must be returned by this method, normally by using the
  59. // append() builtin function.
  60. type Appender interface {
  61. Append([]byte, time.Time) []byte
  62. }
  63. // AppendFunc is an utility type to allow users to create a
  64. // function-only version of an Appender
  65. type AppendFunc func([]byte, time.Time) []byte
  66. func (af AppendFunc) Append(b []byte, t time.Time) []byte {
  67. return af(b, t)
  68. }
  69. type appenderList []Appender
  70. type dumper interface {
  71. dump(io.Writer)
  72. }
  73. func (l appenderList) dump(out io.Writer) {
  74. var buf bytes.Buffer
  75. ll := len(l)
  76. for i, a := range l {
  77. if dumper, ok := a.(dumper); ok {
  78. dumper.dump(&buf)
  79. } else {
  80. fmt.Fprintf(&buf, "%#v", a)
  81. }
  82. if i < ll-1 {
  83. fmt.Fprintf(&buf, ",\n")
  84. }
  85. }
  86. if _, err := buf.WriteTo(out); err != nil {
  87. panic(err)
  88. }
  89. }
  90. // does the time.Format thing
  91. type stdlibFormat struct {
  92. s string
  93. }
  94. // StdlibFormat returns an Appender that simply goes through `time.Format()`
  95. // For example, if you know you want to display the abbreviated month name for %b,
  96. // you can create a StdlibFormat with the pattern `Jan` and register that
  97. // for specification `b`:
  98. //
  99. // a := StdlibFormat(`Jan`)
  100. // ss := NewSpecificationSet()
  101. // ss.Set('b', a) // does %b -> abbreviated month name
  102. func StdlibFormat(s string) Appender {
  103. return &stdlibFormat{s: s}
  104. }
  105. func (v stdlibFormat) Append(b []byte, t time.Time) []byte {
  106. return t.AppendFormat(b, v.s)
  107. }
  108. func (v stdlibFormat) str() string {
  109. return v.s
  110. }
  111. func (v stdlibFormat) canCombine() bool {
  112. return true
  113. }
  114. func (v stdlibFormat) combine(w combiner) Appender {
  115. return StdlibFormat(v.s + w.str())
  116. }
  117. func (v stdlibFormat) dump(out io.Writer) {
  118. fmt.Fprintf(out, "stdlib: %s", v.s)
  119. }
  120. type verbatimw struct {
  121. s string
  122. }
  123. // Verbatim returns an Appender suitable for generating static text.
  124. // For static text, this method is slightly favorable than creating
  125. // your own appender, as adjacent verbatim blocks will be combined
  126. // at compile time to produce more efficient Appenders
  127. func Verbatim(s string) Appender {
  128. return &verbatimw{s: s}
  129. }
  130. func (v verbatimw) Append(b []byte, _ time.Time) []byte {
  131. return append(b, v.s...)
  132. }
  133. func (v verbatimw) canCombine() bool {
  134. return canCombine(v.s)
  135. }
  136. func (v verbatimw) combine(w combiner) Appender {
  137. if _, ok := w.(*stdlibFormat); ok {
  138. return StdlibFormat(v.s + w.str())
  139. }
  140. return Verbatim(v.s + w.str())
  141. }
  142. func (v verbatimw) str() string {
  143. return v.s
  144. }
  145. func (v verbatimw) dump(out io.Writer) {
  146. fmt.Fprintf(out, "verbatim: %s", v.s)
  147. }
  148. // These words below, as well as any decimal character
  149. var combineExclusion = []string{
  150. "Mon",
  151. "Monday",
  152. "Jan",
  153. "January",
  154. "MST",
  155. "PM",
  156. "pm",
  157. }
  158. func canCombine(s string) bool {
  159. if strings.ContainsAny(s, "0123456789") {
  160. return false
  161. }
  162. for _, word := range combineExclusion {
  163. if strings.Contains(s, word) {
  164. return false
  165. }
  166. }
  167. return true
  168. }
  169. type combiner interface {
  170. canCombine() bool
  171. combine(combiner) Appender
  172. str() string
  173. }
  174. // this is container for the compiler to keep track of appenders,
  175. // and combine them as we parse and compile the pattern
  176. type combiningAppend struct {
  177. list appenderList
  178. prev Appender
  179. prevCanCombine bool
  180. }
  181. func (ca *combiningAppend) Append(w Appender) {
  182. if ca.prevCanCombine {
  183. if wc, ok := w.(combiner); ok && wc.canCombine() {
  184. ca.prev = ca.prev.(combiner).combine(wc)
  185. ca.list[len(ca.list)-1] = ca.prev
  186. return
  187. }
  188. }
  189. ca.list = append(ca.list, w)
  190. ca.prev = w
  191. ca.prevCanCombine = false
  192. if comb, ok := w.(combiner); ok {
  193. if comb.canCombine() {
  194. ca.prevCanCombine = true
  195. }
  196. }
  197. }
  198. func appendCentury(b []byte, t time.Time) []byte {
  199. n := t.Year() / 100
  200. if n < 10 {
  201. b = append(b, '0')
  202. }
  203. return append(b, strconv.Itoa(n)...)
  204. }
  205. type weekday int
  206. func (v weekday) Append(b []byte, t time.Time) []byte {
  207. n := int(t.Weekday())
  208. if n < int(v) {
  209. n += 7
  210. }
  211. return append(b, byte(n+48))
  212. }
  213. type weeknumberOffset int
  214. func (v weeknumberOffset) Append(b []byte, t time.Time) []byte {
  215. yd := t.YearDay()
  216. offset := int(t.Weekday()) - int(v)
  217. if offset < 0 {
  218. offset += 7
  219. }
  220. if yd < offset {
  221. return append(b, '0', '0')
  222. }
  223. n := ((yd - offset) / 7) + 1
  224. if n < 10 {
  225. b = append(b, '0')
  226. }
  227. return append(b, strconv.Itoa(n)...)
  228. }
  229. func appendWeekNumber(b []byte, t time.Time) []byte {
  230. _, n := t.ISOWeek()
  231. if n < 10 {
  232. b = append(b, '0')
  233. }
  234. return append(b, strconv.Itoa(n)...)
  235. }
  236. func appendDayOfYear(b []byte, t time.Time) []byte {
  237. n := t.YearDay()
  238. if n < 10 {
  239. b = append(b, '0', '0')
  240. } else if n < 100 {
  241. b = append(b, '0')
  242. }
  243. return append(b, strconv.Itoa(n)...)
  244. }
  245. type hourPadded struct {
  246. pad byte
  247. twelveHour bool
  248. }
  249. func (v hourPadded) Append(b []byte, t time.Time) []byte {
  250. h := t.Hour()
  251. if v.twelveHour && h > 12 {
  252. h = h - 12
  253. }
  254. if v.twelveHour && h == 0 {
  255. h = 12
  256. }
  257. if h < 10 {
  258. b = append(b, v.pad)
  259. b = append(b, byte(h+48))
  260. } else {
  261. b = unrollTwoDigits(b, h)
  262. }
  263. return b
  264. }
  265. func unrollTwoDigits(b []byte, v int) []byte {
  266. b = append(b, byte((v/10)+48))
  267. b = append(b, byte((v%10)+48))
  268. return b
  269. }
  270. type hmsWAMPM struct{}
  271. func (v hmsWAMPM) Append(b []byte, t time.Time) []byte {
  272. h := t.Hour()
  273. var am bool
  274. if h == 0 {
  275. b = append(b, '1')
  276. b = append(b, '2')
  277. am = true
  278. } else {
  279. switch {
  280. case h == 12:
  281. // no op
  282. case h > 12:
  283. h = h - 12
  284. default:
  285. am = true
  286. }
  287. b = unrollTwoDigits(b, h)
  288. }
  289. b = append(b, ':')
  290. b = unrollTwoDigits(b, t.Minute())
  291. b = append(b, ':')
  292. b = unrollTwoDigits(b, t.Second())
  293. b = append(b, ' ')
  294. if am {
  295. b = append(b, 'A')
  296. } else {
  297. b = append(b, 'P')
  298. }
  299. b = append(b, 'M')
  300. return b
  301. }