Skip to content

Commit 0ea1e3a

Browse files
Strftime (#8)
Co-authored-by: Ben Visness <[email protected]>
1 parent 09ee001 commit 0ea1e3a

File tree

3 files changed

+160
-19
lines changed

3 files changed

+160
-19
lines changed

stringlib.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) str
222222
infoList := make([]replaceInfo, 0, len(matches))
223223
for _, match := range matches {
224224
start, end := match.Capture(0), match.Capture(1)
225-
sc := newFlagScanner('%', "", "", repl)
225+
sc := newFlagScanner('%', "", "", "", repl)
226226
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
227227
if !sc.ChangeFlag {
228228
if sc.HasFlag {

utils.go

+86-18
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,22 @@ func defaultFormat(v interface{}, f fmt.State, c rune) {
4747
}
4848

4949
type flagScanner struct {
50-
flag byte
51-
start string
52-
end string
53-
buf []byte
54-
str string
55-
Length int
56-
Pos int
57-
HasFlag bool
58-
ChangeFlag bool
50+
flag byte
51+
modifiers []byte
52+
start string
53+
end string
54+
buf []byte
55+
str string
56+
Length int
57+
Pos int
58+
HasFlag bool
59+
ChangeFlag bool
60+
HasModifier bool
61+
Modifier byte
5962
}
6063

61-
func newFlagScanner(flag byte, start, end, str string) *flagScanner {
62-
return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false}
64+
func newFlagScanner(flag byte, modifiers, start, end, str string) *flagScanner {
65+
return &flagScanner{flag, []byte(modifiers), start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false, false, 0}
6366
}
6467

6568
func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) }
@@ -84,38 +87,103 @@ func (fs *flagScanner) Next() (byte, bool) {
8487
fs.AppendChar(fs.flag)
8588
fs.Pos += 2
8689
return fs.Next()
87-
} else if fs.Pos != fs.Length-1 {
90+
} else if fs.Pos < fs.Length-1 {
8891
if fs.HasFlag {
8992
fs.AppendString(fs.end)
9093
}
9194
fs.AppendString(fs.start)
9295
fs.ChangeFlag = true
9396
fs.HasFlag = true
97+
fs.HasModifier = false
98+
fs.Modifier = 0
99+
if fs.Pos < fs.Length-2 {
100+
for _, modifier := range fs.modifiers {
101+
if fs.str[fs.Pos+1] == modifier {
102+
fs.HasModifier = true
103+
fs.Modifier = modifier
104+
fs.Pos += 1
105+
}
106+
}
107+
}
94108
}
95109
}
96110
}
97111
fs.Pos++
98112
return c, false
99113
}
100114

