File: valid.go

package info (click to toggle)
golang-github-segmentio-encoding 0.5.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 14,468 kB
  • sloc: makefile: 286
file content (179 lines) | stat: -rw-r--r-- 3,579 bytes parent folder | download
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
package iso8601

// ValidFlags is a bitset type used to configure the behavior of the Valid
// function.
type ValidFlags int

const (
	// Strict is a validation flag used to represent a string iso8601 validation
	// (this is the default).
	Strict ValidFlags = 0

	// AllowSpaceSeparator allows the presence of a space instead of a 'T' as
	// separator between the date and time.
	AllowSpaceSeparator ValidFlags = 1 << iota

	// AllowMissingTime allows the value to contain only a date.
	AllowMissingTime

	// AllowMissingSubsecond allows the value to contain only a date and time.
	AllowMissingSubsecond

	// AllowMissingTimezone allows the value to be missing the timezone
	// information.
	AllowMissingTimezone

	// AllowNumericTimezone allows the value to represent timezones in their
	// numeric form.
	AllowNumericTimezone

	// Flexible is a combination of all validation flag that allow for
	// non-strict checking of the input value.
	Flexible = AllowSpaceSeparator | AllowMissingTime | AllowMissingSubsecond | AllowMissingTimezone | AllowNumericTimezone
)

// Valid check value to verify whether or not it is a valid iso8601 time
// representation.
func Valid(value string, flags ValidFlags) bool {
	var ok bool

	// year
	if value, ok = readDigits(value, 4, 4); !ok {
		return false
	}

	if value, ok = readByte(value, '-'); !ok {
		return false
	}

	// month
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	if value, ok = readByte(value, '-'); !ok {
		return false
	}

	// day
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	if len(value) == 0 && (flags&AllowMissingTime) != 0 {
		return true // date only
	}

	// separator
	if value, ok = readByte(value, 'T'); !ok {
		if (flags & AllowSpaceSeparator) == 0 {
			return false
		}
		if value, ok = readByte(value, ' '); !ok {
			return false
		}
	}

	// hour
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	if value, ok = readByte(value, ':'); !ok {
		return false
	}

	// minute
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	if value, ok = readByte(value, ':'); !ok {
		return false
	}

	// second
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	// microsecond
	if value, ok = readByte(value, '.'); !ok {
		if (flags & AllowMissingSubsecond) == 0 {
			return false
		}
	} else {
		if value, ok = readDigits(value, 1, 9); !ok {
			return false
		}
	}

	if len(value) == 0 && (flags&AllowMissingTimezone) != 0 {
		return true // date and time
	}

	// timezone
	if value, ok = readByte(value, 'Z'); ok {
		return len(value) == 0
	}

	if (flags & AllowSpaceSeparator) != 0 {
		value, _ = readByte(value, ' ')
	}

	if value, ok = readByte(value, '+'); !ok {
		if value, ok = readByte(value, '-'); !ok {
			return false
		}
	}

	// timezone hour
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	if value, ok = readByte(value, ':'); !ok {
		if (flags & AllowNumericTimezone) == 0 {
			return false
		}
	}

	// timezone minute
	if value, ok = readDigits(value, 2, 2); !ok {
		return false
	}

	return len(value) == 0
}

func readDigits(value string, min, max int) (string, bool) {
	if len(value) < min {
		return value, false
	}

	i := 0

	for i < max && i < len(value) && isDigit(value[i]) {
		i++
	}

	if i < max && i < min {
		return value, false
	}

	return value[i:], true
}

func readByte(value string, c byte) (string, bool) {
	if len(value) == 0 {
		return value, false
	}
	if value[0] != c {
		return value, false
	}
	return value[1:], true
}

func isDigit(c byte) bool {
	return '0' <= c && c <= '9'
}