-
Notifications
You must be signed in to change notification settings - Fork 8
/
date.go
183 lines (163 loc) · 3.83 KB
/
date.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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package cal
import (
"encoding/json"
"errors"
"time"
"cloud.google.com/go/civil"
"github.com/invopop/jsonschema"
"github.com/invopop/validation"
)
// Date represents a simple date without time used most frequently
// with business documents.
type Date struct {
civil.Date
}
// NewDate provides a pointer to a new date instance.
func NewDate(year int, month time.Month, day int) *Date {
d := MakeDate(year, month, day)
return &d
}
// MakeDate provides a new date instance.
func MakeDate(year int, month time.Month, day int) Date {
return Date{
civil.Date{
Year: year,
Month: month,
Day: day,
},
}
}
// Today generates a new date instance for today.
func Today() Date {
t := time.Now().UTC()
return Date{
civil.DateOf(t),
}
}
// TodayIn generates a new date instance for today in the given location.
func TodayIn(loc *time.Location) Date {
t := time.Now().In(loc)
return Date{
civil.DateOf(t),
}
}
// DateOf returns the Date in which a time occurs in the time's location.
func DateOf(t time.Time) Date {
return Date{
civil.DateOf(t),
}
}
// Validate ensures the the date object looks valid.
func (d Date) Validate() error {
if d.IsZero() {
return nil // there is a specific test for this
}
if !d.Date.IsValid() {
return errors.New("invalid date")
}
return nil
}
// Clone returns a new pointer to a copy of the date.
func (d *Date) Clone() *Date {
d2 := *d
return &d2
}
// Time returns a time object for the date.
func (d Date) Time() time.Time {
return d.TimeIn(time.UTC)
}
// TimeIn returns a time object for the date in the given location which
// may be important when considering timezones and arithmetic.
func (d Date) TimeIn(loc *time.Location) time.Time {
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
}
// Add returns a new date with the given number of years, months and days.
// This uses the time package to do the arithmetic.
func (d Date) Add(years, months, days int) Date {
t := d.Time()
t = t.AddDate(years, months, days)
return DateOf(t)
}
// UnmarshalJSON is used to parse a date from json and ensures that
// we can handle invalid data reasonably.
func (d *Date) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
// Zero dates are not great, put pass validation.
if s == "0000-00-00" {
*d = Date{}
return nil
}
dt, err := civil.ParseDate(s)
if err != nil {
return err
}
*d = Date{dt}
return nil
}
// JSONSchema returns a custom json schema for the date.
func (Date) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
Type: "string",
Format: "date",
Title: "Date",
Description: "Civil date in simplified ISO format, like 2021-05-26",
}
}
type dateValidationRule struct {
notZero bool
after *Date
before *Date
}
// Validate is used to check a dates value.
func (d *dateValidationRule) Validate(value interface{}) error {
in, ok := value.(Date)
if !ok {
inp, ok := value.(*Date)
if !ok {
return nil
}
if inp == nil {
return nil
}
in = *inp
}
if d.notZero {
if in.Date.IsZero() {
return errors.New("required")
}
}
if d.after != nil {
if in.Date.DaysSince(d.after.Date) < 0 {
return errors.New("too early")
}
}
if d.before != nil {
if in.Date.DaysSince(d.before.Date) > 0 {
return errors.New("too late")
}
}
return nil
}
// DateNotZero ensures the date is not a zero value.
func DateNotZero() validation.Rule {
return &dateValidationRule{
notZero: true,
}
}
// DateAfter returns a validation rule which checks to ensure the date
// is *after* the provided date.
func DateAfter(date Date) validation.Rule {
return &dateValidationRule{
after: &date,
}
}
// DateBefore is used during validation to ensure the date is before
// the value passed in.
func DateBefore(date Date) validation.Rule {
return &dateValidationRule{
before: &date,
}
}