javalocaldatepicocli

make picocli parse local date format


PicoCLI accepts 2019-04-26 as input for a LocalDate variable, but it does not accept the German Date format like 26.04.2019.
For that you need:

SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy",Locale.GERMANY);

How do you tell to PicoCLI to use this formatter and not depend on US date input?


Solution

  • You can define a custom type converter, either for a specific option, or globally for all options and positional parameters of a certain type.

    Often the most compact way to register a custom converter is with a lambda expression:

    new CommandLine(new DateFormatDemo())
          .registerConverter(Date.class,
                             s -> new SimpleDateFormat("dd.MM.yyyy", Locale.GERMANY).parse(s))
          .execute(args);
    

    If you need to set a converter for a specific option, you will need to define a class and specify that class in the @Option(converter = X.class) annotation of that option.

    Note that it is okay to throw an exception from the ITypeConverter.convert method if the user input was invalid. Picocli will catch this exception and display an error message to the end user.

    For example:

    class StrictGermanDateConverter implements ITypeConverter<Date> {
        @Override
        public Date convert(String value) throws Exception {
            Date result = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMANY).parse(value);
            if (result.getYear() < 0) {
                throw new IllegalArgumentException("year should be after 1900");
            }
            return result;
        }
    }
    

    Here is an example that uses this stricter converter to demonstrate:

    import picocli.CommandLine;
    import picocli.CommandLine.Command;
    import picocli.CommandLine.ITypeConverter;
    import picocli.CommandLine.Model.CommandSpec;
    import picocli.CommandLine.Option;
    import picocli.CommandLine.Spec;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    import java.util.Locale;
    
    @Command(name = "demo")
    public class DateFormatDemo implements Runnable {
    
        @Option(names = {"-d", "--date"}, description = "Date in German format `dd.MM.yyyy`",
                converter = StrictGermanDateConverter.class, paramLabel = "dd.MM.yyyy")
        Date specialDate;
    
        @Option(names = {"-x", "--default"}, paramLabel = "yyyy-MM-dd",
                description = "This option uses the default converter")
        Date defaultDate;
    
        @Spec CommandSpec spec;
    
        public void run() {
            List<String> args = spec.commandLine().getParseResult().originalArgs();
            System.out.printf("%s -> %s; %s%n", args, specialDate, defaultDate);
        }
    
        public static void main(String[] args) {
    
            // invalid input: shows error message and usage help
            new CommandLine(new DateFormatDemo()).execute("-d=55.55.55");
    
            // alternatively, register a global converter
            // for _all_ Date options
            new CommandLine(new DateFormatDemo())
                  .registerConverter(Date.class,
                       s -> new SimpleDateFormat("MMM.dd.yyyy", Locale.ITALIAN).parse(s))
                  .execute("-d=31.07.1969", "--default=Gennaio.01.2020");
        }
    }
    

    The first invocation with invalid input -d=55.55.55 prints the following output:

    Invalid value for option '--date': cannot convert '55.55.55' to Date (java.lang.IllegalArgumentException: year should be after 1900)
    Usage: demo [-d=dd.MM.yyyy] [-x=yyyy-MM-dd]
      -d, --date=dd.MM.yyyy      Date in German format `dd.MM.yyyy`
      -x, --default=yyyy-MM-dd   This option uses the default converter
    

    The second invocation, where we pass --default=Gennaio.01.2020 to confirm that the global type converter now handles dates in our custom Italian format, gives the following output:

    [-d=31.07.1969, --default=Gennaio.01.2020] -> Thu Jul 31 00:00:00 JST 1969; Wed Jan 01 00:00:00 JST 2020