@@ -2,11 +2,13 @@ package cli
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"flag"
6
7
"fmt"
7
8
"io"
8
9
"os"
9
10
"reflect"
11
+ "regexp"
10
12
"strings"
11
13
"testing"
12
14
"time"
@@ -2259,23 +2261,23 @@ func TestTimestamp_set(t *testing.T) {
2259
2261
ts := timestampValue {
2260
2262
timestamp : nil ,
2261
2263
hasBeenSet : false ,
2262
- layout : "Jan 2, 2006 at 3:04pm (MST)" ,
2264
+ layouts : [] string { "Jan 2, 2006 at 3:04pm (MST)" } ,
2263
2265
}
2264
2266
2265
2267
time1 := "Feb 3, 2013 at 7:54pm (PST)"
2266
- require .NoError (t , ts .Set (time1 ), "Failed to parse time %s with layout %s " , time1 , ts .layout )
2268
+ require .NoError (t , ts .Set (time1 ), "Failed to parse time %s with layouts %v " , time1 , ts .layouts )
2267
2269
require .True (t , ts .hasBeenSet , "hasBeenSet is not true after setting a time" )
2268
2270
2269
2271
ts .hasBeenSet = false
2270
- ts .layout = time .RFC3339
2272
+ ts .layouts = [] string { time .RFC3339 }
2271
2273
time2 := "2006-01-02T15:04:05Z"
2272
- require .NoError (t , ts .Set (time2 ), "Failed to parse time %s with layout %s " , time2 , ts .layout )
2274
+ require .NoError (t , ts .Set (time2 ), "Failed to parse time %s with layout %v " , time2 , ts .layouts )
2273
2275
require .True (t , ts .hasBeenSet , "hasBeenSet is not true after setting a time" )
2274
2276
}
2275
2277
2276
- func TestTimestampFlagApply (t * testing.T ) {
2278
+ func TestTimestampFlagApply_SingleFormat (t * testing.T ) {
2277
2279
expectedResult , _ := time .Parse (time .RFC3339 , "2006-01-02T15:04:05Z" )
2278
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : time .RFC3339 }}
2280
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { time .RFC3339 } }}
2279
2281
set := flag .NewFlagSet ("test" , 0 )
2280
2282
_ = fl .Apply (set )
2281
2283
@@ -2284,9 +2286,226 @@ func TestTimestampFlagApply(t *testing.T) {
2284
2286
assert .Equal (t , expectedResult , set .Lookup ("time" ).Value .(flag.Getter ).Get ())
2285
2287
}
2286
2288
2289
+ func TestTimestampFlagApply_MultipleFormats (t * testing.T ) {
2290
+ now := time .Now ().UTC ()
2291
+
2292
+ testCases := []struct {
2293
+ caseName string
2294
+ layoutsPrecisions map [string ]time.Duration
2295
+ expRes time.Time
2296
+ expErrValidation func (err error ) (validation error )
2297
+ }{
2298
+ {
2299
+ caseName : "all_valid_layouts" ,
2300
+ layoutsPrecisions : map [string ]time.Duration {
2301
+ time .RFC3339 : time .Second ,
2302
+ time .DateTime : time .Second ,
2303
+ time .RFC1123 : time .Second ,
2304
+ },
2305
+ expRes : now .Truncate (time .Second ),
2306
+ },
2307
+ {
2308
+ caseName : "one_invalid_layout" ,
2309
+ layoutsPrecisions : map [string ]time.Duration {
2310
+ time .RFC3339 : time .Second ,
2311
+ time .DateTime : time .Second ,
2312
+ "foo" : 0 ,
2313
+ },
2314
+ expRes : now .Truncate (time .Second ),
2315
+ },
2316
+ {
2317
+ caseName : "multiple_invalid_layouts" ,
2318
+ layoutsPrecisions : map [string ]time.Duration {
2319
+ time .RFC3339 : time .Second ,
2320
+ "foo" : 0 ,
2321
+ time .DateTime : time .Second ,
2322
+ "bar" : 0 ,
2323
+ },
2324
+ expRes : now .Truncate (time .Second ),
2325
+ },
2326
+ {
2327
+ caseName : "all_invalid_layouts" ,
2328
+ layoutsPrecisions : map [string ]time.Duration {
2329
+ "foo" : 0 ,
2330
+ "2024-08-07 74:01:82Z-100" : 0 ,
2331
+ "25:70" : 0 ,
2332
+ "" : 0 ,
2333
+ },
2334
+ expErrValidation : func (err error ) error {
2335
+ if err == nil {
2336
+ return errors .New ("got nil err" )
2337
+ }
2338
+
2339
+ found := regexp .MustCompile (`(cannot parse ".+" as ".*")|(extra text: ".+")` ).Match ([]byte (err .Error ()))
2340
+ if ! found {
2341
+ return fmt .Errorf ("given error does not satisfy pattern: %w" , err )
2342
+ }
2343
+
2344
+ return nil
2345
+ },
2346
+ },
2347
+ {
2348
+ caseName : "empty_layout" ,
2349
+ layoutsPrecisions : map [string ]time.Duration {
2350
+ "" : 0 ,
2351
+ },
2352
+ expErrValidation : func (err error ) error {
2353
+ if err == nil {
2354
+ return errors .New ("got nil err" )
2355
+ }
2356
+
2357
+ found := regexp .MustCompile (`extra text: ".+"` ).Match ([]byte (err .Error ()))
2358
+ if ! found {
2359
+ return fmt .Errorf ("given error does not satisfy pattern: %w" , err )
2360
+ }
2361
+
2362
+ return nil
2363
+ },
2364
+ },
2365
+ {
2366
+ caseName : "nil_layouts_slice" ,
2367
+ expErrValidation : func (err error ) error {
2368
+ if err == nil {
2369
+ return errors .New ("got nil err" )
2370
+ }
2371
+
2372
+ found := regexp .MustCompile (`got nil/empty layouts slice` ).Match ([]byte (err .Error ()))
2373
+ if ! found {
2374
+ return fmt .Errorf ("given error does not satisfy pattern: %w" , err )
2375
+ }
2376
+
2377
+ return nil
2378
+ },
2379
+ },
2380
+ {
2381
+ caseName : "empty_layouts_slice" ,
2382
+ layoutsPrecisions : map [string ]time.Duration {},
2383
+ expErrValidation : func (err error ) error {
2384
+ if err == nil {
2385
+ return errors .New ("got nil err" )
2386
+ }
2387
+
2388
+ found := regexp .MustCompile (`got nil/empty layouts slice` ).Match ([]byte (err .Error ()))
2389
+ if ! found {
2390
+ return fmt .Errorf ("given error does not satisfy pattern: %w" , err )
2391
+ }
2392
+
2393
+ return nil
2394
+ },
2395
+ },
2396
+ }
2397
+
2398
+ // TODO: replace with maps.Keys() (go >= ), lo.Keys() if acceptable
2399
+ getKeys := func (m map [string ]time.Duration ) []string {
2400
+ if m == nil {
2401
+ return nil
2402
+ }
2403
+
2404
+ keys := make ([]string , 0 , len (m ))
2405
+ for k := range m {
2406
+ keys = append (keys , k )
2407
+ }
2408
+ return keys
2409
+ }
2410
+
2411
+ for idx := range testCases {
2412
+ testCase := testCases [idx ]
2413
+ t .Run (testCase .caseName , func (t * testing.T ) {
2414
+ // t.Parallel()
2415
+ fl := TimestampFlag {
2416
+ Name : "time" ,
2417
+ Config : TimestampConfig {
2418
+ Layouts : getKeys (testCase .layoutsPrecisions ),
2419
+ },
2420
+ }
2421
+
2422
+ set := flag .NewFlagSet ("test" , 0 )
2423
+ _ = fl .Apply (set )
2424
+
2425
+ if len (testCase .layoutsPrecisions ) == 0 {
2426
+ err := set .Parse ([]string {"--time" , now .Format (time .RFC3339 )})
2427
+ if testCase .expErrValidation != nil {
2428
+ assert .NoError (t , testCase .expErrValidation (err ))
2429
+ }
2430
+ }
2431
+
2432
+ validLayouts := make ([]string , 0 , len (testCase .layoutsPrecisions ))
2433
+ invalidLayouts := make ([]string , 0 , len (testCase .layoutsPrecisions ))
2434
+
2435
+ // TODO: replace with lo.Filter if acceptable
2436
+ for layout , prec := range testCase .layoutsPrecisions {
2437
+ v , err := time .Parse (layout , now .Format (layout ))
2438
+ if err != nil || prec == 0 || now .Truncate (prec ).UnixNano () != v .Truncate (prec ).UnixNano () {
2439
+ invalidLayouts = append (invalidLayouts , layout )
2440
+ continue
2441
+ }
2442
+ validLayouts = append (validLayouts , layout )
2443
+ }
2444
+
2445
+ for _ , layout := range validLayouts {
2446
+ err := set .Parse ([]string {"--time" , now .Format (layout )})
2447
+ assert .NoError (t , err )
2448
+ if ! testCase .expRes .IsZero () {
2449
+ assert .Equal (t , testCase .expRes , set .Lookup ("time" ).Value .(flag.Getter ).Get ())
2450
+ }
2451
+ }
2452
+
2453
+ for range invalidLayouts {
2454
+ err := set .Parse ([]string {"--time" , now .Format (time .RFC3339 )})
2455
+ if testCase .expErrValidation != nil {
2456
+ assert .NoError (t , testCase .expErrValidation (err ))
2457
+ }
2458
+ }
2459
+ })
2460
+ }
2461
+ }
2462
+
2463
+ func TestTimestampFlagApply_ShortenedLayouts (t * testing.T ) {
2464
+ now := time .Now ().UTC ()
2465
+
2466
+ shortenedLayoutsPrecisions := map [string ]time.Duration {
2467
+ time .Kitchen : time .Minute ,
2468
+ time .Stamp : time .Second ,
2469
+ time .StampMilli : time .Millisecond ,
2470
+ time .StampMicro : time .Microsecond ,
2471
+ time .StampNano : time .Nanosecond ,
2472
+ time .TimeOnly : time .Second ,
2473
+ "15:04" : time .Minute ,
2474
+ }
2475
+
2476
+ // TODO: replace with maps.Keys() (go >= ), lo.Keys() if acceptable
2477
+ getKeys := func (m map [string ]time.Duration ) []string {
2478
+ if m == nil {
2479
+ return nil
2480
+ }
2481
+
2482
+ keys := make ([]string , 0 , len (m ))
2483
+ for k := range m {
2484
+ keys = append (keys , k )
2485
+ }
2486
+ return keys
2487
+ }
2488
+
2489
+ fl := TimestampFlag {
2490
+ Name : "time" ,
2491
+ Config : TimestampConfig {
2492
+ Layouts : getKeys (shortenedLayoutsPrecisions ),
2493
+ },
2494
+ }
2495
+
2496
+ set := flag .NewFlagSet ("test" , 0 )
2497
+ _ = fl .Apply (set )
2498
+
2499
+ for layout , prec := range shortenedLayoutsPrecisions {
2500
+ err := set .Parse ([]string {"--time" , now .Format (layout )})
2501
+ assert .NoError (t , err )
2502
+ assert .Equal (t , now .Truncate (prec ), set .Lookup ("time" ).Value .(flag.Getter ).Get ())
2503
+ }
2504
+ }
2505
+
2287
2506
func TestTimestampFlagApplyValue (t * testing.T ) {
2288
2507
expectedResult , _ := time .Parse (time .RFC3339 , "2006-01-02T15:04:05Z" )
2289
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : time .RFC3339 }, Value : expectedResult }
2508
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { time .RFC3339 } }, Value : expectedResult }
2290
2509
set := flag .NewFlagSet ("test" , 0 )
2291
2510
_ = fl .Apply (set )
2292
2511
@@ -2296,7 +2515,7 @@ func TestTimestampFlagApplyValue(t *testing.T) {
2296
2515
}
2297
2516
2298
2517
func TestTimestampFlagApply_Fail_Parse_Wrong_Layout (t * testing.T ) {
2299
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : "randomlayout" }}
2518
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { "randomlayout" } }}
2300
2519
set := flag .NewFlagSet ("test" , 0 )
2301
2520
set .SetOutput (io .Discard )
2302
2521
_ = fl .Apply (set )
@@ -2306,7 +2525,7 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Layout(t *testing.T) {
2306
2525
}
2307
2526
2308
2527
func TestTimestampFlagApply_Fail_Parse_Wrong_Time (t * testing.T ) {
2309
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : "Jan 2, 2006 at 3:04pm (MST)" }}
2528
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { "Jan 2, 2006 at 3:04pm (MST)" } }}
2310
2529
set := flag .NewFlagSet ("test" , 0 )
2311
2530
set .SetOutput (io .Discard )
2312
2531
_ = fl .Apply (set )
@@ -2318,7 +2537,7 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
2318
2537
func TestTimestampFlagApply_Timezoned (t * testing.T ) {
2319
2538
pdt := time .FixedZone ("PDT" , - 7 * 60 * 60 )
2320
2539
expectedResult , _ := time .Parse (time .RFC3339 , "2006-01-02T15:04:05Z" )
2321
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : time .ANSIC , Timezone : pdt }}
2540
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { time .ANSIC } , Timezone : pdt }}
2322
2541
set := flag .NewFlagSet ("test" , 0 )
2323
2542
_ = fl .Apply (set )
2324
2543
@@ -2519,7 +2738,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
2519
2738
},
2520
2739
{
2521
2740
name : "timestamp" ,
2522
- flag : & TimestampFlag {Name : "flag" , Value : ts , Config : TimestampConfig {Layout : time .RFC3339 }, Sources : EnvVars ("tflag" )},
2741
+ flag : & TimestampFlag {Name : "flag" , Value : ts , Config : TimestampConfig {Layouts : [] string { time .RFC3339 } }, Sources : EnvVars ("tflag" )},
2523
2742
toParse : []string {"--flag" , "2006-11-02T15:04:05Z" },
2524
2743
expect : `--flag value (default: 2005-01-02 15:04:05 +0000 UTC)` + withEnvHint ([]string {"tflag" }, "" ),
2525
2744
environ : map [string ]string {
@@ -2603,7 +2822,7 @@ func TestFlagValue(t *testing.T) {
2603
2822
func TestTimestampFlagApply_WithDestination (t * testing.T ) {
2604
2823
var destination time.Time
2605
2824
expectedResult , _ := time .Parse (time .RFC3339 , "2006-01-02T15:04:05Z" )
2606
- fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layout : time .RFC3339 }, Destination : & destination }
2825
+ fl := TimestampFlag {Name : "time" , Aliases : []string {"t" }, Config : TimestampConfig {Layouts : [] string { time .RFC3339 } }, Destination : & destination }
2607
2826
set := flag .NewFlagSet ("test" , 0 )
2608
2827
_ = fl .Apply (set )
2609
2828
0 commit comments