Преглед на файлове

dline/kline: Allow year/month/day durations

tags/v0.7.0
Daniel Oaks преди 7 години
родител
ревизия
0c86c454c2
променени са 4 файла, в които са добавени 189 реда и са изтрити 4 реда
  1. 183
    0
      irc/custime/parseduration.go
  2. 2
    1
      irc/dline.go
  3. 2
    2
      irc/help.go
  4. 2
    1
      irc/kline.go

+ 183
- 0
irc/custime/parseduration.go Целия файл

@@ -0,0 +1,183 @@
1
+// Copyright 2010 The Go Authors. All rights reserved.
2
+
3
+package custime
4
+
5
+import (
6
+	"errors"
7
+	"time"
8
+)
9
+
10
+// see https://github.com/golang/go/blob/7ad512e7ffe576c4894ea84b02e954846fbda643/src/time/format.go#L1251
11
+
12
+// This is a forked version of the ParseDuration function that also handles days/months/years
13
+
14
+var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
15
+
16
+// leadingInt consumes the leading [0-9]* from s.
17
+func leadingInt(s string) (x int64, rem string, err error) {
18
+	i := 0
19
+	for ; i < len(s); i++ {
20
+		c := s[i]
21
+		if c < '0' || c > '9' {
22
+			break
23
+		}
24
+		if x > (1<<63-1)/10 {
25
+			// overflow
26
+			return 0, "", errLeadingInt
27
+		}
28
+		x = x*10 + int64(c) - '0'
29
+		if x < 0 {
30
+			// overflow
31
+			return 0, "", errLeadingInt
32
+		}
33
+	}
34
+	return x, s[i:], nil
35
+}
36
+
37
+// leadingFraction consumes the leading [0-9]* from s.
38
+// It is used only for fractions, so does not return an error on overflow,
39
+// it just stops accumulating precision.
40
+func leadingFraction(s string) (x int64, scale float64, rem string) {
41
+	i := 0
42
+	scale = 1
43
+	overflow := false
44
+	for ; i < len(s); i++ {
45
+		c := s[i]
46
+		if c < '0' || c > '9' {
47
+			break
48
+		}
49
+		if overflow {
50
+			continue
51
+		}
52
+		if x > (1<<63-1)/10 {
53
+			// It's possible for overflow to give a positive number, so take care.
54
+			overflow = true
55
+			continue
56
+		}
57
+		y := x*10 + int64(c) - '0'
58
+		if y < 0 {
59
+			overflow = true
60
+			continue
61
+		}
62
+		x = y
63
+		scale *= 10
64
+	}
65
+	return x, scale, s[i:]
66
+}
67
+
68
+var unitMap = map[string]int64{
69
+	"ns": int64(time.Nanosecond),
70
+	"us": int64(time.Microsecond),
71
+	"µs": int64(time.Microsecond), // U+00B5 = micro symbol
72
+	"μs": int64(time.Microsecond), // U+03BC = Greek letter mu
73
+	"ms": int64(time.Millisecond),
74
+	"s":  int64(time.Second),
75
+	"m":  int64(time.Minute),
76
+	"h":  int64(time.Hour),
77
+	"d":  int64(time.Hour * 24),
78
+	"mo": int64(time.Hour * 24 * 30),
79
+	"y":  int64(time.Hour * 24 * 265),
80
+}
81
+
82
+// ParseDuration parses a duration string.
83
+// A duration string is a possibly signed sequence of
84
+// decimal numbers, each with optional fraction and a unit suffix,
85
+// such as "300ms", "-1.5h" or "2h45m".
86
+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
87
+func ParseDuration(s string) (time.Duration, error) {
88
+	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
89
+	orig := s
90
+	var d int64
91
+	neg := false
92
+
93
+	// Consume [-+]?
94
+	if s != "" {
95
+		c := s[0]
96
+		if c == '-' || c == '+' {
97
+			neg = c == '-'
98
+			s = s[1:]
99
+		}
100
+	}
101
+	// Special case: if all that is left is "0", this is zero.
102
+	if s == "0" {
103
+		return 0, nil
104
+	}
105
+	if s == "" {
106
+		return 0, errors.New("time: invalid duration " + orig)
107
+	}
108
+	for s != "" {
109
+		var (
110
+			v, f  int64       // integers before, after decimal point
111
+			scale float64 = 1 // value = v + f/scale
112
+		)
113
+
114
+		var err error
115
+
116
+		// The next character must be [0-9.]
117
+		if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
118
+			return 0, errors.New("time: invalid duration " + orig)
119
+		}
120
+		// Consume [0-9]*
121
+		pl := len(s)
122
+		v, s, err = leadingInt(s)
123
+		if err != nil {
124
+			return 0, errors.New("time: invalid duration " + orig)
125
+		}
126
+		pre := pl != len(s) // whether we consumed anything before a period
127
+
128
+		// Consume (\.[0-9]*)?
129
+		post := false
130
+		if s != "" && s[0] == '.' {
131
+			s = s[1:]
132
+			pl := len(s)
133
+			f, scale, s = leadingFraction(s)
134
+			post = pl != len(s)
135
+		}
136
+		if !pre && !post {
137
+			// no digits (e.g. ".s" or "-.s")
138
+			return 0, errors.New("time: invalid duration " + orig)
139
+		}
140
+
141
+		// Consume unit.
142
+		i := 0
143
+		for ; i < len(s); i++ {
144
+			c := s[i]
145
+			if c == '.' || '0' <= c && c <= '9' {
146
+				break
147
+			}
148
+		}
149
+		if i == 0 {
150
+			return 0, errors.New("time: missing unit in duration " + orig)
151
+		}
152
+		u := s[:i]
153
+		s = s[i:]
154
+		unit, ok := unitMap[u]
155
+		if !ok {
156
+			return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
157
+		}
158
+		if v > (1<<63-1)/unit {
159
+			// overflow
160
+			return 0, errors.New("time: invalid duration " + orig)
161
+		}
162
+		v *= unit
163
+		if f > 0 {
164
+			// float64 is needed to be nanosecond accurate for fractions of hours.
165
+			// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
166
+			v += int64(float64(f) * (float64(unit) / scale))
167
+			if v < 0 {
168
+				// overflow
169
+				return 0, errors.New("time: invalid duration " + orig)
170
+			}
171
+		}
172
+		d += v
173
+		if d < 0 {
174
+			// overflow
175
+			return 0, errors.New("time: invalid duration " + orig)
176
+		}
177
+	}
178
+
179
+	if neg {
180
+		d = -d
181
+	}
182
+	return time.Duration(d), nil
183
+}

