github.com/segmentio/encoding@v0.4.0/iso8601/valid_test.go (about)

     1  package iso8601
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  )
     7  
     8  func TestValidate(t *testing.T) {
     9  	tests := []struct {
    10  		value string
    11  		flags ValidFlags
    12  		valid bool
    13  	}{
    14  		// valid
    15  		{"2018-01-01T23:42:59.123456789Z", Strict, true},
    16  		{"2018-01-01T23:42:59.123456789+07:00", Strict, true},
    17  		{"2018-01-01T23:42:59.123456789-07:00", Strict, true},
    18  		{"2018-01-01T23:42:59.000+07:00", Strict, true},
    19  
    20  		{"2018-01-01", Flexible, true},
    21  		{"2018-01-01 23:42:59", Flexible, true},
    22  		{"2018-01-01T23:42:59.123-0700", Flexible, true},
    23  
    24  		// invalid
    25  		{"", Flexible, false},                                 // empty string
    26  		{"whatever", Flexible, false},                         // not a time
    27  		{"2018-01-01", Strict, false},                         // missing time
    28  		{"2018-01-01 23:42:59-0700", Strict, false},           // missing subsecond
    29  		{"2018-01-01T23:42:59.123456789+0700", Strict, false}, // don't allow numeric time zone
    30  		{"2018_01-01T23:42:59.123456789Z", Strict, false},     // invalid date separator (first)
    31  		{"2018-01_01T23:42:59.123456789Z", Strict, false},     // invalid date separator (second)
    32  		{"2018-01-01 23:42:59.123456789Z", Strict, false},     // invalid date-time separator
    33  		{"2018-01-01T23-42:59.123456789Z", Strict, false},     // invalid time separator (first)
    34  		{"2018-01-01T23:42-59.123456789Z", Strict, false},     // invalid time separator (second)
    35  		{"2018-01-01T23:42:59,123456789Z", Strict, false},     // invalid decimal separator
    36  		{"2018-01-01T23:42:59.123456789", Strict, false},      // missing timezone
    37  		{"18-01-01T23:42:59.123456789Z", Strict, false},       // 2-digit year
    38  		{"2018-1-01T23:42:59.123456789Z", Strict, false},      // 1-digit month
    39  		{"2018-01-1T23:42:59.123456789Z", Strict, false},      // 1-digit day
    40  		{"2018-01-01T3:42:59.123456789Z", Strict, false},      // 1-digit hour
    41  		{"2018-01-01T23:2:59.123456789Z", Strict, false},      // 1-digit minute
    42  		{"2018-01-01T23:42:9.123456789Z", Strict, false},      // 1-digit second
    43  		{"2018-01-01T23:42:59.Z", Strict, false},              // not enough subsecond digits
    44  		{"2018-01-01T23:42:59.1234567890Z", Strict, false},    // too many subsecond digits
    45  		{"2018-01-01T23:42:59.123456789+7:00", Strict, false}, // 1-digit timezone hour
    46  		{"2018-01-01T23:42:59.123456789+07:0", Strict, false}, // 1-digit timezone minute
    47  		{"2018-01-01_23:42:59", Flexible, false},              // invalid date-time separator (not a space)
    48  	}
    49  
    50  	for _, test := range tests {
    51  		if test.valid != Valid(test.value, test.flags) {
    52  			t.Errorf("%q expected Valid to return %t", test.value, test.valid)
    53  		} else if test.valid {
    54  			if !isIsoString(test.value) {
    55  				t.Errorf("behavior mismatch, isIsoString says %q must not be a valid date", test.value)
    56  			}
    57  		} else if test.flags != Strict {
    58  			if isIsoString(test.value) {
    59  				t.Errorf("behavior mismatch, isIsoString says %q must be a valid date", test.value)
    60  			}
    61  		}
    62  	}
    63  }
    64  
    65  func BenchmarkValidate(b *testing.B) {
    66  	b.Run("success", benchmarkValidateSuccess)
    67  	b.Run("failure", benchmarkValidateFailure)
    68  }
    69  
    70  func benchmarkValidateSuccess(b *testing.B) {
    71  	for i := 0; i < b.N; i++ {
    72  		if !Valid("2018-01-01T23:42:59.123456789Z", Flexible) {
    73  			b.Fatal("not valid")
    74  		}
    75  	}
    76  }
    77  
    78  func benchmarkValidateFailure(b *testing.B) {
    79  	for i := 0; i < b.N; i++ {
    80  		if Valid("2018-01-01T23:42:59 oops!", Flexible) {
    81  			b.Fatal("valid but should not")
    82  		}
    83  	}
    84  }
    85  
    86  func BenchmarkTimeParse(b *testing.B) {
    87  	b.Run("success", benchmarkTimeParseSuccess)
    88  	b.Run("failure", benchmarkTimeParseFailure)
    89  }
    90  
    91  func benchmarkTimeParseSuccess(b *testing.B) {
    92  	for i := 0; i < b.N; i++ {
    93  		if _, err := time.Parse(time.RFC3339Nano, "2018-01-01T23:42:59.123456789Z"); err != nil {
    94  			b.Fatal("not valid")
    95  		}
    96  	}
    97  }
    98  
    99  func benchmarkTimeParseFailure(b *testing.B) {
   100  	for i := 0; i < b.N; i++ {
   101  		if _, err := time.Parse(time.RFC3339Nano, "2018-01-01T23:42:59 oops!"); err == nil {
   102  			b.Fatal("valid but should not")
   103  		}
   104  	}
   105  }
   106  
   107  // =============================================================================
   108  // This code is extracted from a library we had that we are replacing with this
   109  // package, we use it to verify that the behavior matches.
   110  // =============================================================================
   111  var validDates = [...]string{
   112  	time.RFC3339Nano,
   113  	time.RFC3339,
   114  	"2006-01-02T15:04:05.999-0700",
   115  	"2006-01-02 15:04:05",
   116  	"2006-01-02",
   117  }
   118  
   119  func isIsoString(str string) bool {
   120  	// Per RFC3339Nano Spec a date should never be more than 35 chars.
   121  	if len(str) > 36 {
   122  		return false
   123  	}
   124  
   125  	for _, format := range validDates {
   126  		if _, err := time.Parse(format, str); err == nil {
   127  			return true
   128  		}
   129  	}
   130  
   131  	return false
   132  }