-
-
Notifications
You must be signed in to change notification settings - Fork 155
/
birthdate.go
144 lines (124 loc) · 3.47 KB
/
birthdate.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
package openid
import (
"bytes"
"fmt"
"io"
"regexp"
"strconv"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/pkg/errors"
)
// https://openid.net/specs/openid-connect-core-1_0.html
//
// End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
// The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY
// format is allowed. Note that depending on the underlying platform's date related function,
// providing just year can result in varying month and day, so the implementers need to
// take this factor into account to correctly process the dates.
type BirthdateClaim struct {
year *int
month *int
day *int
}
func (b BirthdateClaim) Year() int {
if b.year == nil {
return 0
}
return *(b.year)
}
func (b BirthdateClaim) Month() int {
if b.month == nil {
return 0
}
return *(b.month)
}
func (b BirthdateClaim) Day() int {
if b.day == nil {
return 0
}
return *(b.day)
}
func (b *BirthdateClaim) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, `failed to unmarshal JSON string for birthdate claim`)
}
if err := b.Accept(s); err != nil {
return errors.Wrap(err, `failed to accept JSON value for birthdate claim`)
}
return nil
}
func tointptr(v int64) *int {
i := int(v)
return &i
}
var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`)
// Accepts a value read from JSON, and converts it to a BirthdateClaim.
// This method DOES NOT verify the correctness of a date.
// Consumers should check for validity of dates such as Apr 31 et al
func (b *BirthdateClaim) Accept(v interface{}) error {
b.year = nil
b.month = nil
b.day = nil
switch v := v.(type) {
case *BirthdateClaim:
if ptr := v.year; ptr != nil {
year := *ptr
b.year = &year
}
if ptr := v.month; ptr != nil {
month := *ptr
b.month = &month
}
if ptr := v.day; ptr != nil {
day := *ptr
b.day = &day
}
return nil
case string:
// yeah, yeah, regexp is slow. PR's welcome
indices := birthdateRx.FindStringSubmatchIndex(v)
if indices == nil {
return errors.New(`invalid pattern for birthdate`)
}
var tmp BirthdateClaim
// Okay, this really isn't kosher, but we're doing this for
// the coverage game... Because birthdateRx already checked that
// the string contains 3 strings with consecutive decimal values
// we can assume that strconv.ParseInt always succeeds.
// strconv.ParseInt (and strconv.ParseUint that it uses internally)
// only returns range errors, so we should be safe.
year, _ := strconv.ParseInt(v[indices[2]:indices[3]], 10, 64)
if year <= 0 {
return errors.New(`failed to parse birthdate year`)
}
tmp.year = tointptr(year)
month, _ := strconv.ParseInt(v[indices[4]:indices[5]], 10, 64)
if month <= 0 {
return errors.New(`failed to parse birthdate month`)
}
tmp.month = tointptr(month)
day, _ := strconv.ParseInt(v[indices[6]:indices[7]], 10, 64)
if day <= 0 {
return errors.New(`failed to parse birthdate day`)
}
tmp.day = tointptr(day)
*b = tmp
return nil
default:
return errors.Errorf(`invalid type for birthdate: %T`, v)
}
}
func (b BirthdateClaim) encode(dst io.Writer) {
fmt.Fprintf(dst, "%04d-%02d-%02d", b.Year(), b.Month(), b.Day())
}
func (b BirthdateClaim) String() string {
var buf bytes.Buffer
b.encode(&buf)
return buf.String()
}
func (b BirthdateClaim) MarshalText() ([]byte, error) {
var buf bytes.Buffer
b.encode(&buf)
return buf.Bytes(), nil
}