1package stdlib 2 3import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/zclconf/go-cty/cty" 9) 10 11func TestFormatDate(t *testing.T) { 12 tests := []struct { 13 Format cty.Value 14 Want cty.Value 15 Err string 16 }{ 17 { 18 cty.StringVal(""), // pointless, but valid 19 cty.StringVal(""), 20 ``, 21 }, 22 { 23 cty.StringVal("YYYY-MM-DD"), 24 cty.StringVal("2006-01-02"), 25 ``, 26 }, 27 { 28 cty.StringVal("EEE, MMM D ''YY"), 29 cty.StringVal("Mon, Jan 2 '06"), 30 ``, 31 }, 32 { 33 cty.StringVal("hh:mm:ss"), 34 cty.StringVal("15:04:05"), 35 ``, 36 }, 37 { 38 cty.StringVal("H 'o''clock' AA"), 39 cty.StringVal("3 o'clock PM"), 40 ``, 41 }, 42 { 43 cty.StringVal("H 'o''clock'"), 44 cty.StringVal("3 o'clock"), 45 ``, 46 }, 47 { 48 cty.StringVal("hh:mm:ssZZZZ"), 49 cty.StringVal("15:04:05+0000"), 50 ``, 51 }, 52 { 53 cty.StringVal("hh:mm:ssZZZZZ"), 54 cty.StringVal("15:04:05+00:00"), 55 ``, 56 }, 57 { 58 cty.StringVal("MMMM"), 59 cty.StringVal("January"), 60 ``, 61 }, 62 { 63 cty.StringVal("EEEE"), 64 cty.StringVal("Monday"), 65 ``, 66 }, 67 { 68 cty.StringVal("aa"), 69 cty.StringVal("pm"), 70 ``, 71 }, 72 73 // Some common standard machine-oriented formats 74 { 75 cty.StringVal("YYYY-MM-DD'T'hh:mm:ssZ"), // RFC3339 76 cty.StringVal("2006-01-02T15:04:05Z"), // (since RFC3339 is the input format too, this is a bit pointless) 77 ``, 78 }, 79 { 80 cty.StringVal("DD MMM YYYY hh:mm ZZZ"), // RFC822 81 cty.StringVal("02 Jan 2006 15:04 UTC"), 82 ``, 83 }, 84 { 85 cty.StringVal("EEEE, DD-MMM-YY hh:mm:ss ZZZ"), // RFC850 86 cty.StringVal("Monday, 02-Jan-06 15:04:05 UTC"), 87 ``, 88 }, 89 { 90 cty.StringVal("EEE, DD MMM YYYY hh:mm:ss ZZZ"), // RFC1123 91 cty.StringVal("Mon, 02 Jan 2006 15:04:05 UTC"), 92 ``, 93 }, 94 95 // Invalids 96 { 97 cty.StringVal("Y"), 98 cty.NilVal, 99 `invalid date format verb "Y": year must either be "YY" or "YYYY"`, 100 }, 101 { 102 cty.StringVal("YYYYY"), 103 cty.NilVal, 104 `invalid date format verb "YYYYY": year must either be "YY" or "YYYY"`, 105 }, 106 { 107 cty.StringVal("A"), 108 cty.NilVal, 109 `invalid date format verb "A": must be "AA"`, 110 }, 111 { 112 cty.StringVal("a"), 113 cty.NilVal, 114 `invalid date format verb "a": must be "aa"`, 115 }, 116 { 117 cty.StringVal("'blah blah"), 118 cty.NilVal, 119 `unterminated literal '`, 120 }, 121 { 122 cty.StringVal("'"), 123 cty.NilVal, 124 `unterminated literal '`, 125 }, 126 } 127 ts := time.Date(2006, time.January, 2, 15, 04, 05, 0, time.UTC) 128 timeVal := cty.StringVal(ts.Format(time.RFC3339)) 129 for _, test := range tests { 130 t.Run(test.Format.GoString(), func(t *testing.T) { 131 got, err := FormatDate(test.Format, timeVal) 132 133 if test.Err != "" { 134 if err == nil { 135 t.Fatalf("no error; want error %q", test.Err) 136 } 137 138 if got, want := err.Error(), test.Err; got != want { 139 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 140 } 141 } else { 142 if err != nil { 143 t.Fatalf("unexpected error: %s", err) 144 } 145 146 if !got.RawEquals(test.Want) { 147 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 148 } 149 } 150 }) 151 } 152 153 parseErrTests := []struct { 154 Timestamp cty.Value 155 Err string 156 }{ 157 { 158 cty.StringVal(""), 159 `not a valid RFC3339 timestamp: end of string before year`, 160 }, 161 { 162 cty.StringVal("2017-01-02"), 163 `not a valid RFC3339 timestamp: missing required time introducer 'T'`, 164 }, 165 { 166 cty.StringVal(`2017-12-02t00:00:00Z`), 167 `not a valid RFC3339 timestamp: missing required time introducer 'T'`, 168 }, 169 { 170 cty.StringVal("2017:01:02"), 171 `not a valid RFC3339 timestamp: found ":01:02" where "-" is expected`, 172 }, 173 { 174 cty.StringVal("2017"), 175 `not a valid RFC3339 timestamp: end of string where "-" is expected`, 176 }, 177 { 178 cty.StringVal("2017-01-02T"), 179 `not a valid RFC3339 timestamp: end of string before hour`, 180 }, 181 { 182 cty.StringVal("2017-01-02T00"), 183 `not a valid RFC3339 timestamp: end of string where ":" is expected`, 184 }, 185 { 186 cty.StringVal("2017-01-02T00:00:00"), 187 `not a valid RFC3339 timestamp: end of string before UTC offset`, 188 }, 189 { 190 cty.StringVal("2017-01-02T26:00:00Z"), 191 // This one generates an odd message due to an apparent quirk in 192 // the Go time parser. Ideally it would use "26" as the errant string. 193 `not a valid RFC3339 timestamp: cannot use ":00:00Z" as hour`, 194 }, 195 { 196 cty.StringVal("2017-13-02T00:00:00Z"), 197 // This one generates an odd message due to an apparent quirk in 198 // the Go time parser. Ideally it would use "13" as the errant string. 199 `not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as month`, 200 }, 201 { 202 cty.StringVal("2017-02-31T00:00:00Z"), 203 `not a valid RFC3339 timestamp: day out of range`, 204 }, 205 { 206 cty.StringVal(`"2017-12-02T00:00:00Z"`), 207 `not a valid RFC3339 timestamp: cannot use "\"2017-12-02T00:00:00Z\"" as year`, 208 }, 209 { 210 cty.StringVal(`2-12-02T00:00:00Z`), 211 // Go parser seems to be trying to parse "2-12" as a year here, 212 // producing a confusing error message. 213 `not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as year`, 214 }, 215 } 216 for _, test := range parseErrTests { 217 t.Run(fmt.Sprintf("%s parse error", test.Timestamp.AsString()), func(t *testing.T) { 218 _, err := FormatDate(cty.StringVal(""), test.Timestamp) 219 220 if err == nil { 221 t.Fatalf("no error; want error %q", test.Err) 222 } 223 224 if got, want := err.Error(), test.Err; got != want { 225 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 226 } 227 }) 228 } 229} 230