-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
uid.go
149 lines (133 loc) · 4.56 KB
/
uid.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
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package uid supports generating unique IDs. Its chief purpose is to prevent
// multiple test executions from interfering with each other, and to facilitate
// cleanup of old entities that may remain if tests exit early.
package uid
import (
"fmt"
"regexp"
"strconv"
"sync/atomic"
"time"
)
// A Space manages a set of unique IDs distinguished by a prefix.
type Space struct {
Prefix string // Prefix of UIDs. Read-only.
Sep rune // Separates UID parts. Read-only.
Time time.Time // Timestamp for UIDs. Read-only.
re *regexp.Regexp
count int32 // atomic
short bool
}
// Options are optional values for a Space.
type Options struct {
Sep rune // Separates parts of the UID. Defaults to '-'.
Time time.Time // Timestamp for all UIDs made with this space. Defaults to current time.
// Short, if true, makes the result of space.New shorter by 6 characters.
// This can be useful for character restricted IDs. It will use a shorter
// but less readable time representation, and will only use two characters
// for the count suffix instead of four.
//
// e.x. normal: gotest-20181030-59751273685000-0001
// e.x. short: gotest-1540917351273685000-01
Short bool
}
// NewSpace creates a new UID space. A UID Space is used to generate unique IDs.
func NewSpace(prefix string, opts *Options) *Space {
var short bool
sep := '-'
tm := time.Now().UTC()
if opts != nil {
short = opts.Short
if opts.Sep != 0 {
sep = opts.Sep
}
if !opts.Time.IsZero() {
tm = opts.Time
}
}
var re string
if short {
re = fmt.Sprintf(`^%s%[2]c(\d+)%[2]c\d+$`, regexp.QuoteMeta(prefix), sep)
} else {
re = fmt.Sprintf(`^%s%[2]c(\d{4})(\d{2})(\d{2})%[2]c(\d+)%[2]c\d+$`,
regexp.QuoteMeta(prefix), sep)
}
return &Space{
Prefix: prefix,
Sep: sep,
Time: tm,
re: regexp.MustCompile(re),
short: short,
}
}
// New generates a new unique ID. The ID consists of the Space's prefix, a
// timestamp, and a counter value. All unique IDs generated in the same test
// execution will have the same timestamp.
//
// Aside from the characters in the prefix, IDs contain only letters, numbers
// and sep.
func (s *Space) New() string {
c := atomic.AddInt32(&s.count, 1)
if s.short && c > 99 {
// Short spaces only have space for 99 IDs. (two characters)
panic("Short space called New more than 99 times. Ran out of IDs.")
} else if c > 9999 {
// Spaces only have space for 9999 IDs. (four characters)
panic("New called more than 9999 times. Ran out of IDs.")
}
if s.short {
return fmt.Sprintf("%s%c%d%c%02d", s.Prefix, s.Sep, s.Time.UnixNano(), s.Sep, c)
}
// Write the time as a date followed by nanoseconds from midnight of that date.
// That makes it easier to see the approximate time of the ID when it is displayed.
y, m, d := s.Time.Date()
ns := s.Time.Sub(time.Date(y, m, d, 0, 0, 0, 0, time.UTC))
// Zero-pad the counter for lexical sort order for IDs with the same timestamp.
return fmt.Sprintf("%s%c%04d%02d%02d%c%d%c%04d",
s.Prefix, s.Sep, y, m, d, s.Sep, ns, s.Sep, c)
}
// Timestamp extracts the timestamp of uid, which must have been generated by
// s. The second return value is true on success, false if there was a problem.
func (s *Space) Timestamp(uid string) (time.Time, bool) {
subs := s.re.FindStringSubmatch(uid)
if subs == nil {
return time.Time{}, false
}
if s.short {
ns, err := strconv.ParseInt(subs[1], 10, 64)
if err != nil {
return time.Time{}, false
}
return time.Unix(ns/1e9, ns%1e9), true
}
y, err1 := strconv.Atoi(subs[1])
m, err2 := strconv.Atoi(subs[2])
d, err3 := strconv.Atoi(subs[3])
ns, err4 := strconv.Atoi(subs[4])
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
return time.Time{}, false
}
return time.Date(y, time.Month(m), d, 0, 0, 0, ns, time.UTC), true
}
// Older reports whether uid was created by m and has a timestamp older than
// the current time by at least d.
func (s *Space) Older(uid string, d time.Duration) bool {
ts, ok := s.Timestamp(uid)
if !ok {
return false
}
return time.Since(ts) > d
}