-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathserialize.go
135 lines (122 loc) · 3.39 KB
/
serialize.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
package cronrange
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
var (
strSingleWhitespace = ` `
strDoubleQuotation = `"`
strSemicolon = `;`
strMarkDuration = `DR=`
strMarkTimeZone = `TZ=`
errIncompleteExpr = errors.New("expression should contain at least two parts")
errMissDurationExpr = errors.New("duration is missing from the expression")
errEmptyExpr = errors.New("expression is empty")
errJSONNoQuotationFix = errors.New(`json string should start and end with '"'`)
)
// String returns a normalized CronRange expression, which can be consumed by ParseString().
func (cr CronRange) String() string {
sb := strings.Builder{}
sb.Grow(36)
if cr.duration > 0 {
sb.WriteString(strMarkDuration)
sb.WriteString(strconv.FormatUint(uint64(cr.duration/time.Minute), 10))
sb.WriteString(strSemicolon)
sb.WriteString(strSingleWhitespace)
}
if len(cr.timeZone) > 0 {
sb.WriteString(strMarkTimeZone)
sb.WriteString(cr.timeZone)
sb.WriteString(strSemicolon)
sb.WriteString(strSingleWhitespace)
}
sb.WriteString(cr.cronExpression)
return sb.String()
}
// ParseString attempts to deserialize the given expression or return failure if any parsing errors occur.
func ParseString(s string) (cr *CronRange, err error) {
if s == "" {
err = errEmptyExpr
return
}
var (
cronExpr, timeZone, durStr string
durMin uint64
parts = strings.Split(s, strSemicolon)
idxExpr = len(parts) - 1
)
if idxExpr == 0 {
err = errIncompleteExpr
return
}
PL:
for idx, part := range parts {
part = strings.TrimSpace(part)
// skip empty part
if part == "" {
continue
}
switch {
case idx == idxExpr:
// cron expression must be the last part
cronExpr = part
case strings.HasPrefix(part, strMarkDuration):
durStr = part[len(strMarkDuration):]
if durMin, err = strconv.ParseUint(durStr, 10, 64); err != nil {
break PL
}
case strings.HasPrefix(part, strMarkTimeZone):
timeZone = part[len(strMarkTimeZone):]
default:
err = fmt.Errorf(`expression got unknown part: %q`, part)
}
}
if err == nil {
if len(durStr) > 0 {
cr, err = New(cronExpr, timeZone, durMin)
} else {
err = errMissDurationExpr
}
}
return
}
// MarshalJSON implements the encoding/json.Marshaler interface for serialization of CronRange.
func (cr CronRange) MarshalJSON() ([]byte, error) {
expr := cr.String()
if expr == "" {
return []byte("null"), nil
}
return json.Marshal(expr)
}
// UnmarshalJSON implements the encoding/json.Unmarshaler interface for deserialization of CronRange.
func (cr *CronRange) UnmarshalJSON(b []byte) (err error) {
// Precondition checks
raw := string(b)
if raw == "" {
return errEmptyExpr
}
if !(strings.HasPrefix(raw, strDoubleQuotation) && strings.HasSuffix(raw, strDoubleQuotation) && len(raw) >= 2) {
return errJSONNoQuotationFix
}
// Extract and treat as CronRange expression
var newCr *CronRange
if newCr, err = ParseString(raw[1 : len(raw)-1]); err == nil {
*cr = *newCr
}
return
}
// String returns a string representing time range with formatted time values in Internet RFC 3339 format.
func (tr TimeRange) String() string {
sb := strings.Builder{}
sb.Grow(54)
sb.WriteString("[")
sb.WriteString(tr.Start.Format(time.RFC3339))
sb.WriteString(",")
sb.WriteString(tr.End.Format(time.RFC3339))
sb.WriteString("]")
return sb.String()
}