github.com/segmentio/encoding@v0.4.0/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  }