-
Notifications
You must be signed in to change notification settings - Fork 343
/
interval.go
167 lines (138 loc) · 5.05 KB
/
interval.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
Package interval implements custom predicates to match routes
only during some period of time.
Package includes three predicates: Between, Before and After.
All predicates can be created using the date represented as:
- a string in RFC3339 format (see https://golang.org/pkg/time/#pkg-constants)
- a string in RFC3339 format without numeric timezone offset and a location name (see https://golang.org/pkg/time/#LoadLocation)
- an int64 or float64 number corresponding to the given Unix time in seconds since January 1, 1970 UTC.
float64 number will be converted into int64 number.
Between predicate matches only if current date is inside the specified
range of dates. Between predicate requires two dates to be constructed.
Upper boundary must be after lower boundary. Range includes the lower
boundary, but excludes the upper boundary.
Before predicate matches only if current date is before the specified
date. Only one date is required to construct the predicate.
After predicate matches only if current date is after or equal to
the specified date. Only one date is required to construct the predicate.
Examples:
example1: Path("/zalando") && Between("2016-01-01T12:00:00+02:00", "2016-02-01T12:00:00+02:00") -> "https://www.zalando.de";
example2: Path("/zalando") && Between(1451642400, 1454320800) -> "https://www.zalando.de";
example3: Path("/zalando") && Before("2016-02-01T12:00:00+02:00") -> "https://www.zalando.de";
example4: Path("/zalando") && Before(1454320800) -> "https://www.zalando.de";
example5: Path("/zalando") && After("2016-01-01T12:00:00+02:00") -> "https://www.zalando.de";
example6: Path("/zalando") && After(1451642400) -> "https://www.zalando.de";
example7: Path("/zalando") && Between("2021-02-18T00:00:00", "2021-02-18T01:00:00", "Europe/Berlin") -> "https://www.zalando.de";
example8: Path("/zalando") && Before("2021-02-18T00:00:00", "Europe/Berlin") -> "https://www.zalando.de";
example9: Path("/zalando") && After("2021-02-18T00:00:00", "Europe/Berlin") -> "https://www.zalando.de";
*/
package interval
import (
"net/http"
"time"
"github.com/zalando/skipper/predicates"
"github.com/zalando/skipper/routing"
)
type spec int
const (
between spec = iota
before
after
)
const rfc3339nz = "2006-01-02T15:04:05" // RFC3339 without numeric timezone offset
type predicate struct {
typ spec
begin time.Time
end time.Time
getTime func() time.Time
}
// Creates Between predicate.
func NewBetween() routing.PredicateSpec { return between }
// Creates Before predicate.
func NewBefore() routing.PredicateSpec { return before }
// Creates After predicate.
func NewAfter() routing.PredicateSpec { return after }
func (s spec) Name() string {
switch s {
case between:
return predicates.BetweenName
case before:
return predicates.BeforeName
case after:
return predicates.AfterName
default:
panic("invalid interval predicate type")
}
}
func (s spec) Create(args []interface{}) (routing.Predicate, error) {
p := predicate{typ: s, getTime: time.Now}
var loc *time.Location
switch {
case
s == between && len(args) == 3 && parseLocation(args[2], &loc) && parseRFCnz(args[0], &p.begin, loc) && parseRFCnz(args[1], &p.end, loc) && p.begin.Before(p.end),
s == between && len(args) == 2 && parseRFC(args[0], &p.begin) && parseRFC(args[1], &p.end) && p.begin.Before(p.end),
s == between && len(args) == 2 && parseUnix(args[0], &p.begin) && parseUnix(args[1], &p.end) && p.begin.Before(p.end),
s == before && len(args) == 2 && parseLocation(args[1], &loc) && parseRFCnz(args[0], &p.end, loc),
s == before && len(args) == 1 && parseRFC(args[0], &p.end),
s == before && len(args) == 1 && parseUnix(args[0], &p.end),
s == after && len(args) == 2 && parseLocation(args[1], &loc) && parseRFCnz(args[0], &p.begin, loc),
s == after && len(args) == 1 && parseRFC(args[0], &p.begin),
s == after && len(args) == 1 && parseUnix(args[0], &p.begin):
return &p, nil
}
return nil, predicates.ErrInvalidPredicateParameters
}
func parseUnix(arg interface{}, t *time.Time) bool {
switch a := arg.(type) {
case float64:
*t = time.Unix(int64(a), 0)
return true
case int64:
*t = time.Unix(a, 0)
return true
}
return false
}
func parseRFC(arg interface{}, t *time.Time) bool {
if s, ok := arg.(string); ok {
tt, err := time.Parse(time.RFC3339, s)
if err == nil {
*t = tt
return true
}
}
return false
}
func parseRFCnz(arg interface{}, t *time.Time, loc *time.Location) bool {
if s, ok := arg.(string); ok {
tt, err := time.ParseInLocation(rfc3339nz, s, loc)
if err == nil {
*t = tt
return true
}
}
return false
}
func parseLocation(arg interface{}, loc **time.Location) bool {
if s, ok := arg.(string); ok {
location, err := time.LoadLocation(s)
if err == nil {
*loc = location
return true
}
}
return false
}
func (p *predicate) Match(r *http.Request) bool {
now := p.getTime()
switch p.typ {
case between:
return (p.begin.Before(now) || p.begin.Equal(now)) && p.end.After(now)
case before:
return p.end.After(now)
case after:
return p.begin.Before(now) || p.begin.Equal(now)
default:
return false
}
}