github.com/kamalshkeir/kencoding@v0.0.2-0.20230409043843-44b609a0475a/iso8601/valid.go (about) 1 package iso8601 2 3 // ValidFlags is a bitset type used to configure the behavior of the Valid 4 //function. 5 type ValidFlags int 6 7 const ( 8 // Strict is a validation flag used to represent a string iso8601 validation 9 // (this is the default). 10 Strict ValidFlags = 0 11 12 // AllowSpaceSeparator allows the presence of a space instead of a 'T' as 13 // separator between the date and time. 14 AllowSpaceSeparator ValidFlags = 1 << iota 15 16 // AllowMissingTime allows the value to contain only a date. 17 AllowMissingTime 18 19 // AllowMissingSubsecond allows the value to contain only a date and time. 20 AllowMissingSubsecond 21 22 // AllowMissingTimezone allows the value to be missing the timezone 23 // information. 24 AllowMissingTimezone 25 26 // AllowNumericTimezone allows the value to represent timezones in their 27 // numeric form. 28 AllowNumericTimezone 29 30 // Flexible is a combination of all validation flag that allow for 31 // non-strict checking of the input value. 32 Flexible = AllowSpaceSeparator | AllowMissingTime | AllowMissingSubsecond | AllowMissingTimezone | AllowNumericTimezone 33 ) 34 35 // Valid check value to verify whether or not it is a valid iso8601 time 36 // representation. 37 func Valid(value string, flags ValidFlags) bool { 38 var ok bool 39 40 // year 41 if value, ok = readDigits(value, 4, 4); !ok { 42 return false 43 } 44 45 if value, ok = readByte(value, '-'); !ok { 46 return false 47 } 48 49 // month 50 if value, ok = readDigits(value, 2, 2); !ok { 51 return false 52 } 53 54 if value, ok = readByte(value, '-'); !ok { 55 return false 56 } 57 58 // day 59 if value, ok = readDigits(value, 2, 2); !ok { 60 return false 61 } 62 63 if len(value) == 0 && (flags&AllowMissingTime) != 0 { 64 return true // date only 65 } 66 67 // separator 68 if value, ok = readByte(value, 'T'); !ok { 69 if (flags & AllowSpaceSeparator) == 0 { 70 return false 71 } 72 if value, ok = readByte(value, ' '); !ok { 73 return false 74 } 75 } 76 77 // hour 78 if value, ok = readDigits(value, 2, 2); !ok { 79 return false 80 } 81 82 if value, ok = readByte(value, ':'); !ok { 83 return false 84 } 85 86 // minute 87 if value, ok = readDigits(value, 2, 2); !ok { 88 return false 89 } 90 91 if value, ok = readByte(value, ':'); !ok { 92 return false 93 } 94 95 // second 96 if value, ok = readDigits(value, 2, 2); !ok { 97 return false 98 } 99 100 // microsecond 101 if value, ok = readByte(value, '.'); !ok { 102 if (flags & AllowMissingSubsecond) == 0 { 103 return false 104 } 105 } else { 106 if value, ok = readDigits(value, 1, 9); !ok { 107 return false 108 } 109 } 110 111 if len(value) == 0 && (flags&AllowMissingTimezone) != 0 { 112 return true // date and time 113 } 114 115 // timezone 116 if value, ok = readByte(value, 'Z'); ok { 117 return len(value) == 0 118 } 119 120 if (flags & AllowSpaceSeparator) != 0 { 121 value, _ = readByte(value, ' ') 122 } 123 124 if value, ok = readByte(value, '+'); !ok { 125 if value, ok = readByte(value, '-'); !ok { 126 return false 127 } 128 } 129 130 // timezone hour 131 if value, ok = readDigits(value, 2, 2); !ok { 132 return false 133 } 134 135 if value, ok = readByte(value, ':'); !ok { 136 if (flags & AllowNumericTimezone) == 0 { 137 return false 138 } 139 } 140 141 // timezone minute 142 if value, ok = readDigits(value, 2, 2); !ok { 143 return false 144 } 145 146 return len(value) == 0 147 } 148 149 func readDigits(value string, min, max int) (string, bool) { 150 if len(value) < min { 151 return value, false 152 } 153 154 i := 0 155 156 for i < max && i < len(value) && isDigit(value[i]) { 157 i++ 158 } 159 160 if i < max && i < min { 161 return value, false 162 } 163 164 return value[i:], true 165 } 166 167 func readByte(value string, c byte) (string, bool) { 168 if len(value) == 0 { 169 return value, false 170 } 171 if value[0] != c { 172 return value, false 173 } 174 return value[1:], true 175 } 176 177 func isDigit(c byte) bool { 178 return '0' <= c && c <= '9' 179 }