+ 2
- 1
irc/dline.go Целия файл

@@ -14,6 +14,7 @@ import (
14 14
 	"encoding/json"
15 15
 
16 16
 	"github.com/DanielOaks/girc-go/ircmsg"
17
+	"github.com/DanielOaks/oragono/irc/custime"
17 18
 	"github.com/tidwall/buntdb"
18 19
 )
19 20
 
@@ -201,7 +202,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
201 202
 	}
202 203
 
203 204
 	// duration
204
-	duration, err := time.ParseDuration(msg.Params[currentArg])
205
+	duration, err := custime.ParseDuration(msg.Params[currentArg])
205 206
 	durationIsUsed := err == nil
206 207
 	if durationIsUsed {
207 208
 		currentArg++

+ 2
- 2
irc/help.go Целия файл

@@ -112,7 +112,7 @@ Bans are saved across subsequent launches of the server.
112 112
 from. If "MYSELF" is not given, trying to DLINE yourself will result in an error.
113 113
 
114 114
 [duration] can be of the following forms:
115
-	10h 8m 13s
115
+	1y 12mo 31d 10h 8m 13s
116 116
 
117 117
 <net> is specified in typical CIDR notation. For example:
118 118
 	127.0.0.1/8
@@ -172,7 +172,7 @@ Bans are saved across subsequent launches of the server.
172 172
 from. If "MYSELF" is not given, trying to KLINE yourself will result in an error.
173 173
 
174 174
 [duration] can be of the following forms:
175
-	10h 8m 13s
175
+	1y 12mo 31d 10h 8m 13s
176 176
 
177 177
 <mask> is specified in typical IRC format. For example:
178 178
 	dan

+ 2
- 1
irc/kline.go Целия файл

@@ -11,6 +11,7 @@ import (
11 11
 
12 12
 	"github.com/DanielOaks/girc-go/ircmatch"
13 13
 	"github.com/DanielOaks/girc-go/ircmsg"
14
+	"github.com/DanielOaks/oragono/irc/custime"
14 15
 	"github.com/tidwall/buntdb"
15 16
 )
16 17
 
@@ -128,7 +129,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
128 129
 	}
129 130
 
130 131
 	// duration
131
-	duration, err := time.ParseDuration(msg.Params[currentArg])
132
+	duration, err := custime.ParseDuration(msg.Params[currentArg])
132 133
 	durationIsUsed := err == nil
133 134
 	if durationIsUsed {
134 135
 		currentArg++

Loading…
Отказ
Запис