101-
var cDateFlagToGo = map[byte]string{
102-
'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02",
103-
'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05",
104-
'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"}
115+
var cDateFlagToGo = map[string]string{
116+
// Formatting
117+
"n": "\n",
118+
"t": "\t",
119+
120+
// Year
121+
"Y": "2006", "y": "06",
122+
123+
// Month
124+
"b": "Jan", "B": "January",
125+
"m": "01", "-m": "1",
126+
127+
// Day of the year/month
128+
"j": "002",
129+
"d": "02", "-d": "2", "e": "_2",
130+
131+
// Day of the week
132+
"a": "Mon", "A": "Monday",
105133

134+
// Hour, minute, second
135+
"H": "15",
136+
"I": "03", "l": "3",
137+
"M": "04",
138+
"S": "05",
139+
140+
// Other
141+
"c": "02 Jan 06 15:04 MST",
142+
"x": "01/02/06", "X": "15:04:05",
143+
"D": "01/02/06",
144+
"F": "2006-01-02",
145+
"r": "03:04:05 PM", "R": "15:04",
146+
"T": "15:04:05",
147+
"p": "PM", "P": "pm",
148+
"z": "-0700", "Z": "MST",
149+
150+
// Many other flags are handled in the body of strftime since they cannot
151+
// be represented in Go format strings.
152+
}
153+
154+
// This implementation of strftime is inspired by both the C spec and Ruby's
155+
// extensions. This allows for flags like %-d, which provides the day of the
156+
// month without padding (1..31 instead of 01..31).
106157
func strftime(t time.Time, cfmt string) string {
107-
sc := newFlagScanner('%', "", "", cfmt)
158+
sc := newFlagScanner('%', "-", "", "", cfmt)
108159
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
109160
if !sc.ChangeFlag {
110161
if sc.HasFlag {
111-
if v, ok := cDateFlagToGo[c]; ok {
162+
flag := string(c)
163+
if sc.HasModifier {
164+
flag = string(sc.Modifier) + flag
165+
}
166+
167+
if v, ok := cDateFlagToGo[flag]; ok {
112168
sc.AppendString(t.Format(v))
113169
} else {
114170
switch c {
171+
case 'G':
172+
isoYear, _ := t.ISOWeek()
173+
sc.AppendString(fmt.Sprint(isoYear))
174+
case 'g':
175+
isoYear, _ := t.ISOWeek()
176+
sc.AppendString(fmt.Sprint(isoYear)[2:])
177+
case 'V':
178+
_, isoWeek := t.ISOWeek()
179+
sc.AppendString(fmt.Sprint(isoWeek))
115180
case 'w':
116181
sc.AppendString(fmt.Sprint(int(t.Weekday())))
117182
default:
118183
sc.AppendChar('%')
184+
if sc.HasModifier {
185+
sc.AppendChar(sc.Modifier)
186+
}
119187
sc.AppendChar(c)
120188
}
121189
}

utils_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package lua
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestStrftime(t *testing.T) {
10+
type testCase struct {
11+
T time.Time
12+
Fmt string
13+
Expected string
14+
}
15+
16+
t1 := time.Date(2016, time.February, 3, 13, 23, 45, 123, time.FixedZone("Plus2", 60*60*2))
17+
t2 := time.Date(1945, time.September, 6, 7, 35, 4, 989, time.FixedZone("Minus5", 60*60*-5))
18+
19+
cases := []testCase{
20+
{t1, "foo%nbar%tbaz 100%% cool", "foo\nbar\tbaz 100% cool"},
21+
22+
{t1, "%Y %y", "2016 16"},
23+
{t1, "%G %g", "2016 16"},
24+
{t1, "%b %B", "Feb February"},
25+
{t1, "%m %-m", "02 2"},
26+
{t1, "%V", "5"},
27+
{t1, "%w", "3"},
28+
{t1, "%j", "034"},
29+
{t1, "%d %-d %e", "03 3 3"},
30+
{t1, "%a %A", "Wed Wednesday"},
31+
{t1, "%H %I %l", "13 01 1"},
32+
{t1, "%M", "23"},
33+
{t1, "%S", "45"},
34+
{t1, "%c", "03 Feb 16 13:23 Plus2"},
35+
{t1, "%D %x", "02/03/16 02/03/16"},
36+
{t1, "%F", "2016-02-03"},
37+
{t1, "%r", "01:23:45 PM"},
38+
{t1, "%R %T %X", "13:23 13:23:45 13:23:45"},
39+
{t1, "%p %P", "PM pm"},
40+
{t1, "%z %Z", "+0200 Plus2"},
41+
42+
{t2, "%Y %y", "1945 45"},
43+
{t2, "%G %g", "1945 45"},
44+
{t2, "%b %B", "Sep September"},
45+
{t2, "%m %-m", "09 9"},
46+
{t2, "%V", "36"},
47+
{t2, "%w", "4"},
48+
{t2, "%j", "249"},
49+
{t2, "%d %-d %e", "06 6 6"},
50+
{t2, "%a %A", "Thu Thursday"},
51+
{t2, "%H %I %l", "07 07 7"},
52+
{t2, "%M", "35"},
53+
{t2, "%S", "04"},
54+
{t2, "%c", "06 Sep 45 07:35 Minus5"},
55+
{t2, "%D %x", "09/06/45 09/06/45"},
56+
{t2, "%F", "1945-09-06"},
57+
{t2, "%r", "07:35:04 AM"},
58+
{t2, "%R %T %X", "07:35 07:35:04 07:35:04"},
59+
{t2, "%p %P", "AM am"},
60+
{t2, "%z %Z", "-0500 Minus5"},
61+
62+
{t1, "not real flags: %-Q %_J %^^ %-", "not real flags: %-Q %_J %^^ %-"},
63+
{t1, "end in flag: %", "end in flag: %"},
64+
}
65+
for i, c := range cases {
66+
t.Run(fmt.Sprintf("Case %d (\"%s\")", i, c.Fmt), func(t *testing.T) {
67+
actual := strftime(c.T, c.Fmt)
68+
if actual != c.Expected {
69+
t.Errorf("bad strftime: expected \"%s\" but got \"%s\"", c.Expected, actual)
70+
}
71+
})
72+
}
73+
}

0 commit comments

Comments
 (0)