Przeglądaj źródła

Split parsing into a separate file

Lucas Stadler 9 lat temu
rodzic
commit
3c5a48df7c
3 zmienionych plików z 206 dodań i 201 usunięć
  1. 206 0
      go/remind/parse.go
  2. 0 0
      go/remind/parse_test.go
  3. 0 201
      go/remind/remind.go

+ 206 - 0
go/remind/parse.go

@ -0,0 +1,206 @@
1
package main
2
3
import (
4
	"fmt"
5
	"strconv"
6
	"strings"
7
	"time"
8
)
9
10
var timeLayouts = []string{
11
	time.RFC3339,
12
	"2006-01-02",
13
}
14
15
func parseTime(s string) (time.Time, error) {
16
	now := time.Now().Round(time.Second)
17
	return parseTimeRelative(s, now)
18
}
19
20
func parseTimeRelative(s string, now time.Time) (time.Time, error) {
21
	var t time.Time
22
23
	parts := strings.Fields(s)
24
	// more indicates the parts index where there might be "more"
25
	// parsable data
26
	more := 0
27
28
	if len(parts) == 0 {
29
		return t, fmt.Errorf("empty date spec")
30
	}
31
32
	switch parts[0] {
33
	case "today":
34
		if len(parts) >= 1 {
35
			t = now
36
			more = 1
37
		}
38
	case "tomorrow":
39
		if len(parts) >= 1 {
40
			t = now.AddDate(0, 0, 1)
41
			more = 1
42
		}
43
	case "in":
44
		if len(parts) >= 3 {
45
			n, err := parseNumber(parts[1])
46
			if err != nil {
47
				return t, err
48
			}
49
			modifier, err := parseModifier(parts[2])
50
			if err != nil {
51
				return t, err
52
			}
53
			t = modifier(n, now)
54
			more = 3
55
		}
56
	case "next":
57
		if len(parts) >= 2 {
58
			modifier, err := parseModifier(parts[1])
59
			if err != nil {
60
				return t, err
61
			}
62
			t = modifier(1, now)
63
			more = 2
64
		}
65
	}
66
67
	if len(parts) == more {
68
		return t, nil
69
	} else if len(parts) == more+2 && parts[more] == "at" {
70
		h, err := parseHours(parts[more+1])
71
		if err != nil {
72
			return t, err
73
		}
74
75
		t = truncateHours(t)
76
		return t.Add(h), nil
77
	} else if len(parts) == more+3 && parts[more] == "at" &&
78
		(parts[more+2] == "am" || parts[more+2] == "pm") {
79
		h, err := parseHours(parts[more+1] + parts[more+2])
80
		if err != nil {
81
			return t, err
82
		}
83
84
		t = truncateHours(t)
85
		return t.Add(h), nil
86
	}
87
88
	l := len(parts)
89
	if (l == 1 || l == 2) && (strings.HasSuffix(parts[l-1], "am") || strings.HasSuffix(parts[l-1], "pm")) {
90
		s = parts[0]
91
		if l == 2 {
92
			s += parts[1]
93
		}
94
95
		h, err := parseHours(s)
96
		if err != nil {
97
			return t, err
98
		}
99
100
		t = truncateHours(now)
101
		return t.Add(h), nil
102
	}
103
104
	for _, layout := range timeLayouts {
105
		t, err := time.Parse(layout, s)
106
		if err == nil {
107
			return t, nil
108
		}
109
	}
110
111
	return t, fmt.Errorf("unknown date spec '%s' (unexpected)", s)
112
}
113
114
func parseNumber(n string) (int, error) {
115
	switch n {
116
	case "a", "an", "one":
117
		return 1, nil
118
	case "two":
119
		return 2, nil
120
	case "three":
121
		return 3, nil
122
	case "four":
123
		return 4, nil
124
	case "five":
125
		return 5, nil
126
	case "six":
127
		return 6, nil
128
	case "seven":
129
		return 7, nil
130
	case "eight":
131
		return 8, nil
132
	case "nine":
133
		return 9, nil
134
	case "ten":
135
		return 10, nil
136
	default:
137
		return strconv.Atoi(n)
138
	}
139
}
140
141
func parseModifier(m string) (func(int, time.Time) time.Time, error) {
142
	switch m {
143
	case "second", "seconds":
144
		return durationModifier(time.Second), nil
145
	case "minute", "minutes":
146
		return durationModifier(time.Minute), nil
147
	case "hour", "hours":
148
		return durationModifier(time.Hour), nil
149
	case "day", "days":
150
		return dateModifier(0, 0, 1), nil
151
	case "week", "weeks":
152
		return dateModifier(0, 0, 7), nil
153
	case "month", "months":
154
		return dateModifier(0, 1, 0), nil
155
	case "year", "years":
156
		return dateModifier(1, 0, 0), nil
157
	default:
158
		return nil, fmt.Errorf("unknown modifier '%s'", m)
159
	}
160
}
161
162
func durationModifier(d time.Duration) func(int, time.Time) time.Time {
163
	return func(n int, t time.Time) time.Time {
164
		return t.Add(time.Duration(n) * d)
165
	}
166
}
167
168
func dateModifier(years, months, days int) func(int, time.Time) time.Time {
169
	return func(n int, t time.Time) time.Time {
170
		return t.AddDate(n*years, n*months, n*days)
171
	}
172
}
173
174
func parseHours(s string) (time.Duration, error) {
175
	var d time.Duration
176
177
	isPm := false
178
	switch {
179
	case strings.HasSuffix(s, "am"):
180
		s = s[0 : len(s)-2]
181
	case strings.HasSuffix(s, "pm"):
182
		isPm = true
183
		s = s[0 : len(s)-2]
184
	default:
185
		return d, fmt.Errorf("unknown hours '%s'", s)
186
	}
187
188
	n, err := strconv.Atoi(s)
189
	if err != nil {
190
		return d, err
191
	}
192
193
	if n < 1 || n > 12 {
194
		return d, fmt.Errorf("invalid hour: %d (must be between 1 and 12)", n)
195
	}
196
197
	if isPm {
198
		n += 12
199
	}
200
201
	return time.Duration(n) * time.Hour, nil
202
}
203
204
func truncateHours(t time.Time) time.Time {
205
	return t.Truncate(time.Hour).Add(-time.Duration(t.Hour()) * time.Hour)
206
}

