1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 06:59:03 +01:00

Move strftime to a separate repo

This commit is contained in:
Oliver Steele 2017-08-10 19:02:13 -04:00
parent 6b15fbf6c7
commit cdb0e44c6f
6 changed files with 1 additions and 517 deletions

View File

@ -14,8 +14,8 @@ import (
"unicode"
"unicode/utf8"
"github.com/osteele/liquid/strftime"
"github.com/osteele/liquid/values"
strftime "github.com/osteele/rbstrftime"
)
// A FilterDictionary holds filters.

View File

@ -1,238 +0,0 @@
// Package strftime implements a Strftime function that is compatible with Ruby's Time.strftime.
package strftime
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
)
// Strftime is compatible with Ruby's Time.strftime.
func Strftime(format string, t time.Time) (string, error) {
return re.ReplaceAllStringFunc(format, func(directive string) string {
var (
m = re.FindAllStringSubmatch(directive, 1)[0]
flags = m[1]
width = m[2]
conversion, _ = utf8.DecodeRuneInString(m[3])
c = replaceComponent(t, conversion, flags, width)
pad, w = '0', 2
)
if s, ok := c.(string); ok {
return s
}
if f, ok := padding[conversion]; ok {
pad, w = f.c, f.w
}
switch flags {
case "-":
w = 0
case "_":
pad = ' '
case "0":
pad = '0'
}
if len(width) > 0 {
w, _ = strconv.Atoi(width) // nolint: gas
}
fm := fmt.Sprintf("%%%c%dd", pad, w)
if pad == '-' {
fm = fmt.Sprintf("%%%dd", w)
}
s := fmt.Sprintf(fm, c)
switch flags {
case "^":
return strings.ToUpper(s)
// case "#":
default:
return s
}
}), nil
}
var re = regexp.MustCompile(`%([-_0]|::?)?(\d+)?[EO]?([a-zA-Z\+nt%])`)
var amPmTable = map[bool]string{true: "AM", false: "PM"}
var amPmLowerTable = map[bool]string{true: "am", false: "pm"}
var padding = map[rune]struct {
c rune
w int
}{
'e': {'-', 2},
'f': {'0', 6},
'j': {'0', 3},
'k': {'-', 2},
'L': {'0', 3},
'l': {'-', 2},
'N': {'0', 9},
'u': {'-', 0},
'w': {'-', 0},
'Y': {'0', 4},
}
func replaceComponent(t time.Time, c rune, flags, width string) interface{} { // nolint: gocyclo
switch c {
// Date
case 'Y':
return t.Year()
case 'y':
return t.Year() % 100
case 'C':
return t.Year() / 100
case 'm':
return t.Month()
case 'B':
return t.Month().String()
case 'b', 'h':
return t.Month().String()[:3]
case 'd', 'e':
return t.Day()
case 'j':
return t.YearDay()
// Time
case 'H', 'k':
return t.Hour()
case 'I', 'l':
return (t.Hour()+11)%12 + 1
case 'M':
return t.Minute()
case 'S':
return t.Second()
case 'L':
return t.Nanosecond() / 1e6
case 'N':
ns := t.Nanosecond()
if len(width) > 0 {
w, _ := strconv.Atoi(width) // nolint: gas
if w <= 9 {
return fmt.Sprintf("%09d", ns)[:w]
}
return fmt.Sprintf(fmt.Sprintf("%%09d%%0%dd", w-9), ns, 0)
}
return ns
case 'P':
return amPmLowerTable[t.Hour() < 12]
case 'p':
return amPmTable[t.Hour() < 12]
// Time zone
case 'z':
_, offset := t.Zone()
var (
h = offset / 3600
m = (offset / 60) % 60
)
switch flags {
case ":":
return fmt.Sprintf("%+03d:%02d", h, m)
case "::":
return fmt.Sprintf("%+03d:%02d:%02d", h, m, offset%60)
default:
return fmt.Sprintf("%+03d%02d", h, m)
}
case 'Z':
z, _ := t.Zone()
return z
// Weekday
case 'A':
return t.Weekday().String()
case 'a':
return t.Weekday().String()[:3]
case 'u':
return (t.Weekday()+6)%7 + 1
case 'w':
return t.Weekday()
// ISO Year
case 'G':
y, _ := t.ISOWeek()
return y
case 'g':
y, _ := t.ISOWeek()
return y % 100
case 'V':
_, wn := t.ISOWeek()
return wn
// ISO Week
case 'U':
t = t.Add(24 * time.Hour)
y, wn := t.ISOWeek()
if y < t.Year() {
wn = 0
}
return wn
case 'W':
y, wn := t.ISOWeek()
if y < t.Year() {
wn = 0
}
return wn
// Epoch seconds
case 's':
return t.Unix()
case 'Q':
return t.UnixNano() / 1000
// Literals
case 'n':
return "\n"
case 't':
return "\t"
case '%':
return "%"
// Combinations
case 'c':
// date and time (%a %b %e %T %Y)
h, m, s := t.Clock()
return fmt.Sprintf("%s %s %2d %02d:%02d:%02d %04d", t.Weekday().String()[:3], t.Month().String()[:3], t.Day(), h, m, s, t.Year())
case 'D', 'x':
// Date (%m/%d/%y)
y, m, d := t.Date()
return fmt.Sprintf("%02d/%02d/%02d", m, d, y%100)
case 'F':
// The ISO 8601 date format (%Y-%m-%d)
y, m, d := t.Date()
return fmt.Sprintf("%04d-%02d-%02d", y, m, d)
case 'v':
// VMS date (%e-%b-%Y)
return fmt.Sprintf("%2d-%s-%04d", t.Day(), t.Month().String()[:3], t.Year())
case 'f':
return t.Nanosecond() / 1e3
case 'r':
// 12-hour time (%I:%M:%S %p)
h, m, s := t.Clock()
h12 := (h+11)%12 + 1
return fmt.Sprintf("%02d:%02d:%02d %s", h12, m, s, amPmTable[h < 12])
case 'R':
// 24-hour time (%H:%M)
h, m, _ := t.Clock()
return fmt.Sprintf("%02d:%02d", h, m)
case 'T', 'X':
// 24-hour time (%H:%M:%S)
h, m, s := t.Clock()
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
case '+':
// date(1) (%a %b %e %H:%M:%S %Z %Y)
s, err := Strftime("%a %b %e %H:%M:%S %Z %Y", t)
if err != nil {
panic(err)
}
return s
default:
return fmt.Sprintf("%%%c", c)
}
}

View File

@ -1,148 +0,0 @@
package strftime
import (
"encoding/csv"
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func timeMustParse(f, s string) time.Time {
t, err := time.ParseInLocation(f, s, time.Local)
if err != nil {
panic(err)
}
return t
}
var conversionTests = []struct{ format, expect string }{
{"%1N", "1"},
{"%3N", "123"},
{"%6N", "123456"},
{"%9N", "123456789"},
{"%12N", "123456789000"},
{"%v", " 2-Jan-2006"},
{"%Z", "EST"},
{"%:z", "-05:00"},
{"%::z", "-05:00:00"},
{"%%", "%"},
}
var dayOfWeekTests = []string{
"%A=Sunday %a=Sun %u=7 %w=0 %d=01 %e= 1 %j=001 %U=01 %V=52 %W=00",
"%A=Monday %a=Mon %u=1 %w=1 %d=02 %e= 2 %j=002 %U=01 %V=01 %W=01",
"%A=Tuesday %a=Tue %u=2 %w=2 %d=03 %e= 3 %j=003 %U=01 %V=01 %W=01",
"%A=Wednesday %a=Wed %u=3 %w=3 %d=04 %e= 4 %j=004 %U=01 %V=01 %W=01",
"%A=Thursday %a=Thu %u=4 %w=4 %d=05 %e= 5 %j=005 %U=01 %V=01 %W=01",
"%A=Friday %a=Fri %u=5 %w=5 %d=06 %e= 6 %j=006 %U=01 %V=01 %W=01",
"%A=Saturday %a=Sat %u=6 %w=6 %d=07 %e= 7 %j=007 %U=01 %V=01 %W=01",
}
var hourTests = []struct {
hour int
expect string
}{
{0, "%H=00 %k= 0 %I=12 %l=12 %P=am %p=AM"},
{1, "%H=01 %k= 1 %I=01 %l= 1 %P=am %p=AM"},
{12, "%H=12 %k=12 %I=12 %l=12 %P=pm %p=PM"},
{13, "%H=13 %k=13 %I=01 %l= 1 %P=pm %p=PM"},
{23, "%H=23 %k=23 %I=11 %l=11 %P=pm %p=PM"},
}
func TestStrftime(t *testing.T) {
require.NoError(t, os.Setenv("TZ", "America/New_York"))
dt := timeMustParse(time.RFC3339Nano, "2006-01-02T15:04:05.123456789-05:00")
for _, test := range conversionTests {
name := fmt.Sprintf("Strftime %q", test.format)
actual, err := Strftime(test.format, dt)
require.NoErrorf(t, err, name)
require.Equalf(t, test.expect, actual, name)
}
skip := map[string]bool{}
f, err := os.Open("testdata/skip.csv")
require.NoError(t, err)
defer f.Close() // nolint: errcheck
r := csv.NewReader(f)
rows, err := r.ReadAll()
require.NoError(t, err)
for _, row := range rows {
skip[row[0]] = true
}
f, err = os.Open("testdata/data.csv")
require.NoError(t, err)
defer f.Close() // nolint: errcheck
r = csv.NewReader(f)
rows, err = r.ReadAll()
require.NoError(t, err)
for _, row := range rows {
format, expect := row[0], row[1]
if skip[format] {
continue
}
name := fmt.Sprintf("Strftime %q", format)
actual, err := Strftime(format, dt)
require.NoErrorf(t, err, name)
require.Equalf(t, expect, actual, name)
}
dt = timeMustParse(time.RFC1123Z, "Mon, 02 Jan 2006 15:04:05 -0500")
tests := []struct{ format, expect string }{
{"%a, %b %d, %Y", "Mon, Jan 02, 2006"},
{"%Y/%m/%d", "2006/01/02"},
{"%Y/%m/%e", "2006/01/ 2"},
{"%Y/%-m/%-d", "2006/1/2"},
{"%a, %b %d, %Y %z", "Mon, Jan 02, 2006 -0500"},
{"%a, %b %d, %Y %Z", "Mon, Jan 02, 2006 EST"},
// {"", ""}, this errors on Travis
}
for _, test := range tests {
s, err := Strftime(test.format, dt)
require.NoErrorf(t, err, test.format)
require.Equalf(t, test.expect, s, test.format)
}
}
func TestStrftime_dow(t *testing.T) {
require.NoError(t, os.Setenv("TZ", "America/New_York"))
for day, expect := range dayOfWeekTests {
dt := time.Date(2006, 01, day+1, 15, 4, 5, 0, time.UTC)
format := "%%A=%A %%a=%a %%u=%u %%w=%w %%d=%d %%e=%e %%j=%j %%U=%U %%V=%V %%W=%W"
name := fmt.Sprintf("%s.Strftime", dt)
actual, err := Strftime(format, dt)
require.NoErrorf(t, err, name)
require.Equalf(t, expect, actual, name)
}
}
func TestStrftime_hours(t *testing.T) {
require.NoError(t, os.Setenv("TZ", "America/New_York"))
for _, test := range hourTests {
dt := time.Date(2006, 01, 2, test.hour, 4, 5, 0, time.UTC)
format := "%%H=%H %%k=%k %%I=%I %%l=%l %%P=%P %%p=%p"
name := fmt.Sprintf("%s.Strftime", dt)
actual, err := Strftime(format, dt)
require.NoErrorf(t, err, name)
require.Equalf(t, test.expect, actual, name)
}
}
func TestStrftime_zones(t *testing.T) {
require.NoError(t, os.Setenv("TZ", "America/New_York"))
ins := []struct{ source, expect string }{
{"02 Jan 06 15:04 UTC", "%z=+0000 %Z=UTC"},
{"02 Jan 06 15:04 EST", "%z=-0500 %Z=EST"},
{"02 Jul 06 15:04 EDT", "%z=-0400 %Z=EDT"},
}
for _, test := range ins {
rt := timeMustParse(time.RFC822, test.source)
actual, err := Strftime("%%z=%z %%Z=%Z", rt)
require.NoErrorf(t, err, test.source)
require.Equalf(t, test.expect, actual, test.source)
}
}

View File

@ -1,83 +0,0 @@
%A,Monday
%B,January
%C,20
%D,01/02/06
%F,2006-01-02
%G,2006
%H,15
%I,03
%L,000
%M,04
%N,000000000
%P,pm
%R,15:04
%S,05
%T,15:04:05
%U,01
%V,01
%W,01
%X,15:04:05
%Y,2006
%Z,""
%a,Mon
%b,Jan
%c,Mon Jan 2 15:04:05 2006
%d,02
%e, 2
%g,06
%h,Jan
%j,002
%k,15
%l, 3
%m,01
%n,"
"
%p,PM
%r,03:04:05 PM
%s,1136232245
%t,
%u,1
%v, 2-JAN-2006
%w,1
%x,01/02/06
%y,06
%z,-0500
%-I,3
%-M,4
%-S,5
%-U,1
%-V,1
%-W,1
%-d,2
%-e,2
%-g,6
%-j,2
%-l,3
%-m,1
%-y,6
%_I, 3
%_M, 4
%_S, 5
%_U, 1
%_V, 1
%_W, 1
%_d, 2
%_g, 6
%_j, 2
%_m, 1
%_y, 6
%_z, -500
%^A,MONDAY
%^B,JANUARY
%^P,PM
%^a,MON
%^b,JAN
%^c,MON JAN 2 15:04:05 2006
%^h,JAN
%#A,MONDAY
%#B,JANUARY
%#P,PM
%#a,MON
%#b,JAN
%#h,JAN
%#p,pm
1 %A Monday
2 %B January
3 %C 20
4 %D 01/02/06
5 %F 2006-01-02
6 %G 2006
7 %H 15
8 %I 03
9 %L 000
10 %M 04
11 %N 000000000
12 %P pm
13 %R 15:04
14 %S 05
15 %T 15:04:05
16 %U 01
17 %V 01
18 %W 01
19 %X 15:04:05
20 %Y 2006
21 %Z
22 %a Mon
23 %b Jan
24 %c Mon Jan 2 15:04:05 2006
25 %d 02
26 %e 2
27 %g 06
28 %h Jan
29 %j 002
30 %k 15
31 %l 3
32 %m 01
33 %n
34 %p PM
35 %r 03:04:05 PM
36 %s 1136232245
37 %t
38 %u 1
39 %v 2-JAN-2006
40 %w 1
41 %x 01/02/06
42 %y 06
43 %z -0500
44 %-I 3
45 %-M 4
46 %-S 5
47 %-U 1
48 %-V 1
49 %-W 1
50 %-d 2
51 %-e 2
52 %-g 6
53 %-j 2
54 %-l 3
55 %-m 1
56 %-y 6
57 %_I 3
58 %_M 4
59 %_S 5
60 %_U 1
61 %_V 1
62 %_W 1
63 %_d 2
64 %_g 6
65 %_j 2
66 %_m 1
67 %_y 6
68 %_z -500
69 %^A MONDAY
70 %^B JANUARY
71 %^P PM
72 %^a MON
73 %^b JAN
74 %^c MON JAN 2 15:04:05 2006
75 %^h JAN
76 %#A MONDAY
77 %#B JANUARY
78 %#P PM
79 %#a MON
80 %#b JAN
81 %#h JAN
82 %#p pm

View File

@ -1,20 +0,0 @@
# Create a file data.csv in the current directory, containing format strings,
# and the reference date thus formatted.
#
# This is run manually rather than on go generate, so that go generate doesn't
# require a Ruby installation.
require "CSV"
rt = Time.new(2006, 1, 2, 15, 4, 5, "-05:00")
CSV.open(File.join(File.dirname(__FILE__), "data.csv"), "w") do |csv|
for mod in ['', '-', '_', '^', '#'] do
for c in ('A'..'Z').to_a + ('a'..'z').to_a do
fmt = "%#{mod}#{c}"
out = rt.strftime(fmt)
next if out == fmt
next if mod != '' && out == rt.strftime("%#{c}")
csv << [fmt, out]
end
end
end

View File

@ -1,27 +0,0 @@
%L
%N
%P
%Z
%v
%-g
%-v
%-y
%-z
%_g
%_v
%_y
%_z
%^a
%^b
%^c
%^h
%#a
%#b
%#h
%#p
%^A
%^B
%^P
%#A
%#B
%#P
1 %L
2 %N
3 %P
4 %Z
5 %v
6 %-g
7 %-v
8 %-y
9 %-z
10 %_g
11 %_v
12 %_y
13 %_z
14 %^a
15 %^b
16 %^c
17 %^h
18 %#a
19 %#b
20 %#h
21 %#p
22 %^A
23 %^B
24 %^P
25 %#A
26 %#B
27 %#P