go/remind/remind_test.go → go/remind/parse_test.go


+ 0 - 201
go/remind/remind.go

@ -2,9 +2,6 @@ package main
2 2
3 3
import (
4 4
	"fmt"
5
	"strconv"
6
	"strings"
7
	"time"
8 5
)
9 6
10 7
func main() {
@ -33,201 +30,3 @@ func main() {
33 30
		}
34 31
	}
35 32
}
36
37
var timeLayouts = []string{
38
	time.RFC3339,
39
	"2006-01-02",
40
}
41
42
func parseTime(s string) (time.Time, error) {
43
	now := time.Now().Round(time.Second)
44
	return parseTimeRelative(s, now)
45
}
46
47
func parseTimeRelative(s string, now time.Time) (time.Time, error) {
48
	var t time.Time
49
50
	parts := strings.Fields(s)
51
	// more indicates the parts index where there might be "more"
52
	// parsable data
53
	more := 0
54
55
	if len(parts) == 0 {
56
		return t, fmt.Errorf("empty date spec")
57
	}
58
59
	switch parts[0] {
60
	case "today":
61
		if len(parts) >= 1 {
62
			t = now
63
			more = 1
64
		}
65
	case "tomorrow":
66
		if len(parts) >= 1 {
67
			t = now.AddDate(0, 0, 1)
68
			more = 1
69
		}
70
	case "in":
71
		if len(parts) >= 3 {
72
			n, err := parseNumber(parts[1])
73
			if err != nil {
74
				return t, err
75
			}
76
			modifier, err := parseModifier(parts[2])
77
			if err != nil {
78
				return t, err
79
			}
80
			t = modifier(n, now)
81
			more = 3
82
		}
83
	case "next":
84
		if len(parts) >= 2 {
85
			modifier, err := parseModifier(parts[1])
86
			if err != nil {
87
				return t, err
88
			}
89
			t = modifier(1, now)
90
			more = 2
91
		}
92
	}
93
94
	if len(parts) == more {
95
		return t, nil
96
	} else if len(parts) == more+2 && parts[more] == "at" {
97
		h, err := parseHours(parts[more+1])
98
		if err != nil {
99
			return t, err
100
		}
101
102
		t = truncateHours(t)
103
		return t.Add(h), nil
104
	} else if len(parts) == more+3 && parts[more] == "at" &&
105
		(parts[more+2] == "am" || parts[more+2] == "pm") {
106
		h, err := parseHours(parts[more+1] + parts[more+2])
107
		if err != nil {
108
			return t, err
109
		}
110
111
		t = truncateHours(t)
112
		return t.Add(h), nil
113
	}
114
115
	l := len(parts)
116
	if (l == 1 || l == 2) && (strings.HasSuffix(parts[l-1], "am") || strings.HasSuffix(parts[l-1], "pm")) {
117
		s = parts[0]
118
		if l == 2 {
119
			s += parts[1]
120
		}
121
122
		h, err := parseHours(s)
123
		if err != nil {
124
			return t, err
125
		}
126
127
		t = truncateHours(now)
128
		return t.Add(h), nil
129
	}
130
131
	for _, layout := range timeLayouts {
132
		t, err := time.Parse(layout, s)
133
		if err == nil {
134
			return t, nil
135
		}
136
	}
137
138
	return t, fmt.Errorf("unknown date spec '%s' (unexpected)", s)
139
}
140
141
func parseNumber(n string) (int, error) {
142
	switch n {
143
	case "a", "an", "one":
144
		return 1, nil
145
	case "two":
146
		return 2, nil
147
	case "three":
148
		return 3, nil
149
	case "four":
150
		return 4, nil
151
	case "five":
152
		return 5, nil
153
	case "six":
154
		return 6, nil
155
	case "seven":
156
		return 7, nil
157
	case "eight":
158
		return 8, nil
159
	case "nine":
160
		return 9, nil
161
	case "ten":
162
		return 10, nil
163
	default:
164
		return strconv.Atoi(n)
165
	}
166
}
167
168
func parseModifier(m string) (func(int, time.Time) time.Time, error) {
169
	switch m {
170
	case "second", "seconds":
171
		return durationModifier(time.Second), nil
172
	case "minute", "minutes":
173
		return durationModifier(time.Minute), nil
174
	case "hour", "hours":
175
		return durationModifier(time.Hour), nil
176
	case "day", "days":
177
		return dateModifier(0, 0, 1), nil
178
	case "week", "weeks":
179
		return dateModifier(0, 0, 7), nil
180
	case "month", "months":
181
		return dateModifier(0, 1, 0), nil
182
	case "year", "years":
183
		return dateModifier(1, 0, 0), nil
184
	default:
185
		return nil, fmt.Errorf("unknown modifier '%s'", m)
186
	}
187
}
188
189
func durationModifier(d time.Duration) func(int, time.Time) time.Time {
190
	return func(n int, t time.Time) time.Time {
191
		return t.Add(time.Duration(n) * d)
192
	}
193
}
194
195
func dateModifier(years, months, days int) func(int, time.Time) time.Time {
196
	return func(n int, t time.Time) time.Time {
197
		return t.AddDate(n*years, n*months, n*days)
198
	}
199
}
200
201
func parseHours(s string) (time.Duration, error) {
202
	var d time.Duration
203
204
	isPm := false
205
	switch {
206
	case strings.HasSuffix(s, "am"):
207
		s = s[0 : len(s)-2]
208
	case strings.HasSuffix(s, "pm"):
209
		isPm = true
210
		s = s[0 : len(s)-2]
211
	default:
212
		return d, fmt.Errorf("unknown hours '%s'", s)
213
	}
214
215
	n, err := strconv.Atoi(s)
216
	if err != nil {
217
		return d, err
218
	}
219
220
	if n < 1 || n > 12 {
221
		return d, fmt.Errorf("invalid hour: %d (must be between 1 and 12)", n)
222
	}
223
224
	if isPm {
225
		n += 12
226
	}
227
228
	return time.Duration(n) * time.Hour, nil
229
}
230
231
func truncateHours(t time.Time) time.Time {
232
	return t.Truncate(time.Hour).Add(-time.Duration(t.Hour()) * time.Hour)
233
}